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.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValues; import org.apache.lucene.search.DoubleValues;
import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.LeafGeoPointFieldData;
import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.MultiGeoPointValues;
import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData;
import java.io.IOException; import java.io.IOException;
@ -28,7 +28,7 @@ final class GeoEmptyValueSource extends FieldDataBasedDoubleValuesSource {
@Override @Override
public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) { public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) {
LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf); LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf);
final MultiGeoPointValues values = leafData.getGeoPointValues(); final MultiGeoPointValues values = leafData.getPointValues(); // shade
return new DoubleValues() { return new DoubleValues() {
@Override @Override
public double doubleValue() { public double doubleValue() {

View file

@ -11,8 +11,8 @@ package org.elasticsearch.script.expression;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValues; import org.apache.lucene.search.DoubleValues;
import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.LeafGeoPointFieldData;
import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.MultiGeoPointValues;
import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData;
import java.io.IOException; import java.io.IOException;
@ -28,7 +28,7 @@ final class GeoLatitudeValueSource extends FieldDataBasedDoubleValuesSource {
@Override @Override
public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) { public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) {
LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf); LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf);
final MultiGeoPointValues values = leafData.getGeoPointValues(); final MultiGeoPointValues values = leafData.getPointValues();
return new DoubleValues() { return new DoubleValues() {
@Override @Override
public double doubleValue() throws IOException { 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.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValues; import org.apache.lucene.search.DoubleValues;
import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.LeafGeoPointFieldData;
import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.MultiGeoPointValues;
import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData;
import java.io.IOException; import java.io.IOException;
@ -28,7 +28,7 @@ final class GeoLongitudeValueSource extends FieldDataBasedDoubleValuesSource {
@Override @Override
public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) { public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) {
LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf); LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf);
final MultiGeoPointValues values = leafData.getGeoPointValues(); final MultiGeoPointValues values = leafData.getPointValues();
return new DoubleValues() { return new DoubleValues() {
@Override @Override
public double doubleValue() throws IOException { public double doubleValue() throws IOException {
@ -53,8 +53,7 @@ final class GeoLongitudeValueSource extends FieldDataBasedDoubleValuesSource {
if (obj == null) return false; if (obj == null) return false;
if (getClass() != obj.getClass()) return false; if (getClass() != obj.getClass()) return false;
GeoLongitudeValueSource other = (GeoLongitudeValueSource) obj; GeoLongitudeValueSource other = (GeoLongitudeValueSource) obj;
if (fieldData.equals(other.fieldData) == false) return false; return fieldData.equals(other.fieldData);
return true;
} }
@Override @Override

View file

@ -218,7 +218,7 @@ public class MissingValueIT extends ESIntegTestCase {
assertSearchResponse(response); assertSearchResponse(response);
GeoCentroid centroid = response.getAggregations().get("centroid"); GeoCentroid centroid = response.getAggregations().get("centroid");
GeoPoint point = new GeoPoint(1.5, 1.5); GeoPoint point = new GeoPoint(1.5, 1.5);
assertThat(point.lat(), closeTo(centroid.centroid().lat(), 1E-5)); assertThat(point.getY(), closeTo(centroid.centroid().getY(), 1E-5));
assertThat(point.lon(), closeTo(centroid.centroid().lon(), 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(response.getHits().getTotalHits().value, equalTo(0L));
assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid, notNullValue());
assertThat(geoCentroid.getName(), equalTo(aggName)); assertThat(geoCentroid.getName(), equalTo(aggName));
GeoPoint centroid = geoCentroid.centroid(); assertThat(geoCentroid.centroid(), equalTo(null));
assertThat(centroid, equalTo(null));
assertEquals(0, geoCentroid.count()); assertEquals(0, geoCentroid.count());
} }
@ -59,8 +58,7 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
GeoCentroid geoCentroid = response.getAggregations().get(aggName); GeoCentroid geoCentroid = response.getAggregations().get(aggName);
assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid, notNullValue());
assertThat(geoCentroid.getName(), equalTo(aggName)); assertThat(geoCentroid.getName(), equalTo(aggName));
GeoPoint centroid = geoCentroid.centroid(); assertThat(geoCentroid.centroid(), equalTo(null));
assertThat(centroid, equalTo(null));
assertEquals(0, geoCentroid.count()); assertEquals(0, geoCentroid.count());
} }
@ -73,9 +71,7 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
GeoCentroid geoCentroid = response.getAggregations().get(aggName); GeoCentroid geoCentroid = response.getAggregations().get(aggName);
assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid, notNullValue());
assertThat(geoCentroid.getName(), equalTo(aggName)); assertThat(geoCentroid.getName(), equalTo(aggName));
GeoPoint centroid = geoCentroid.centroid(); assertSameCentroid(geoCentroid.centroid(), singleCentroid);
assertThat(centroid.lat(), closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE));
assertThat(centroid.lon(), closeTo(singleCentroid.lon(), GEOHASH_TOLERANCE));
assertEquals(numDocs, geoCentroid.count()); assertEquals(numDocs, geoCentroid.count());
} }
@ -89,13 +85,11 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
GeoCentroid geoCentroid = response.getAggregations().get(aggName); GeoCentroid geoCentroid = response.getAggregations().get(aggName);
assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid, notNullValue());
assertThat(geoCentroid.getName(), equalTo(aggName)); assertThat(geoCentroid.getName(), equalTo(aggName));
GeoPoint centroid = geoCentroid.centroid(); assertSameCentroid(geoCentroid.centroid(), singleCentroid);
assertThat(centroid.lat(), closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE));
assertThat(centroid.lon(), closeTo(singleCentroid.lon(), GEOHASH_TOLERANCE));
assertEquals(numDocs, geoCentroid.count()); assertEquals(numDocs, geoCentroid.count());
} }
public void testSingleValueFieldGetProperty() throws Exception { public void testSingleValueFieldGetProperty() {
SearchResponse response = client().prepareSearch(IDX_NAME) SearchResponse response = client().prepareSearch(IDX_NAME)
.setQuery(matchAllQuery()) .setQuery(matchAllQuery())
.addAggregation(global("global").subAggregation(geoCentroid(aggName).field(SINGLE_VALUED_FIELD_NAME))) .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, notNullValue());
assertThat(geoCentroid.getName(), equalTo(aggName)); assertThat(geoCentroid.getName(), equalTo(aggName));
assertThat((GeoCentroid) ((InternalAggregation) global).getProperty(aggName), sameInstance(geoCentroid)); assertThat((GeoCentroid) ((InternalAggregation) global).getProperty(aggName), sameInstance(geoCentroid));
GeoPoint centroid = geoCentroid.centroid(); assertSameCentroid(geoCentroid.centroid(), singleCentroid);
assertThat(centroid.lat(), closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE));
assertThat(centroid.lon(), closeTo(singleCentroid.lon(), GEOHASH_TOLERANCE));
assertThat( assertThat(
((GeoPoint) ((InternalAggregation) global).getProperty(aggName + ".value")).lat(), ((GeoPoint) ((InternalAggregation) global).getProperty(aggName + ".value")).lat(),
closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE) closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE)
@ -139,13 +131,11 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
GeoCentroid geoCentroid = searchResponse.getAggregations().get(aggName); GeoCentroid geoCentroid = searchResponse.getAggregations().get(aggName);
assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid, notNullValue());
assertThat(geoCentroid.getName(), equalTo(aggName)); assertThat(geoCentroid.getName(), equalTo(aggName));
GeoPoint centroid = geoCentroid.centroid(); assertSameCentroid(geoCentroid.centroid(), multiCentroid);
assertThat(centroid.lat(), closeTo(multiCentroid.lat(), GEOHASH_TOLERANCE));
assertThat(centroid.lon(), closeTo(multiCentroid.lon(), GEOHASH_TOLERANCE));
assertEquals(2 * numDocs, geoCentroid.count()); assertEquals(2 * numDocs, geoCentroid.count());
} }
public void testSingleValueFieldAsSubAggToGeohashGrid() throws Exception { public void testSingleValueFieldAsSubAggToGeohashGrid() {
SearchResponse response = client().prepareSearch(HIGH_CARD_IDX_NAME) SearchResponse response = client().prepareSearch(HIGH_CARD_IDX_NAME)
.addAggregation( .addAggregation(
geohashGrid("geoGrid").field(SINGLE_VALUED_FIELD_NAME).subAggregation(geoCentroid(aggName).field(SINGLE_VALUED_FIELD_NAME)) 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(); String geohash = cell.getKeyAsString();
GeoPoint expectedCentroid = expectedCentroidsForGeoHash.get(geohash); GeoPoint expectedCentroid = expectedCentroidsForGeoHash.get(geohash);
GeoCentroid centroidAgg = cell.getAggregations().get(aggName); GeoCentroid centroidAgg = cell.getAggregations().get(aggName);
assertThat( assertSameCentroid(centroidAgg.centroid(), expectedCentroid);
"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)
);
} }
} }
} }

View file

@ -11,7 +11,7 @@ package org.elasticsearch.search.geo;
import org.apache.lucene.geo.GeoEncodingUtils; import org.apache.lucene.geo.GeoEncodingUtils;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.document.DocumentField; 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.GeoPoint;
import org.elasticsearch.geo.GeometryTestUtils; import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.index.fielddata.ScriptDocValues;
@ -66,52 +66,52 @@ public class GeoPointScriptDocValuesIT extends ESSingleNodeTestCase {
private double scriptHeight(Map<String, Object> vars) { private double scriptHeight(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc); ScriptDocValues.Geometry geometry = assertGeometry(doc);
if (geometry.size() == 0) { if (geometry.size() == 0) {
return Double.NaN; return Double.NaN;
} else { } else {
GeoBoundingBox boundingBox = geometry.getBoundingBox(); BoundingBox<GeoPoint> boundingBox = geometry.getBoundingBox();
return boundingBox.topLeft().lat() - boundingBox.bottomRight().lat(); return boundingBox.topLeft().lat() - boundingBox.bottomRight().lat();
} }
} }
private double scriptWidth(Map<String, Object> vars) { private double scriptWidth(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc); ScriptDocValues.Geometry geometry = assertGeometry(doc);
if (geometry.size() == 0) { if (geometry.size() == 0) {
return Double.NaN; return Double.NaN;
} else { } else {
GeoBoundingBox boundingBox = geometry.getBoundingBox(); BoundingBox<GeoPoint> boundingBox = geometry.getBoundingBox();
return boundingBox.bottomRight().lon() - boundingBox.topLeft().lon(); return boundingBox.bottomRight().lon() - boundingBox.topLeft().lon();
} }
} }
private double scriptLat(Map<String, Object> vars) { private double scriptLat(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); 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(); return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lat();
} }
private double scriptLon(Map<String, Object> vars) { private double scriptLon(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); 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(); return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lon();
} }
private double scriptLabelLat(Map<String, Object> vars) { private double scriptLabelLat(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); 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(); return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lat();
} }
private double scriptLabelLon(Map<String, Object> vars) { private double scriptLabelLon(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); 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(); return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lon();
} }
private ScriptDocValues.Geometry<?> assertGeometry(Map<?, ?> doc) { private ScriptDocValues.Geometry assertGeometry(Map<?, ?> doc) {
ScriptDocValues.Geometry<?> geometry = (ScriptDocValues.Geometry<?>) doc.get("location"); ScriptDocValues.Geometry geometry = (ScriptDocValues.Geometry) doc.get("location");
if (geometry.size() == 0) { if (geometry.size() == 0) {
assertThat(geometry.getBoundingBox(), Matchers.nullValue()); assertThat(geometry.getBoundingBox(), Matchers.nullValue());
assertThat(geometry.getCentroid(), 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.ElasticsearchParseException;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; 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.ParseField;
import org.elasticsearch.xcontent.ToXContentFragment;
import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser; import org.elasticsearch.xcontent.XContentParser;
import java.io.IOException; 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 * A class representing a Geo-Bounding-Box for use by Geo queries and aggregations
* that deal with extents/rectangles representing rectangular areas of interest. * that deal with extents/rectangles representing rectangular areas of interest.
*/ */
public class GeoBoundingBox implements ToXContentFragment, Writeable { public class GeoBoundingBox extends BoundingBox<GeoPoint> {
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 LAT_FIELD = new ParseField("lat"); public static final ParseField LAT_FIELD = new ParseField("lat");
public static final ParseField LON_FIELD = new ParseField("lon"); 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) { public GeoBoundingBox(GeoPoint topLeft, GeoPoint bottomRight) {
this.topLeft = topLeft; super(topLeft, bottomRight);
this.bottomRight = bottomRight;
} }
public GeoBoundingBox(StreamInput input) throws IOException { public GeoBoundingBox(StreamInput input) throws IOException {
this.topLeft = input.readGeoPoint(); super(input.readGeoPoint(), 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());
} }
@Override
public GeoPoint topLeft() { public GeoPoint topLeft() {
return topLeft; return topLeft;
} }
@Override
public GeoPoint bottomRight() { public GeoPoint bottomRight() {
return 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 @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContentFragment(XContentBuilder builder) throws IOException {
builder.startObject(); builder.startObject(TOP_LEFT_FIELD.getPreferredName());
toXContentFragment(builder, true); 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(); builder.endObject();
return builder; return builder;
} }
public XContentBuilder toXContentFragment(XContentBuilder builder, boolean buildLatLonFields) throws IOException { /**
if (buildLatLonFields) { * There exists a special case where we use an array format for building the XContent for the bounds.
builder.startObject(TOP_LEFT_FIELD.getPreferredName()); * Specifically the GeoBoundingBoxQueryBuilder makes use of this. All other cases build a keyed map.
builder.field(LAT_FIELD.getPreferredName(), topLeft.lat()); */
builder.field(LON_FIELD.getPreferredName(), topLeft.lon()); public XContentBuilder toXContentFragmentWithArray(XContentBuilder builder) throws IOException {
builder.endObject(); builder.array(TOP_LEFT_FIELD.getPreferredName(), topLeft.getX(), topLeft.getY());
} else { builder.array(BOTTOM_RIGHT_FIELD.getPreferredName(), bottomRight.getX(), bottomRight.getY());
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());
}
return builder; return builder;
} }
@ -141,106 +91,36 @@ public class GeoBoundingBox implements ToXContentFragment, Writeable {
out.writeGeoPoint(bottomRight); out.writeGeoPoint(bottomRight);
} }
@Override protected static class GeoBoundsParser extends BoundsParser<GeoBoundingBox> {
public boolean equals(Object o) { GeoBoundsParser(XContentParser parser) {
if (this == o) return true; super(parser);
if (o == null || getClass() != o.getClass()) return false;
GeoBoundingBox that = (GeoBoundingBox) o;
return topLeft.equals(that.topLeft) && bottomRight.equals(that.bottomRight);
} }
@Override @Override
public int hashCode() { protected GeoBoundingBox createWithEnvelope() {
return Objects.hash(topLeft, bottomRight); GeoPoint topLeft = new GeoPoint(envelope.getMaxLat(), envelope.getMinLon());
GeoPoint bottomRight = new GeoPoint(envelope.getMinLat(), envelope.getMaxLon());
return new GeoBoundingBox(topLeft, bottomRight);
} }
@Override @Override
public String toString() { protected GeoBoundingBox createWithBounds() {
return "BBOX (" + topLeft.lon() + ", " + bottomRight.lon() + ", " + topLeft.lat() + ", " + bottomRight.lat() + ")"; 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 * Parses the bounding box and returns bottom, top, left, right coordinates
*/ */
public static GeoBoundingBox parseBoundingBox(XContentParser parser) throws IOException, ElasticsearchParseException { public static GeoBoundingBox parseBoundingBox(XContentParser parser) throws IOException, ElasticsearchParseException {
XContentParser.Token token = parser.currentToken(); GeoBoundsParser bounds = new GeoBoundsParser(parser);
if (token != XContentParser.Token.START_OBJECT) { return bounds.parseBoundingBox();
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);
}
}

View file

@ -30,7 +30,7 @@ import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
public class GeoPoint implements ToXContentFragment { public class GeoPoint implements SpatialPoint, ToXContentFragment {
protected double lat; protected double lat;
protected double lon; protected double lon;
@ -52,8 +52,8 @@ public class GeoPoint implements ToXContentFragment {
this.lon = lon; this.lon = lon;
} }
public GeoPoint(GeoPoint template) { public GeoPoint(SpatialPoint template) {
this(template.getLat(), template.getLon()); this(template.getY(), template.getX());
} }
public GeoPoint reset(double lat, double lon) { public GeoPoint reset(double lat, double lon) {
@ -189,6 +189,16 @@ public class GeoPoint implements ToXContentFragment {
return this.lon; return this.lon;
} }
@Override
public double getX() {
return this.lon;
}
@Override
public double getY() {
return this.lat;
}
public String geohash() { public String geohash() {
return Geohash.stringEncode(lon, lat); return Geohash.stringEncode(lon, lat);
} }

View file

@ -537,7 +537,7 @@ public class GeoUtils {
@Override @Override
public double doubleValue() throws IOException { public double doubleValue() throws IOException {
final GeoPoint from = fromPoints[0]; 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); 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.SortedNumericDocValues;
import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.geo.SpatialPoint;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -86,7 +87,7 @@ public enum FieldData {
* Returns a {@link DocValueBits} representing all documents from <code>pointValues</code> that have * Returns a {@link DocValueBits} representing all documents from <code>pointValues</code> that have
* a value. * a value.
*/ */
public static DocValueBits docsWithValue(final MultiGeoPointValues pointValues) { public static DocValueBits docsWithValue(final MultiPointValues<? extends SpatialPoint> pointValues) {
return new DocValueBits() { return new DocValueBits() {
@Override @Override
public boolean advanceExact(int doc) throws IOException { public boolean advanceExact(int doc) throws IOException {
@ -217,7 +218,7 @@ public enum FieldData {
* if the wrapped {@link SortedNumericDocValues} is a singleton. * if the wrapped {@link SortedNumericDocValues} is a singleton.
*/ */
public static GeoPointValues unwrapSingleton(MultiGeoPointValues values) { 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.index.SortedNumericDocValues;
import org.apache.lucene.search.SortField; import org.apache.lucene.search.SortField;
import org.elasticsearch.common.util.BigArrays; 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.indices.breaker.CircuitBreakerService;
import org.elasticsearch.script.GeoPointFieldScript; import org.elasticsearch.script.GeoPointFieldScript;
import org.elasticsearch.script.field.ToScriptFieldFactory; 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.BucketedSort;
import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.sort.SortOrder;
public class GeoPointScriptFieldData implements IndexGeoPointFieldData { public final class GeoPointScriptFieldData implements IndexGeoPointFieldData {
public static class Builder implements IndexFieldData.Builder { public static class Builder implements IndexFieldData.Builder {
private final String name; private final String name;
private final GeoPointFieldScript.LeafFactory leafFactory; private final GeoPointFieldScript.LeafFactory leafFactory;
@ -89,9 +89,9 @@ public class GeoPointScriptFieldData implements IndexGeoPointFieldData {
} }
@Override @Override
public LeafGeoPointFieldData load(LeafReaderContext context) { public LeafPointFieldData<MultiGeoPointValues> load(LeafReaderContext context) {
GeoPointFieldScript script = leafFactory.newInstance(context); GeoPointFieldScript script = leafFactory.newInstance(context);
return new AbstractLeafGeoPointFieldData(toScriptFieldFactory) { return new LeafGeoPointFieldData(toScriptFieldFactory) {
@Override @Override
public SortedNumericDocValues getSortedNumericDocValues() { public SortedNumericDocValues getSortedNumericDocValues() {
return new GeoPointScriptDocValues(script); return new GeoPointScriptDocValues(script);
@ -110,7 +110,7 @@ public class GeoPointScriptFieldData implements IndexGeoPointFieldData {
} }
@Override @Override
public LeafGeoPointFieldData loadDirect(LeafReaderContext context) { public LeafPointFieldData<MultiGeoPointValues> loadDirect(LeafReaderContext context) {
return load(context); return load(context);
} }
} }

View file

@ -16,28 +16,20 @@ import java.io.IOException;
/** /**
* Per-document geo-point values. * Per-document geo-point values.
*/ */
public final class GeoPointValues { public final class GeoPointValues extends PointValues<GeoPoint> {
private final GeoPoint point = new GeoPoint(); private final GeoPoint point = new GeoPoint();
private final NumericDocValues values;
GeoPointValues(NumericDocValues values) { GeoPointValues(NumericDocValues values) {
this.values = values; super(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);
} }
/** /**
* Get the {@link GeoPoint} associated with the current document. * Get the {@link GeoPoint} associated with the current document.
* The returned {@link GeoPoint} might be reused across calls. * 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()); return point.resetFromEncoded(values.longValue());
} }
} }

View file

@ -11,4 +11,4 @@ package org.elasticsearch.index.fielddata;
/** /**
* Specialization of {@link IndexFieldData} for geo points. * 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; package org.elasticsearch.index.fielddata;
import org.apache.lucene.index.SortedNumericDocValues; 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() { public abstract T getPointValues();
return new MultiGeoPointValues(getSortedNumericDocValues());
}
/** /**
* 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 * A point is encoded as a long that can be decoded by using
* {@link org.elasticsearch.common.geo.GeoPoint#resetFromEncoded(long)} * {@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. * A stateful lightweight per document set of {@link GeoPoint} values.
* To iterate over values in a document use the following pattern: * To iterate over values in a document use the following pattern:
* <pre> * <pre>
* GeoPointValues values = ..; * MultiGeoPointValues values = ..;
* values.setDocId(docId); * values.advanceExact(docId);
* final int numValues = values.count(); * final int numValues = values.docValueCount();
* for (int i = 0; i &lt; numValues; i++) { * for (int i = 0; i &lt; numValues; i++) {
* GeoPoint value = values.valueAt(i); * GeoPoint value = values.nextValue();
* // process value * // process value
* } * }
* </pre> * </pre>
* The set of values associated with a document might contain duplicates and * The set of values associated with a document might contain duplicates and
* comes in a non-specified order. * comes in a non-specified order.
*/ */
public final class MultiGeoPointValues { public class MultiGeoPointValues extends MultiPointValues<GeoPoint> {
private final GeoPoint point = new GeoPoint(); private final GeoPoint point = new GeoPoint();
private final SortedNumericDocValues numericValues;
/**
* Creates a new {@link MultiGeoPointValues} instance
*/
public MultiGeoPointValues(SortedNumericDocValues numericValues) { public MultiGeoPointValues(SortedNumericDocValues numericValues) {
this.numericValues = numericValues; super(numericValues);
} }
/** @Override
* 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 GeoPoint nextValue() throws IOException { public GeoPoint nextValue() throws IOException {
return point.resetFromEncoded(numericValues.nextValue()); 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); final NumericDocValues singleton = DocValues.unwrapSingleton(numericValues);
return singleton != null ? new GeoPointValues(singleton) : null; 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.ArrayUtil;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder; import org.apache.lucene.util.BytesRefBuilder;
import org.elasticsearch.common.geo.BoundingBox;
import org.elasticsearch.common.geo.GeoBoundingBox; import org.elasticsearch.common.geo.GeoBoundingBox;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.geo.SpatialPoint;
import org.elasticsearch.geometry.utils.Geohash; import org.elasticsearch.geometry.utils.Geohash;
import org.elasticsearch.script.field.DocValuesScriptFieldFactory; 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); super(supplier);
} }
@ -230,35 +232,52 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
public abstract int getDimensionalType(); public abstract int getDimensionalType();
/** Returns the bounding box of this geometry */ /** Returns the bounding box of this geometry */
public abstract GeoBoundingBox getBoundingBox(); public abstract BoundingBox<T> getBoundingBox();
/** Returns the suggested label position */ /** Returns the suggested label position */
public abstract GeoPoint getLabelPosition(); public abstract T getLabelPosition();
/** Returns the centroid of this geometry */ /** 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) */ /** 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) */ /** 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); super(supplier);
geometrySupplier = supplier; geometrySupplier = supplier;
} }
@ -366,7 +385,7 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
@Override @Override
public GeoBoundingBox getBoundingBox() { public GeoBoundingBox getBoundingBox() {
return size() == 0 ? null : geometrySupplier.getInternalBoundingBox(); return size() == 0 ? null : (GeoBoundingBox) geometrySupplier.getInternalBoundingBox();
} }
@Override @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.Collection;
import java.util.Collections; import java.util.Collections;
final class LatLonPointDVLeafFieldData extends AbstractLeafGeoPointFieldData { final class LatLonPointDVLeafFieldData extends LeafGeoPointFieldData {
private final LeafReader reader; private final LeafReader reader;
private final String fieldName; 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; package org.elasticsearch.index.fielddata.plain;
import org.elasticsearch.index.fielddata.FieldData; 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.MultiGeoPointValues;
import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
import org.elasticsearch.script.field.DocValuesScriptFieldFactory; import org.elasticsearch.script.field.DocValuesScriptFieldFactory;
import org.elasticsearch.script.field.ToScriptFieldFactory; import org.elasticsearch.script.field.ToScriptFieldFactory;
public abstract class AbstractLeafGeoPointFieldData extends LeafGeoPointFieldData { public abstract class LeafGeoPointFieldData extends LeafPointFieldData<MultiGeoPointValues> {
protected final ToScriptFieldFactory<MultiGeoPointValues> toScriptFieldFactory; protected final ToScriptFieldFactory<MultiGeoPointValues> toScriptFieldFactory;
public AbstractLeafGeoPointFieldData(ToScriptFieldFactory<MultiGeoPointValues> toScriptFieldFactory) { public LeafGeoPointFieldData(ToScriptFieldFactory<MultiGeoPointValues> toScriptFieldFactory) {
this.toScriptFieldFactory = toScriptFieldFactory; this.toScriptFieldFactory = toScriptFieldFactory;
} }
@Override
public final MultiGeoPointValues getPointValues() {
return new MultiGeoPointValues(getSortedNumericDocValues());
}
@Override @Override
public final SortedBinaryDocValues getBytesValues() { public final SortedBinaryDocValues getBytesValues() {
return FieldData.toString(getGeoPointValues()); return FieldData.toString(getPointValues());
} }
@Override @Override
public DocValuesScriptFieldFactory getScriptFieldFactory(String name) { 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.FieldDataContext;
import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.SourceValueFetcherMultiGeoPointIndexFieldData; 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.index.query.SearchExecutionContext;
import org.elasticsearch.script.GeoPointFieldScript; import org.elasticsearch.script.GeoPointFieldScript;
import org.elasticsearch.script.Script; import org.elasticsearch.script.Script;
@ -378,7 +378,7 @@ public class GeoPointFieldMapper extends AbstractPointGeometryFieldMapper<GeoPoi
} }
if ((operation == FielddataOperation.SEARCH || operation == FielddataOperation.SCRIPT) && hasDocValues()) { 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) { if (operation == FielddataOperation.SCRIPT) {

View file

@ -307,7 +307,7 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
builder.startObject(NAME); builder.startObject(NAME);
builder.startObject(fieldName); builder.startObject(fieldName);
geoBoundingBox.toXContentFragment(builder, false); geoBoundingBox.toXContentFragmentWithArray(builder);
builder.endObject(); builder.endObject();
builder.field(VALIDATION_METHOD_FIELD.getPreferredName(), validationMethod); builder.field(VALIDATION_METHOD_FIELD.getPreferredName(), validationMethod);
builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped); builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);

View file

@ -382,7 +382,7 @@ public abstract class DecayFunctionBuilder<DFB extends DecayFunctionBuilder<DFB>
@Override @Override
protected NumericDoubleValues distance(LeafReaderContext context) { 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() { return FieldData.replaceMissing(mode.select(new SortingNumericDoubleValues() {
@Override @Override
public boolean advanceExact(int docId) throws IOException { 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 { protected String getDistanceString(LeafReaderContext ctx, int docId) throws IOException {
StringBuilder values = new StringBuilder(mode.name()); StringBuilder values = new StringBuilder(mode.name());
values.append(" of: ["); values.append(" of: [");
final MultiGeoPointValues geoPointValues = fieldData.load(ctx).getGeoPointValues(); final MultiGeoPointValues geoPointValues = fieldData.load(ctx).getPointValues();
if (geoPointValues.advanceExact(docId)) { if (geoPointValues.advanceExact(docId)) {
final int num = geoPointValues.docValueCount(); final int num = geoPointValues.docValueCount();
for (int i = 0; i < num; i++) { for (int i = 0; i < num; i++) {

View file

@ -8,105 +8,62 @@
package org.elasticsearch.script.field; package org.elasticsearch.script.field;
import org.apache.lucene.util.ArrayUtil;
import org.elasticsearch.common.geo.GeoBoundingBox; import org.elasticsearch.common.geo.GeoBoundingBox;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.MultiGeoPointValues;
import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.index.fielddata.ScriptDocValues;
import java.io.IOException; public class GeoPointDocValuesField extends PointDocValuesField<GeoPoint> {
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;
// maintain bwc by making centroid and bounding box available to ScriptDocValues.GeoPoints // maintain bwc by making centroid and bounding box available to ScriptDocValues.GeoPoints
private ScriptDocValues.GeoPoints geoPoints = null; 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) { public GeoPointDocValuesField(MultiGeoPointValues input, String name) {
this.input = input; super(input, name, GeoPoint::new, new GeoBoundingBox(new GeoPoint(), new GeoPoint()), new GeoPoint[0]);
this.name = name;
} }
@Override @Override
public void setNextDocId(int docId) throws IOException { protected void resetPointAt(int i, GeoPoint point) {
if (input.advanceExact(docId)) {
resize(input.docValueCount());
if (count == 1) {
setSingleValue();
} else {
setMultiValue();
}
} else {
resize(0);
}
}
private void resize(int newSize) {
count = newSize;
if (newSize > values.length) {
int oldLength = values.length;
values = ArrayUtil.grow(values, count);
for (int i = oldLength; i < values.length; ++i) {
values[i] = new GeoPoint();
}
}
}
private void setSingleValue() throws IOException {
GeoPoint point = input.nextValue();
values[0].reset(point.lat(), point.lon());
centroid.reset(point.lat(), point.lon());
boundingBox.topLeft().reset(point.lat(), point.lon());
boundingBox.bottomRight().reset(point.lat(), point.lon());
labelIndex = 0;
}
private void setMultiValue() throws IOException {
double centroidLat = 0;
double centroidLon = 0;
labelIndex = 0;
double maxLon = Double.NEGATIVE_INFINITY;
double minLon = Double.POSITIVE_INFINITY;
double maxLat = Double.NEGATIVE_INFINITY;
double minLat = Double.POSITIVE_INFINITY;
for (int i = 0; i < count; i++) {
GeoPoint point = input.nextValue();
values[i].reset(point.lat(), point.lon()); values[i].reset(point.lat(), point.lon());
centroidLat += point.getLat();
centroidLon += point.getLon();
maxLon = Math.max(maxLon, values[i].getLon());
minLon = Math.min(minLon, values[i].getLon());
maxLat = Math.max(maxLat, values[i].getLat());
minLat = Math.min(minLat, values[i].getLat());
labelIndex = closestPoint(labelIndex, i, (minLat + maxLat) / 2, (minLon + maxLon) / 2);
}
centroid.reset(centroidLat / count, centroidLon / count);
boundingBox.topLeft().reset(maxLat, minLon);
boundingBox.bottomRight().reset(minLat, maxLon);
} }
private int closestPoint(int a, int b, double lat, double lon) { @Override
if (a == b) { protected void resetCentroidAndBounds(GeoPoint point, GeoPoint topLeft, GeoPoint bottomRight) {
return a; centroid.reset(point.lat() / count, point.lon() / count);
boundingBox.topLeft().reset(topLeft.lat(), topLeft.lon());
boundingBox.bottomRight().reset(bottomRight.lat(), bottomRight.lon());
} }
double distA = GeoUtils.planeDistance(lat, lon, values[a].lat(), values[a].lon());
double distB = GeoUtils.planeDistance(lat, lon, values[b].lat(), values[b].lon()); @Override
return distA < distB ? a : b; protected double getXFrom(GeoPoint point) {
return point.lon();
}
@Override
protected double getYFrom(GeoPoint point) {
return point.lat();
}
@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 @Override
@ -117,73 +74,4 @@ public class GeoPointDocValuesField extends AbstractScriptFieldFactory<GeoPoint>
return geoPoints; 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); builder.field("precision", precision);
if (geoBoundingBox.isUnbounded() == false) { if (geoBoundingBox.isUnbounded() == false) {
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName()); builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
geoBoundingBox.toXContentFragment(builder, true); geoBoundingBox.toXContentFragment(builder);
builder.endObject(); builder.endObject();
} }
} }

View file

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

View file

@ -231,7 +231,7 @@ public abstract class GeoGridAggregationBuilder extends ValuesSourceAggregationB
} }
if (geoBoundingBox.isUnbounded() == false) { if (geoBoundingBox.isUnbounded() == false) {
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName()); builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
geoBoundingBox.toXContentFragment(builder, true); geoBoundingBox.toXContentFragment(builder);
builder.endObject(); builder.endObject();
} }
return builder; 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; package org.elasticsearch.search.aggregations.metrics;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.search.aggregations.Aggregation;
/** /**
* Interface for {@link GeoCentroidAggregator} * Interface for {@link GeoCentroidAggregator}
*/ */
public interface GeoCentroid extends Aggregation { public interface GeoCentroid extends CentroidAggregation {}
GeoPoint centroid();
long count();
}

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(); GeoBoundingBox bbox = resolveGeoBoundingBox();
if (bbox != null) { if (bbox != null) {
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName()); builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
bbox.toXContentFragment(builder, true); bbox.toXContentFragment(builder);
builder.endObject(); builder.endObject();
} }
return builder; return builder;

View file

@ -11,80 +11,70 @@ package org.elasticsearch.search.aggregations.metrics;
import org.apache.lucene.geo.GeoEncodingUtils; import org.apache.lucene.geo.GeoEncodingUtils;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.common.geo.GeoPoint; 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.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.search.aggregations.AggregationReduceContext;
import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.InternalAggregation;
import org.elasticsearch.search.aggregations.support.SamplingContext; import org.elasticsearch.search.aggregations.support.SamplingContext;
import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.XContentBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
/** /**
* Serialization and merge logic for {@link GeoCentroidAggregator}. * Serialization and merge logic for {@link GeoCentroidAggregator}.
*/ */
public class InternalGeoCentroid extends InternalAggregation implements GeoCentroid { public class InternalGeoCentroid extends InternalCentroid implements GeoCentroid {
private final GeoPoint centroid;
private final long count;
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( return (Integer.toUnsignedLong(GeoEncodingUtils.encodeLatitude(lat)) << 32) | Integer.toUnsignedLong(
GeoEncodingUtils.encodeLongitude(lon) GeoEncodingUtils.encodeLongitude(lon)
); );
} }
public static double decodeLatitude(long encodedLatLon) { private static double decodeLatitude(long encodedLatLon) {
return GeoEncodingUtils.decodeLatitude((int) (encodedLatLon >>> 32)); return GeoEncodingUtils.decodeLatitude((int) (encodedLatLon >>> 32));
} }
public static double decodeLongitude(long encodedLatLon) { private static double decodeLongitude(long encodedLatLon) {
return GeoEncodingUtils.decodeLongitude((int) (encodedLatLon & 0xFFFFFFFFL)); return GeoEncodingUtils.decodeLongitude((int) (encodedLatLon & 0xFFFFFFFFL));
} }
public InternalGeoCentroid(String name, GeoPoint centroid, long count, Map<String, Object> metadata) { public InternalGeoCentroid(String name, SpatialPoint centroid, long count, Map<String, Object> metadata) {
super(name, metadata); super(
assert (centroid == null) == (count == 0); name,
this.centroid = centroid; centroid,
assert count >= 0; count,
this.count = count; metadata,
new FieldExtractor("lat", SpatialPoint::getY),
new FieldExtractor("lon", SpatialPoint::getX)
);
} }
/** /**
* Read from a stream. * Read from a stream.
*/ */
public InternalGeoCentroid(StreamInput in) throws IOException { public InternalGeoCentroid(StreamInput in) throws IOException {
super(in); super(in, new FieldExtractor("lat", SpatialPoint::getY), new FieldExtractor("lon", SpatialPoint::getX));
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));
} }
@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 { } else {
centroid = null; final long hash = in.readLong();
return new GeoPoint(decodeLatitude(hash), decodeLongitude(hash));
} }
} }
@Override @Override
protected void doWriteTo(StreamOutput out) throws IOException { protected void centroidToStream(StreamOutput out) throws IOException {
out.writeVLong(count);
if (centroid != null) {
out.writeBoolean(true);
if (out.getVersion().onOrAfter(Version.V_7_2_0)) { if (out.getVersion().onOrAfter(Version.V_7_2_0)) {
out.writeDouble(centroid.lat()); out.writeDouble(centroid.getY());
out.writeDouble(centroid.lon()); out.writeDouble(centroid.getX());
} else { } else {
out.writeLong(encodeLatLon(centroid.lat(), centroid.lon())); out.writeLong(encodeLatLon(centroid.getY(), centroid.getX()));
}
} else {
out.writeBoolean(false);
} }
} }
@ -94,35 +84,23 @@ public class InternalGeoCentroid extends InternalAggregation implements GeoCentr
} }
@Override @Override
public GeoPoint centroid() { protected double extractDouble(String name) {
return centroid; return switch (name) {
case "lat" -> centroid.getY();
case "lon" -> centroid.getX();
default -> throw new IllegalArgumentException("Found unknown path element [" + name + "] in [" + getName() + "]");
};
} }
@Override @Override
public long count() { protected InternalGeoCentroid copyWith(SpatialPoint result, long count) {
return count; return new InternalGeoCentroid(name, result, count, getMetadata());
} }
@Override @Override
public InternalGeoCentroid reduce(List<InternalAggregation> aggregations, AggregationReduceContext reduceContext) { protected InternalGeoCentroid copyWith(double firstSum, double secondSum, long totalCount) {
double lonSum = Double.NaN; final GeoPoint result = (Double.isNaN(firstSum)) ? null : new GeoPoint(firstSum / totalCount, secondSum / totalCount);
double latSum = Double.NaN; return copyWith(result, totalCount);
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());
} }
@Override @Override
@ -130,66 +108,8 @@ public class InternalGeoCentroid extends InternalAggregation implements GeoCentr
return new InternalGeoCentroid(name, centroid, samplingContext.scaleUp(count), getMetadata()); 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 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_LAT = new ParseField("lat");
static final ParseField CENTROID_LON = new ParseField("lon"); 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 { public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
if (geoBoundingBox != null) { if (geoBoundingBox != null) {
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName()); builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
geoBoundingBox.toXContentFragment(builder, true); geoBoundingBox.toXContentFragment(builder);
builder.endObject(); builder.endObject();
} }
return builder; return builder;

View file

@ -42,14 +42,14 @@ public class ParsedGeoCentroid extends ParsedAggregation implements GeoCentroid
@Override @Override
public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
if (centroid != null) { 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_LAT.getPreferredName(), centroid.lat());
builder.field(Fields.CENTROID_LON.getPreferredName(), centroid.lon()); builder.field(Fields.CENTROID_LON.getPreferredName(), centroid.lon());
} }
builder.endObject(); builder.endObject();
} }
builder.field(Fields.COUNT.getPreferredName(), count); builder.field(InternalCentroid.Fields.COUNT.getPreferredName(), count);
return builder; return builder;
} }
@ -67,8 +67,8 @@ public class ParsedGeoCentroid extends ParsedAggregation implements GeoCentroid
static { static {
declareAggregationFields(PARSER); declareAggregationFields(PARSER);
PARSER.declareObject((agg, centroid) -> agg.centroid = centroid, GEO_POINT_PARSER, Fields.CENTROID); PARSER.declareObject((agg, centroid) -> agg.centroid = centroid, GEO_POINT_PARSER, InternalCentroid.Fields.CENTROID);
PARSER.declareLong((agg, count) -> agg.count = count, Fields.COUNT); PARSER.declareLong((agg, count) -> agg.count = count, InternalCentroid.Fields.COUNT);
GEO_POINT_PARSER.declareDouble(GeoPoint::resetLat, Fields.CENTROID_LAT); GEO_POINT_PARSER.declareDouble(GeoPoint::resetLat, Fields.CENTROID_LAT);
GEO_POINT_PARSER.declareDouble(GeoPoint::resetLon, Fields.CENTROID_LON); 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.bucket.terms.UnmappedTerms;
import org.elasticsearch.search.aggregations.metrics.InternalAvg; import org.elasticsearch.search.aggregations.metrics.InternalAvg;
import org.elasticsearch.search.aggregations.metrics.InternalCardinality; 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.InternalExtendedStats;
import org.elasticsearch.search.aggregations.metrics.InternalGeoBounds; 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.InternalHDRPercentileRanks;
import org.elasticsearch.search.aggregations.metrics.InternalHDRPercentiles; import org.elasticsearch.search.aggregations.metrics.InternalHDRPercentiles;
import org.elasticsearch.search.aggregations.metrics.InternalMedianAbsoluteDeviation; import org.elasticsearch.search.aggregations.metrics.InternalMedianAbsoluteDeviation;
@ -167,7 +167,7 @@ public class AggregationInspectionHelper {
return (agg.topLeft() == null && agg.bottomRight() == null) == false; 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; return agg.centroid() != null && agg.count() > 0;
} }

View file

@ -162,15 +162,14 @@ public enum CoreValuesSourceType implements ValuesSourceType {
@Override @Override
public ValuesSource getField(FieldContext fieldContext, AggregationScript.LeafFactory script, AggregationContext context) { public ValuesSource getField(FieldContext fieldContext, AggregationScript.LeafFactory script, AggregationContext context) {
if ((fieldContext.indexFieldData() instanceof IndexGeoPointFieldData) == false) { if (fieldContext.indexFieldData()instanceof IndexGeoPointFieldData pointFieldData) {
return new ValuesSource.GeoPoint.Fielddata(pointFieldData);
}
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Expected geo_point type on field [" + fieldContext.field() + "], but got [" + fieldContext.fieldType().typeName() + "]" "Expected geo_point type on field [" + fieldContext.field() + "], but got [" + fieldContext.fieldType().typeName() + "]"
); );
} }
return new ValuesSource.GeoPoint.Fielddata((IndexGeoPointFieldData) fieldContext.indexFieldData());
}
@Override @Override
public ValuesSource replaceMissing( public ValuesSource replaceMissing(
ValuesSource valuesSource, ValuesSource valuesSource,

View file

@ -36,7 +36,7 @@ import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.MultiGeoPointValues;
import org.elasticsearch.index.fielddata.NumericDoubleValues; import org.elasticsearch.index.fielddata.NumericDoubleValues;
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; 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.mapper.MappedFieldType;
import org.elasticsearch.index.query.GeoValidationMethod; import org.elasticsearch.index.query.GeoValidationMethod;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
@ -642,7 +642,7 @@ public class GeoDistanceSortBuilder extends SortBuilder<GeoDistanceSortBuilder>
} }
private NumericDoubleValues getNumericDoubleValues(LeafReaderContext context) throws IOException { 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); final SortedNumericDoubleValues distanceValues = GeoUtils.distanceValues(geoDistance, unit, geoPointValues, localPoints);
if (nested == null) { if (nested == null) {
return FieldData.replaceMissing(sortMode.select(distanceValues), Double.POSITIVE_INFINITY); 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.document.StringField;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.elasticsearch.index.fielddata.plain.AbstractLeafGeoPointFieldData; import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData;
import java.util.List; import java.util.List;
@ -149,7 +149,7 @@ public class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
LeafFieldData fieldData = indexFieldData.load(readerContext); LeafFieldData fieldData = indexFieldData.load(readerContext);
assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed())); assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed()));
MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues(); MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues();
assertValues(fieldValues, 0); assertValues(fieldValues, 0);
assertValues(fieldValues, 1); assertValues(fieldValues, 1);
assertValues(fieldValues, 2); assertValues(fieldValues, 2);
@ -165,7 +165,7 @@ public class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
LeafFieldData fieldData = indexFieldData.load(readerContext); LeafFieldData fieldData = indexFieldData.load(readerContext);
assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed())); assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed()));
MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues(); MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues();
assertValues(fieldValues, 0); assertValues(fieldValues, 0);
assertMissing(fieldValues, 1); assertMissing(fieldValues, 1);
assertValues(fieldValues, 2); assertValues(fieldValues, 2);
@ -181,7 +181,7 @@ public class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
LeafFieldData fieldData = indexFieldData.load(readerContext); LeafFieldData fieldData = indexFieldData.load(readerContext);
assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed())); assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed()));
MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues(); MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues();
assertValues(fieldValues, 0); assertValues(fieldValues, 0);
assertValues(fieldValues, 1); assertValues(fieldValues, 1);
assertValues(fieldValues, 2); assertValues(fieldValues, 2);
@ -197,7 +197,7 @@ public class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
LeafFieldData fieldData = indexFieldData.load(readerContext); LeafFieldData fieldData = indexFieldData.load(readerContext);
assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed())); assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed()));
MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues(); MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues();
assertValues(fieldValues, 0); assertValues(fieldValues, 0);
assertMissing(fieldValues, 1); assertMissing(fieldValues, 1);

View file

@ -74,7 +74,7 @@ public class GeoPointScriptFieldTypeTests extends AbstractNonTextScriptFieldType
@Override @Override
public LeafCollector getLeafCollector(LeafReaderContext context) { public LeafCollector getLeafCollector(LeafReaderContext context) {
MultiGeoPointValues dv = ifd.load(context).getGeoPointValues(); MultiGeoPointValues dv = ifd.load(context).getPointValues();
return new LeafCollector() { return new LeafCollector() {
@Override @Override
public void setScorer(Scorable scorer) {} 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.Strings;
import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.SpatialPoint;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.geometry.utils.Geohash; import org.elasticsearch.geometry.utils.Geohash;
import org.elasticsearch.search.SearchHit; 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.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
@ESIntegTestCase.SuiteScopeTestCase @ESIntegTestCase.SuiteScopeTestCase
@ -285,4 +287,18 @@ public abstract class AbstractGeoTestCase extends ESIntegTestCase {
currentBound.resetLon(geoPoint.lon()); 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.store.Directory;
import org.apache.lucene.tests.index.RandomIndexWriter; import org.apache.lucene.tests.index.RandomIndexWriter;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.SpatialPoint;
import org.elasticsearch.index.mapper.GeoPointFieldMapper; import org.elasticsearch.index.mapper.GeoPointFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregationBuilder;
@ -152,10 +153,10 @@ public class GeoCentroidAggregatorTests extends AggregatorTestCase {
InternalGeoCentroid result = searchAndReduce(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType); InternalGeoCentroid result = searchAndReduce(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType);
assertEquals("my_agg", result.getName()); assertEquals("my_agg", result.getName());
GeoPoint centroid = result.centroid(); SpatialPoint centroid = result.centroid();
assertNotNull(centroid); assertNotNull(centroid);
assertEquals(expectedCentroid.getLat(), centroid.getLat(), GEOHASH_TOLERANCE); assertEquals(expectedCentroid.getX(), centroid.getX(), GEOHASH_TOLERANCE);
assertEquals(expectedCentroid.getLon(), centroid.getLon(), GEOHASH_TOLERANCE); assertEquals(expectedCentroid.getY(), centroid.getY(), GEOHASH_TOLERANCE);
assertTrue(AggregationInspectionHelper.hasValue(result)); assertTrue(AggregationInspectionHelper.hasValue(result));
} }
} }

View file

@ -9,6 +9,7 @@ package org.elasticsearch.search.aggregations.metrics;
import org.apache.lucene.geo.GeoEncodingUtils; import org.apache.lucene.geo.GeoEncodingUtils;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.SpatialPoint;
import org.elasticsearch.common.util.Maps; import org.elasticsearch.common.util.Maps;
import org.elasticsearch.search.aggregations.ParsedAggregation; import org.elasticsearch.search.aggregations.ParsedAggregation;
import org.elasticsearch.search.aggregations.support.SamplingContext; import org.elasticsearch.search.aggregations.support.SamplingContext;
@ -48,14 +49,14 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
long totalCount = 0; long totalCount = 0;
for (InternalGeoCentroid input : inputs) { for (InternalGeoCentroid input : inputs) {
if (input.count() > 0) { if (input.count() > 0) {
lonSum += (input.count() * input.centroid().getLon()); lonSum += (input.count() * input.centroid().getX());
latSum += (input.count() * input.centroid().getLat()); latSum += (input.count() * input.centroid().getY());
} }
totalCount += input.count(); totalCount += input.count();
} }
if (totalCount > 0) { if (totalCount > 0) {
assertEquals(latSum / totalCount, reduced.centroid().getLat(), 1E-5D); assertEquals(latSum / totalCount, reduced.centroid().getY(), 1E-5D);
assertEquals(lonSum / totalCount, reduced.centroid().getLon(), 1E-5D); assertEquals(lonSum / totalCount, reduced.centroid().getX(), 1E-5D);
} }
assertEquals(totalCount, reduced.count()); assertEquals(totalCount, reduced.count());
} }
@ -67,8 +68,8 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
@Override @Override
protected void assertSampled(InternalGeoCentroid sampled, InternalGeoCentroid reduced, SamplingContext samplingContext) { protected void assertSampled(InternalGeoCentroid sampled, InternalGeoCentroid reduced, SamplingContext samplingContext) {
assertEquals(sampled.centroid().getLat(), reduced.centroid().getLat(), 1e-12); assertEquals(sampled.centroid().getY(), reduced.centroid().getY(), 1e-12);
assertEquals(sampled.centroid().getLon(), reduced.centroid().getLon(), 1e-12); assertEquals(sampled.centroid().getX(), reduced.centroid().getX(), 1e-12);
assertEquals(sampled.count(), samplingContext.scaleUp(reduced.count()), 0); assertEquals(sampled.count(), samplingContext.scaleUp(reduced.count()), 0);
} }
@ -79,7 +80,7 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
Long.MAX_VALUE, Long.MAX_VALUE,
Collections.emptyMap() 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)); assertThat(reducedGeoCentroid.count(), equalTo(Long.MAX_VALUE));
} }
@ -95,7 +96,7 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
@Override @Override
protected InternalGeoCentroid mutateInstance(InternalGeoCentroid instance) { protected InternalGeoCentroid mutateInstance(InternalGeoCentroid instance) {
String name = instance.getName(); String name = instance.getName();
GeoPoint centroid = instance.centroid(); SpatialPoint centroid = instance.centroid();
long count = instance.count(); long count = instance.count();
Map<String, Object> metadata = instance.getMetadata(); Map<String, Object> metadata = instance.getMetadata();
switch (between(0, 3)) { switch (between(0, 3)) {
@ -115,9 +116,9 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
} else { } else {
GeoPoint newCentroid = new GeoPoint(centroid); GeoPoint newCentroid = new GeoPoint(centroid);
if (randomBoolean()) { if (randomBoolean()) {
newCentroid.resetLat(centroid.getLat() / 2.0); newCentroid.resetLat(centroid.getY() / 2.0);
} else { } else {
newCentroid.resetLon(centroid.getLon() / 2.0); newCentroid.resetLon(centroid.getX() / 2.0);
} }
centroid = newCentroid; centroid = newCentroid;
} }

View file

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

View file

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

View file

@ -9,7 +9,8 @@ package org.elasticsearch.xpack.spatial.search;
import org.apache.lucene.geo.Circle; import org.apache.lucene.geo.Circle;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.document.DocumentField; 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.common.geo.Orientation;
import org.elasticsearch.geo.GeometryTestUtils; import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Geometry;
@ -20,7 +21,6 @@ import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Polygon; import org.elasticsearch.geometry.Polygon;
import org.elasticsearch.geometry.utils.GeographyValidator; import org.elasticsearch.geometry.utils.GeographyValidator;
import org.elasticsearch.geometry.utils.WellKnownText; import org.elasticsearch.geometry.utils.WellKnownText;
import org.elasticsearch.index.fielddata.ScriptDocValues;
import org.elasticsearch.index.mapper.GeoShapeIndexer; import org.elasticsearch.index.mapper.GeoShapeIndexer;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.MockScriptPlugin; 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.DimensionalShapeType;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation; import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; 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.elasticsearch.xpack.spatial.util.GeoTestUtils;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Before; import org.junit.Before;
@ -79,52 +81,53 @@ public class GeoShapeScriptDocValuesIT extends ESSingleNodeTestCase {
private double scriptHeight(Map<String, Object> vars) { private double scriptHeight(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc); LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
if (geometry.size() == 0) { if (geometry.size() == 0) {
return Double.NaN; return Double.NaN;
} else { } else {
GeoBoundingBox boundingBox = geometry.getBoundingBox(); BoundingBox<GeoPoint> boundingBox = geometry.getBoundingBox();
return boundingBox.topLeft().lat() - boundingBox.bottomRight().lat(); return boundingBox.topLeft().lat() - boundingBox.bottomRight().lat();
} }
} }
private double scriptWidth(Map<String, Object> vars) { private double scriptWidth(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc); LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
if (geometry.size() == 0) { if (geometry.size() == 0) {
return Double.NaN; return Double.NaN;
} else { } else {
GeoBoundingBox boundingBox = geometry.getBoundingBox(); BoundingBox<GeoPoint> boundingBox = geometry.getBoundingBox();
return boundingBox.bottomRight().lon() - boundingBox.topLeft().lon(); return boundingBox.bottomRight().lon() - boundingBox.topLeft().lon();
} }
} }
private double scriptLat(Map<String, Object> vars) { private double scriptLat(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); 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(); return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lat();
} }
private double scriptLon(Map<String, Object> vars) { private double scriptLon(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); 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(); return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lon();
} }
private double scriptLabelLat(Map<String, Object> vars) { private double scriptLabelLat(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); 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(); return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lat();
} }
private double scriptLabelLon(Map<String, Object> vars) { private double scriptLabelLon(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc"); 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(); return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lon();
} }
private ScriptDocValues.Geometry<?> assertGeometry(Map<?, ?> doc) { private LeafShapeFieldData.ShapeScriptValues<GeoPoint> assertGeometry(Map<?, ?> doc) {
ScriptDocValues.Geometry<?> geometry = (ScriptDocValues.Geometry<?>) doc.get("location"); AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues geometry =
(AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues) doc.get("location");
if (geometry.size() == 0) { if (geometry.size() == 0) {
assertThat(geometry.getBoundingBox(), Matchers.nullValue()); assertThat(geometry.getBoundingBox(), Matchers.nullValue());
assertThat(geometry.getCentroid(), Matchers.nullValue()); assertThat(geometry.getCentroid(), Matchers.nullValue());
@ -267,8 +270,8 @@ public class GeoShapeScriptDocValuesIT extends ESSingleNodeTestCase {
.get(); .get();
assertSearchResponse(searchResponse); assertSearchResponse(searchResponse);
Map<String, DocumentField> fields = searchResponse.getHits().getHits()[0].getFields(); Map<String, DocumentField> fields = searchResponse.getHits().getHits()[0].getFields();
assertThat(fields.get("lat").getValue(), equalTo(value.lat())); assertThat(fields.get("lat").getValue(), equalTo(value.getY()));
assertThat(fields.get("lon").getValue(), equalTo(value.lon())); assertThat(fields.get("lon").getValue(), equalTo(value.getX()));
assertThat(fields.get("height").getValue(), equalTo(value.boundingBox().maxY() - value.boundingBox().minY())); assertThat(fields.get("height").getValue(), equalTo(value.boundingBox().maxY() - value.boundingBox().minY()));
assertThat(fields.get("width").getValue(), equalTo(value.boundingBox().maxX() - value.boundingBox().minX())); assertThat(fields.get("width").getValue(), equalTo(value.boundingBox().maxX() - value.boundingBox().minX()));
@ -285,16 +288,21 @@ public class GeoShapeScriptDocValuesIT extends ESSingleNodeTestCase {
doTestLabelPosition(fields, expectedLabelPosition); doTestLabelPosition(fields, expectedLabelPosition);
} else if (fallbackToCentroid && value.dimensionalShapeType() == DimensionalShapeType.POLYGON) { } else if (fallbackToCentroid && value.dimensionalShapeType() == DimensionalShapeType.POLYGON) {
// Use the centroid for all polygons, unless overwritten for specific cases // 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) private void doTestLabelPosition(Map<String, DocumentField> fields, GeoShapeValues.GeoShapeValue expectedLabelPosition)
throws IOException { 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( assertEquals(
"Unexpected longitude for label position,", "Unexpected longitude for label position,",
expectedLabelPosition.lon(), expectedLabelPosition.getX(),
fields.get("label_lon").getValue(), fields.get("label_lon").getValue(),
0.0000001 0.0000001
); );

View file

@ -7,24 +7,16 @@
package org.elasticsearch.xpack.spatial.index.fielddata; 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.LatLonGeometry;
import org.apache.lucene.geo.Point; import org.apache.lucene.geo.Point;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.Orientation; 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.index.mapper.GeoShapeIndexer;
import org.elasticsearch.search.aggregations.support.ValuesSourceType; 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 org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
/** /**
* A stateful lightweight per document geo values. * A stateful lightweight per document geo values.
@ -41,7 +33,7 @@ import java.text.ParseException;
* *
* There is just one value for one document. * There is just one value for one document.
*/ */
public abstract class GeoShapeValues { public abstract class GeoShapeValues extends ShapeValues {
public static GeoShapeValues EMPTY = new GeoShapeValues() { public static GeoShapeValues EMPTY = new GeoShapeValues() {
private final GeoShapeValuesSourceType DEFAULT_VALUES_SOURCE_TYPE = GeoShapeValuesSourceType.instance(); private final GeoShapeValuesSourceType DEFAULT_VALUES_SOURCE_TYPE = GeoShapeValuesSourceType.instance();
@ -65,194 +57,20 @@ public abstract class GeoShapeValues {
/** /**
* Creates a new {@link GeoShapeValues} instance * Creates a new {@link GeoShapeValues} instance
*/ */
protected GeoShapeValues() {} protected GeoShapeValues() {
super(CoordinateEncoder.GEO, GeoShapeValues.GeoShapeValue::new, new GeoShapeIndexer(Orientation.CCW, "missing"));
/** }
* 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;
/** thin wrapper around a {@link GeometryDocValueReader} which encodes / decodes values using /** thin wrapper around a {@link GeometryDocValueReader} which encodes / decodes values using
* the Geo decoder */ * the Geo decoder */
public static class GeoShapeValue implements ToXContentFragment { public static class GeoShapeValue extends ShapeValues.ShapeValue {
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 GeoShapeValue() { public GeoShapeValue() {
this.reader = new GeometryDocValueReader(); super(CoordinateEncoder.GEO, (x, y) -> new GeoPoint(y, x));
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);
}
} }
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { protected Component2D centroidAsComponent2D() throws IOException {
throw new IllegalArgumentException("cannot write xcontent for geo_shape doc value"); return LatLonGeometry.create(new Point(getY(), getX()));
}
}
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);
} }
} }
} }

View file

@ -10,6 +10,6 @@ package org.elasticsearch.xpack.spatial.index.fielddata;
import org.elasticsearch.index.fielddata.IndexFieldData; 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; package org.elasticsearch.xpack.spatial.index.fielddata;
import org.elasticsearch.common.geo.SpatialPoint;
import java.util.function.BiFunction; 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 * 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 SpatialPoint labelPosition;
private final BiFunction<Double, Double, T> pointMaker; 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); super(encoder);
this.pointMaker = pointMaker; this.pointMaker = pointMaker;
} }
@ -75,7 +77,7 @@ public class LabelPositionVisitor<T> extends TriangleTreeReader.DecodedVisitor {
return labelPosition == null; return labelPosition == null;
} }
public T labelPosition() { public SpatialPoint labelPosition() {
return 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; 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.GeoBoundingBox;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.index.fielddata.ScriptDocValues; 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.script.field.ToScriptFieldFactory;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues.GeoShapeValue; import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData;
import org.elasticsearch.xpack.spatial.index.fielddata.LeafGeoShapeFieldData; import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
import java.util.Collection;
import java.util.Collections;
import static org.elasticsearch.common.geo.SphericalMercatorUtils.latToSphericalMercator; import static org.elasticsearch.common.geo.SphericalMercatorUtils.latToSphericalMercator;
import static org.elasticsearch.common.geo.SphericalMercatorUtils.lonToSphericalMercator; 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<ShapeValues> toScriptFieldFactory) {
super(toScriptFieldFactory);
public AbstractAtomicGeoShapeShapeFieldData(ToScriptFieldFactory<GeoShapeValues> toScriptFieldFactory) {
this.toScriptFieldFactory = toScriptFieldFactory;
} }
@Override public static LeafShapeFieldData empty(final int maxDoc, ToScriptFieldFactory<ShapeValues> toScriptFieldFactory) {
public final SortedBinaryDocValues getBytesValues() { return new LeafShapeFieldData.Empty<>(toScriptFieldFactory, GeoShapeValues.EMPTY);
throw new UnsupportedOperationException("scripts and term aggs are not supported by geo_shape doc values");
} }
@Override public static final class GeoShapeScriptValues extends LeafShapeFieldData.ShapeScriptValues<GeoPoint>
public final DocValuesScriptFieldFactory getScriptFieldFactory(String name) { implements
return toScriptFieldFactory.getScriptFieldFactory(getGeoShapeValues(), name); ScriptDocValues.Geometry {
}
public static LeafGeoShapeFieldData empty(final int maxDoc, ToScriptFieldFactory<GeoShapeValues> toScriptFieldFactory) { public GeoShapeScriptValues(GeometrySupplier<GeoPoint, ShapeValues.ShapeValue> supplier) {
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) {
super(supplier); super(supplier);
this.gsSupplier = supplier;
} }
@Override @Override
public int getDimensionalType() { public GeoShapeValues.GeoShapeValue get(int index) {
return gsSupplier.getInternal(0) == null ? -1 : gsSupplier.getInternal(0).dimensionalShapeType().ordinal(); return (GeoShapeValues.GeoShapeValue) super.get(index);
} }
@Override @Override
public GeoPoint getCentroid() { public GeoShapeValues.GeoShapeValue getValue() {
return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalCentroid(); return (GeoShapeValues.GeoShapeValue) super.getValue();
}
@Override
public GeoBoundingBox getBoundingBox() {
return (GeoBoundingBox) super.getBoundingBox();
} }
@Override @Override
@ -93,29 +60,5 @@ public abstract class AbstractAtomicGeoShapeShapeFieldData implements LeafGeoSha
public double getMercatorHeight() { public double getMercatorHeight() {
return latToSphericalMercator(getBoundingBox().top()) - latToSphericalMercator(getBoundingBox().bottom()); 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.script.field.ToScriptFieldFactory;
import org.elasticsearch.search.aggregations.support.ValuesSourceType; import org.elasticsearch.search.aggregations.support.ValuesSourceType;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; 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 org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
final class LatLonShapeDVAtomicShapeFieldData extends AbstractAtomicGeoShapeShapeFieldData { final class LatLonShapeDVAtomicShapeFieldData extends LeafShapeFieldData {
private final LeafReader reader; private final LeafReader reader;
private final String fieldName; private final String fieldName;
LatLonShapeDVAtomicShapeFieldData(LeafReader reader, String fieldName, ToScriptFieldFactory<GeoShapeValues> toScriptFieldFactory) { LatLonShapeDVAtomicShapeFieldData(LeafReader reader, String fieldName, ToScriptFieldFactory<ShapeValues> toScriptFieldFactory) {
super(toScriptFieldFactory); super(toScriptFieldFactory);
this.reader = reader; this.reader = reader;
this.fieldName = fieldName; this.fieldName = fieldName;
@ -46,7 +48,7 @@ final class LatLonShapeDVAtomicShapeFieldData extends AbstractAtomicGeoShapeShap
} }
@Override @Override
public GeoShapeValues getGeoShapeValues() { public ShapeValues getShapeValues() {
try { try {
final BinaryDocValues binaryValues = DocValues.getBinary(reader, fieldName); final BinaryDocValues binaryValues = DocValues.getBinary(reader, fieldName);
final GeoShapeValues.GeoShapeValue geoShapeValue = new GeoShapeValues.GeoShapeValue(); 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.script.field.Field;
import org.elasticsearch.xpack.spatial.index.fielddata.CoordinateEncoder; import org.elasticsearch.xpack.spatial.index.fielddata.CoordinateEncoder;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; 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.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 org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
import java.io.IOException; import java.io.IOException;
@ -183,7 +185,11 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
@Override @Override
public IndexFieldData.Builder fielddataBuilder(FieldDataContext fieldDataContext) { public IndexFieldData.Builder fielddataBuilder(FieldDataContext fieldDataContext) {
failIfNoDocValues(); failIfNoDocValues();
return new AbstractLatLonShapeIndexFieldData.Builder(name(), GeoShapeValuesSourceType.instance(), GeoShapeDocValuesField::new); return (cache, breakerService) -> new LatLonShapeIndexFieldData(
name(),
GeoShapeValuesSourceType.instance(),
GeoShapeDocValuesField::new
);
} }
@Override @Override
@ -340,13 +346,13 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
super.checkIncomingMergeType(mergeWith); super.checkIncomingMergeType(mergeWith);
} }
public static class GeoShapeDocValuesField extends AbstractScriptFieldFactory<GeoShapeValues.GeoShapeValue> public static class GeoShapeDocValuesField extends AbstractScriptFieldFactory<ShapeValues.ShapeValue>
implements implements
Field<GeoShapeValues.GeoShapeValue>, Field<ShapeValues.ShapeValue>,
DocValuesScriptFieldFactory, DocValuesScriptFieldFactory,
ScriptDocValues.GeometrySupplier<GeoShapeValues.GeoShapeValue> { ScriptDocValues.GeometrySupplier<GeoPoint, ShapeValues.ShapeValue> {
private final GeoShapeValues in; private final ShapeValues in;
protected final String name; protected final String name;
private GeoShapeValues.GeoShapeValue value; 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) // maintain bwc by making bounding box and centroid available to GeoShapeValues (ScriptDocValues)
private final GeoPoint centroid = new GeoPoint(); private final GeoPoint centroid = new GeoPoint();
private final GeoBoundingBox boundingBox = new GeoBoundingBox(new GeoPoint(), 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.in = in;
this.name = name; this.name = name;
} }
@ -364,8 +370,8 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
@Override @Override
public void setNextDocId(int docId) throws IOException { public void setNextDocId(int docId) throws IOException {
if (in.advanceExact(docId)) { if (in.advanceExact(docId)) {
value = in.value(); value = (GeoShapeValues.GeoShapeValue) in.value();
centroid.reset(value.lat(), value.lon()); centroid.reset(value.getY(), value.getX());
boundingBox.topLeft().reset(value.boundingBox().maxY(), value.boundingBox().minX()); boundingBox.topLeft().reset(value.boundingBox().maxY(), value.boundingBox().minX());
boundingBox.bottomRight().reset(value.boundingBox().minY(), value.boundingBox().maxX()); boundingBox.bottomRight().reset(value.boundingBox().minY(), value.boundingBox().maxX());
} else { } else {
@ -374,7 +380,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
} }
@Override @Override
public ScriptDocValues<GeoShapeValues.GeoShapeValue> toScriptDocValues() { public ScriptDocValues<ShapeValues.ShapeValue> toScriptDocValues() {
if (geoShapeScriptValues == null) { if (geoShapeScriptValues == null) {
geoShapeScriptValues = new AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues(this); geoShapeScriptValues = new AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues(this);
} }
@ -383,7 +389,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
} }
@Override @Override
public GeoShapeValues.GeoShapeValue getInternal(int index) { public ShapeValues.ShapeValue getInternal(int index) {
if (index != 0) { if (index != 0) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -406,7 +412,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
@Override @Override
public GeoPoint getInternalLabelPosition() { public GeoPoint getInternalLabelPosition() {
try { try {
return value.labelPosition(); return new GeoPoint(value.labelPosition());
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException("Failed to parse geo shape label position: " + e.getMessage(), e); throw new UncheckedIOException("Failed to parse geo shape label position: " + e.getMessage(), e);
} }
@ -440,8 +446,8 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
} }
@Override @Override
public Iterator<GeoShapeValues.GeoShapeValue> iterator() { public Iterator<ShapeValues.ShapeValue> iterator() {
return new Iterator<GeoShapeValues.GeoShapeValue>() { return new Iterator<>() {
private int index = 0; private int index = 0;
@Override @Override
@ -450,7 +456,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
} }
@Override @Override
public GeoShapeValues.GeoShapeValue next() { public ShapeValues.ShapeValue next() {
if (hasNext() == false) { if (hasNext() == false) {
throw new NoSuchElementException(); throw new NoSuchElementException();
} }

View file

@ -12,6 +12,7 @@ import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.Geohash; import org.elasticsearch.geometry.utils.Geohash;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation; import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
import java.io.IOException; import java.io.IOException;
@ -33,7 +34,7 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
} }
@Override @Override
public int setValues(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geoValue) throws IOException { public int setValues(GeoShapeCellValues values, ShapeValues.ShapeValue geoValue) throws IOException {
if (precision == 0) { if (precision == 0) {
return 1; return 1;
@ -51,11 +52,8 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
return setValuesByRasterization("", values, 0, geoValue); return setValuesByRasterization("", values, 0, geoValue);
} }
protected int setValuesByBruteForceScan( protected int setValuesByBruteForceScan(GeoShapeCellValues values, ShapeValues.ShapeValue geoValue, GeoShapeValues.BoundingBox bounds)
GeoShapeCellValues values, throws IOException {
GeoShapeValues.GeoShapeValue 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 // 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. // see that eventually we will be visiting twice the same cell which should not happen.
int idx = 0; 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 { throws IOException {
String hash = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision); String hash = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision);
if (relateTile(geoValue, hash) != GeoRelation.QUERY_DISJOINT) { if (relateTile(geoValue, hash) != GeoRelation.QUERY_DISJOINT) {
@ -93,7 +91,7 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
return 0; 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)) { if (validHash(hash)) {
final Rectangle rectangle = Geohash.toBoundingBox(hash); final Rectangle rectangle = Geohash.toBoundingBox(hash);
int minX = GeoEncodingUtils.encodeLongitude(rectangle.getMinLon()); int minX = GeoEncodingUtils.encodeLongitude(rectangle.getMinLon());
@ -105,26 +103,26 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
return GeoRelation.QUERY_DISJOINT; 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 { throws IOException {
String[] hashes = Geohash.getSubGeohashes(hash); String[] hashes = Geohash.getSubGeohashes(hash);
for (int i = 0; i < hashes.length; i++) { for (String s : hashes) {
GeoRelation relation = relateTile(geoValue, hashes[i]); GeoRelation relation = relateTile(geoValue, s);
if (relation == GeoRelation.QUERY_CROSSES) { if (relation == GeoRelation.QUERY_CROSSES) {
if (hashes[i].length() == precision) { if (s.length() == precision) {
values.resizeCell(valuesIndex + 1); values.resizeCell(valuesIndex + 1);
values.add(valuesIndex++, Geohash.longEncode(hashes[i])); values.add(valuesIndex++, Geohash.longEncode(s));
} else { } else {
valuesIndex = setValuesByRasterization(hashes[i], values, valuesIndex, geoValue); valuesIndex = setValuesByRasterization(s, values, valuesIndex, geoValue);
} }
} else if (relation == GeoRelation.QUERY_INSIDE) { } else if (relation == GeoRelation.QUERY_INSIDE) {
if (hashes[i].length() == precision) { if (s.length() == precision) {
values.resizeCell(valuesIndex + 1); values.resizeCell(valuesIndex + 1);
values.add(valuesIndex++, Geohash.longEncode(hashes[i])); values.add(valuesIndex++, Geohash.longEncode(s));
} else { } else {
int numTilesAtPrecision = getNumTilesAtPrecision(precision, hash.length()); int numTilesAtPrecision = getNumTilesAtPrecision(precision, hash.length());
values.resizeCell(getNewSize(valuesIndex, numTilesAtPrecision + 1)); 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) { protected int setValuesForFullyContainedTile(String hash, GeoShapeCellValues values, int valuesIndex, int targetPrecision) {
String[] hashes = Geohash.getSubGeohashes(hash); String[] hashes = Geohash.getSubGeohashes(hash);
for (int i = 0; i < hashes.length; i++) { for (String s : hashes) {
if (validHash(hashes[i])) { if (validHash(s)) {
if (hashes[i].length() == targetPrecision) { if (s.length() == targetPrecision) {
values.add(valuesIndex++, Geohash.longEncode(hashes[i])); values.add(valuesIndex++, Geohash.longEncode(s));
} else { } 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.search.aggregations.bucket.geogrid.GeoTileUtils;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation; import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
import java.io.IOException; import java.io.IOException;
@ -47,7 +48,7 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
* @return the number of tiles set by the shape * @return the number of tiles set by the shape
*/ */
@Override @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(); GeoShapeValues.BoundingBox bounds = geoValue.boundingBox();
assert bounds.minX() <= bounds.maxX(); 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)) { if (validTile(xTile, yTile, precision)) {
final double tiles = 1 << precision; final double tiles = 1 << precision;
final int minX = GeoEncodingUtils.encodeLongitude(GeoTileUtils.tileToLon(xTile, tiles)); final int minX = GeoEncodingUtils.encodeLongitude(GeoTileUtils.tileToLon(xTile, tiles));
@ -112,7 +113,7 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
*/ */
protected int setValuesByBruteForceScan( protected int setValuesByBruteForceScan(
GeoShapeCellValues values, GeoShapeCellValues values,
GeoShapeValues.GeoShapeValue geoValue, ShapeValues.ShapeValue geoValue,
int minXTile, int minXTile,
int minYTile, int minYTile,
int maxXTile, int maxXTile,
@ -137,7 +138,7 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
int zTile, int zTile,
GeoShapeCellValues values, GeoShapeCellValues values,
int valuesIndex, int valuesIndex,
GeoShapeValues.GeoShapeValue geoValue ShapeValues.ShapeValue geoValue
) throws IOException { ) throws IOException {
zTile++; zTile++;
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {

View file

@ -7,7 +7,7 @@
package org.elasticsearch.xpack.spatial.search.aggregations.bucket.geogrid; 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.io.IOException;
@ -44,7 +44,7 @@ public abstract class GeoGridTiler {
* *
* @return the number of cells the geoValue intersects * @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 */ /** Maximum number of cells that can be created by this tiler */
protected abstract long getMaxCells(); 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.index.fielddata.SortedNumericDoubleValues;
import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.ValuesSource;
import org.elasticsearch.search.aggregations.support.ValuesSourceType; 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.GeoShapeValuesSource;
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType; import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
@ -46,7 +46,7 @@ public class GeoShapeCellIdSource extends ValuesSource.Numeric {
@Override @Override
public SortedNumericDocValues longValues(LeafReaderContext ctx) { public SortedNumericDocValues longValues(LeafReaderContext ctx) {
GeoShapeValues geoValues = valuesSource.geoShapeValues(ctx); ShapeValues geoValues = valuesSource.shapeValues(ctx);
ValuesSourceType vs = geoValues.valuesSourceType(); ValuesSourceType vs = geoValues.valuesSourceType();
if (GeoShapeValuesSourceType.instance() == vs) { if (GeoShapeValuesSourceType.instance() == vs) {
// docValues are geo shapes // docValues are geo shapes

View file

@ -7,17 +7,17 @@
package org.elasticsearch.xpack.spatial.search.aggregations.bucket.geogrid; 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.io.IOException;
import java.util.function.LongConsumer; import java.util.function.LongConsumer;
/** Sorted numeric doc values for geo shapes */ /** Sorted numeric doc values for geo shapes */
class GeoShapeCellValues extends ByteTrackingSortingNumericDocValues { class GeoShapeCellValues extends ByteTrackingSortingNumericDocValues {
private final GeoShapeValues geoShapeValues; private final ShapeValues geoShapeValues;
protected final GeoGridTiler tiler; protected final GeoGridTiler tiler;
protected GeoShapeCellValues(GeoShapeValues geoShapeValues, GeoGridTiler tiler, LongConsumer circuitBreakerConsumer) { protected GeoShapeCellValues(ShapeValues geoShapeValues, GeoGridTiler tiler, LongConsumer circuitBreakerConsumer) {
super(circuitBreakerConsumer); super(circuitBreakerConsumer);
this.geoShapeValues = geoShapeValues; this.geoShapeValues = geoShapeValues;
this.tiler = tiler; 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.AggregationContext;
import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; 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.GeoShapeValuesSource;
import java.io.IOException; import java.io.IOException;
@ -66,13 +67,13 @@ public final class GeoShapeBoundsAggregator extends MetricsAggregator {
if (valuesSource == null) { if (valuesSource == null) {
return LeafBucketCollector.NO_OP_COLLECTOR; return LeafBucketCollector.NO_OP_COLLECTOR;
} }
final GeoShapeValues values = valuesSource.geoShapeValues(aggCtx.getLeafReaderContext()); final ShapeValues values = valuesSource.shapeValues(aggCtx.getLeafReaderContext());
return new LeafBucketCollectorBase(sub, values) { return new LeafBucketCollectorBase(sub, values) {
@Override @Override
public void collect(int doc, long bucket) throws IOException { public void collect(int doc, long bucket) throws IOException {
if (values.advanceExact(doc)) { if (values.advanceExact(doc)) {
maybeResize(bucket); maybeResize(bucket);
final GeoShapeValues.GeoShapeValue value = values.value(); final GeoShapeValues.ShapeValue value = values.value();
final GeoShapeValues.BoundingBox bounds = value.boundingBox(); final GeoShapeValues.BoundingBox bounds = value.boundingBox();
tops.set(bucket, Math.max(tops.get(bucket), bounds.top)); tops.set(bucket, Math.max(tops.get(bucket), bounds.top));
bottoms.set(bucket, Math.min(bottoms.get(bucket), bounds.bottom)); 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.AggregationContext;
import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
import org.elasticsearch.xpack.spatial.index.fielddata.DimensionalShapeType; 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 org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSource;
import java.io.IOException; import java.io.IOException;
@ -65,7 +65,7 @@ public final class GeoShapeCentroidAggregator extends MetricsAggregator {
if (valuesSource == null) { if (valuesSource == null) {
return LeafBucketCollector.NO_OP_COLLECTOR; 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 compensatedSumLat = new CompensatedSum(0, 0);
final CompensatedSum compensatedSumLon = new CompensatedSum(0, 0); final CompensatedSum compensatedSumLon = new CompensatedSum(0, 0);
final CompensatedSum compensatedSumWeight = 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 // Compute the sum of double values with Kahan summation algorithm which is more
// accurate than naive summation. // accurate than naive summation.
final DimensionalShapeType shapeType = DimensionalShapeType.fromOrdinalByte(dimensionalShapeTypes.get(bucket)); 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()); final int compares = shapeType.compareTo(value.dimensionalShapeType());
// update the sum // update the sum
if (compares < 0) { if (compares < 0) {
// shape with higher dimensional value // shape with higher dimensional value
final double coordinateWeight = value.weight(); final double coordinateWeight = value.weight();
compensatedSumLat.reset(coordinateWeight * value.lat(), 0.0); compensatedSumLat.reset(coordinateWeight * value.getY(), 0.0);
compensatedSumLon.reset(coordinateWeight * value.lon(), 0.0); compensatedSumLon.reset(coordinateWeight * value.getX(), 0.0);
compensatedSumWeight.reset(coordinateWeight, 0.0); compensatedSumWeight.reset(coordinateWeight, 0.0);
dimensionalShapeTypes.set(bucket, (byte) value.dimensionalShapeType().ordinal()); dimensionalShapeTypes.set(bucket, (byte) value.dimensionalShapeType().ordinal());
} else if (compares == 0) { } else if (compares == 0) {
@ -96,8 +96,8 @@ public final class GeoShapeCentroidAggregator extends MetricsAggregator {
compensatedSumLon.reset(lonSum.get(bucket), lonCompensations.get(bucket)); compensatedSumLon.reset(lonSum.get(bucket), lonCompensations.get(bucket));
compensatedSumWeight.reset(weightSum.get(bucket), weightCompensations.get(bucket)); compensatedSumWeight.reset(weightSum.get(bucket), weightCompensations.get(bucket));
final double coordinateWeight = value.weight(); final double coordinateWeight = value.weight();
compensatedSumLat.add(coordinateWeight * value.lat()); compensatedSumLat.add(coordinateWeight * value.getY());
compensatedSumLon.add(coordinateWeight * value.lon()); compensatedSumLon.add(coordinateWeight * value.getX());
compensatedSumWeight.add(coordinateWeight); compensatedSumWeight.add(coordinateWeight);
} else { } else {
// do not modify centroid calculation since shape is of lower dimension than the running dimension // 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.apache.lucene.index.LeafReaderContext;
import org.elasticsearch.common.Rounding; 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.index.fielddata.SortedBinaryDocValues;
import org.elasticsearch.search.aggregations.AggregationExecutionException; 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.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.io.IOException;
import java.util.function.Function; import java.util.function.Function;
public abstract class GeoShapeValuesSource extends ValuesSource { public abstract class GeoShapeValuesSource extends ShapeValuesSource {
public static final GeoShapeValuesSource EMPTY = new GeoShapeValuesSource() { public static final GeoShapeValuesSource EMPTY = new GeoShapeValuesSource() {
@Override @Override
public GeoShapeValues geoShapeValues(LeafReaderContext context) { public GeoShapeValues shapeValues(LeafReaderContext context) {
return GeoShapeValues.EMPTY; return GeoShapeValues.EMPTY;
} }
@Override
public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException {
return FieldData.emptySortedBinary();
}
}; };
public abstract GeoShapeValues geoShapeValues(LeafReaderContext context);
@Override @Override
protected Function<Rounding, Rounding.Prepared> roundingPreparer() throws IOException { protected Function<Rounding, Rounding.Prepared> roundingPreparer() throws IOException {
throw new AggregationExecutionException("can't round a [geo_shape]"); 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 { 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; this.indexFieldData = indexFieldData;
} }
@ -66,8 +45,8 @@ public abstract class GeoShapeValuesSource extends ValuesSource {
return indexFieldData.load(context).getBytesValues(); return indexFieldData.load(context).getBytesValues();
} }
public GeoShapeValues geoShapeValues(LeafReaderContext context) { public ShapeValues shapeValues(LeafReaderContext context) {
return indexFieldData.load(context).getGeoShapeValues(); 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.index.LeafReaderContext;
import org.apache.lucene.util.BytesRef; 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.IndexGeoPointFieldData;
import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
import org.elasticsearch.script.AggregationScript; 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.AggregationContext;
import org.elasticsearch.search.aggregations.support.FieldContext; import org.elasticsearch.search.aggregations.support.FieldContext;
import org.elasticsearch.search.aggregations.support.MissingValues; 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.ValuesSource;
import org.elasticsearch.search.aggregations.support.ValuesSourceType; import org.elasticsearch.search.aggregations.support.ValuesSourceType;
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; 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; import java.io.IOException;
public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType { public class GeoShapeValuesSourceType extends ShapeValuesSourceType {
static GeoShapeValuesSourceType INSTANCE = new GeoShapeValuesSourceType(); static GeoShapeValuesSourceType INSTANCE = new GeoShapeValuesSourceType();
@ -39,17 +38,11 @@ public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
return GeoShapeValuesSource.EMPTY; return GeoShapeValuesSource.EMPTY;
} }
@Override
public ValuesSource getScript(AggregationScript.LeafFactory script, ValueType scriptValueType) {
// TODO (support scripts)
throw new UnsupportedOperationException("geo_shape");
}
@Override @Override
public ValuesSource getField(FieldContext fieldContext, AggregationScript.LeafFactory script, AggregationContext context) { public ValuesSource getField(FieldContext fieldContext, AggregationScript.LeafFactory script, AggregationContext context) {
boolean isGeoPoint = fieldContext.indexFieldData() instanceof IndexGeoPointFieldData; boolean isPoint = fieldContext.indexFieldData() instanceof IndexGeoPointFieldData;
boolean isGeoShape = fieldContext.indexFieldData() instanceof IndexGeoShapeFieldData; boolean isShape = fieldContext.indexFieldData() instanceof IndexShapeFieldData;
if (isGeoPoint == false && isGeoShape == false) { if (isPoint == false && isShape == false) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Expected geo_point or geo_shape type on field [" "Expected geo_point or geo_shape type on field ["
+ fieldContext.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 ValuesSource.GeoPoint.Fielddata((IndexGeoPointFieldData) fieldContext.indexFieldData());
} }
return new GeoShapeValuesSource.Fielddata((IndexGeoShapeFieldData) fieldContext.indexFieldData()); return new GeoShapeValuesSource.Fielddata((LatLonShapeIndexFieldData) fieldContext.indexFieldData());
} }
@Override @Override
@ -71,12 +64,12 @@ public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
DocValueFormat docValueFormat, DocValueFormat docValueFormat,
AggregationContext context AggregationContext context
) { ) {
GeoShapeValuesSource geoShapeValuesSource = (GeoShapeValuesSource) valuesSource; GeoShapeValuesSource shapeValuesSource = (GeoShapeValuesSource) valuesSource;
final GeoShapeValues.GeoShapeValue missing = GeoShapeValues.GeoShapeValue.missing(rawMissing.toString()); final ShapeValues.ShapeValue missing = GeoShapeValues.EMPTY.missing(rawMissing.toString());
return new GeoShapeValuesSource() { return new GeoShapeValuesSource() {
@Override @Override
public GeoShapeValues geoShapeValues(LeafReaderContext context) { public GeoShapeValues shapeValues(LeafReaderContext context) {
GeoShapeValues values = geoShapeValuesSource.geoShapeValues(context); ShapeValues values = shapeValuesSource.shapeValues(context);
return new GeoShapeValues() { return new GeoShapeValues() {
private boolean exists; private boolean exists;
@ -95,20 +88,20 @@ public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
} }
@Override @Override
public GeoShapeValue value() throws IOException { public ShapeValue value() throws IOException {
return exists ? values.value() : missing; return exists ? values.value() : missing;
} }
@Override @Override
public String toString() { public String toString() {
return "anon MultiGeoShapeValues of [" + super.toString() + "]"; return "anon MultiShapeValues of [" + super.toString() + "]";
} }
}; };
} }
@Override @Override
public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { 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() { public String typeName() {
return "geoshape"; 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 }); Line line = new Line(new double[] { 180.0, 180.0 }, new double[] { latA, latB });
GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line); 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 }); Line line = new Line(new double[] { -180.0, -180.0 }, new double[] { latA, latB });
GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line); 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 }); Line line = new Line(new double[] { lonA, lonB }, new double[] { 90.0, 90.0 });
GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line); 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 }); Line line = new Line(new double[] { lonA, lonB }, new double[] { -90.0, -90.0 });
GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line); 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.GeoPoint;
import org.elasticsearch.common.geo.GeometryNormalizer; import org.elasticsearch.common.geo.GeometryNormalizer;
import org.elasticsearch.common.geo.Orientation; import org.elasticsearch.common.geo.Orientation;
import org.elasticsearch.common.geo.SpatialPoint;
import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.GeometryCollection; import org.elasticsearch.geometry.GeometryCollection;
import org.elasticsearch.geometry.LinearRing; import org.elasticsearch.geometry.LinearRing;
@ -133,11 +134,11 @@ public class GeometryDocValueTests extends ESTestCase {
// Label position is the centroid if within the polygon // Label position is the centroid if within the polygon
GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(rectangle); GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(rectangle);
GeoPoint labelPosition = shapeValue.labelPosition(); SpatialPoint labelPosition = shapeValue.labelPosition();
double labelLon = ((double) minX + maxX) / 2; double labelX = ((double) minX + maxX) / 2;
double labelLat = ((double) minY + maxY) / 2; double labelY = ((double) minY + maxY) / 2;
assertEquals(labelLon, labelPosition.lon(), 0.0000001); assertEquals(labelX, labelPosition.getX(), 0.0000001);
assertEquals(labelLat, labelPosition.lat(), 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 // Label position is calculated as the first triangle
GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry); GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry);
GeoPoint labelPosition = shapeValue.labelPosition(); SpatialPoint labelPosition = shapeValue.labelPosition();
assertThat( assertThat(
"Expect label position to match one of eight triangles in the two rectangles", "Expect label position to match one of eight triangles in the two rectangles",
labelPosition, new GeoPoint(labelPosition),
isRectangleLabelPosition(r1, r2) isRectangleLabelPosition(r1, r2)
); );
} }
@ -177,11 +178,11 @@ public class GeometryDocValueTests extends ESTestCase {
// Label position is the centroid if within the polygon // Label position is the centroid if within the polygon
GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry); GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry);
GeoPoint labelPosition = shapeValue.labelPosition(); SpatialPoint labelPosition = shapeValue.labelPosition();
double centroidX = CoordinateEncoder.GEO.decodeX(reader.getCentroidX()); double centroidX = CoordinateEncoder.GEO.decodeX(reader.getCentroidX());
double centroidY = CoordinateEncoder.GEO.decodeY(reader.getCentroidY()); double centroidY = CoordinateEncoder.GEO.decodeY(reader.getCentroidY());
assertEquals(centroidX, labelPosition.lon(), 0.0000001); assertEquals(centroidX, labelPosition.getX(), 0.0000001);
assertEquals(centroidY, labelPosition.lat(), 0.0000001); assertEquals(centroidY, labelPosition.getY(), 0.0000001);
Circle tolerance = new Circle(centroidY, centroidX, 1); Circle tolerance = new Circle(centroidY, centroidX, 1);
assertTrue("Expect label position to be within the geometry", shapeValue.relate(tolerance) != GeoRelation.QUERY_DISJOINT); 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 // Label position is the centroid if within the polygon
GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry); GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry);
GeoPoint labelPosition = shapeValue.labelPosition(); SpatialPoint labelPosition = shapeValue.labelPosition();
double centroidX = CoordinateEncoder.GEO.decodeX(reader.getCentroidX()); double centroidX = CoordinateEncoder.GEO.decodeX(reader.getCentroidX());
double centroidY = CoordinateEncoder.GEO.decodeY(reader.getCentroidY()); double centroidY = CoordinateEncoder.GEO.decodeY(reader.getCentroidY());
assertEquals(centroidX, labelPosition.lon(), 0.0000001); assertEquals(centroidX, labelPosition.getX(), 0.0000001);
assertEquals(centroidY, labelPosition.lat(), 0.0000001); assertEquals(centroidY, labelPosition.getY(), 0.0000001);
Circle tolerance = new Circle(centroidY, centroidX, 1); Circle tolerance = new Circle(centroidY, centroidX, 1);
assertTrue("Expect label position to be within the geometry", shapeValue.relate(tolerance) != GeoRelation.QUERY_DISJOINT); 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.Strings;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.Orientation; 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.DocumentMapper;
import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.Mapper; import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.MapperTestCase;
import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.ParsedDocument;
import org.elasticsearch.index.mapper.SourceToParse; import org.elasticsearch.index.mapper.SourceToParse;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.VersionUtils; import org.elasticsearch.test.VersionUtils;
import org.elasticsearch.xcontent.ToXContent; import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentFactory; import org.elasticsearch.xcontent.XContentFactory;
import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xcontent.XContentType;
import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin;
import org.junit.AssumptionViolatedException; import org.junit.AssumptionViolatedException;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import static org.hamcrest.Matchers.containsString; 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.hasSize;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase { public class GeoShapeWithDocValuesFieldMapperTests extends GeoFieldMapperTests {
@Override @Override
protected void minimalMapping(XContentBuilder b) throws IOException { protected String getFieldName() {
b.field("type", "geo_shape"); return "geo_shape";
}
@Override
protected Object getSampleValueForDocument() {
return "POINT (14.0 15.0)";
} }
@Override @Override
@ -64,45 +57,47 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
checker.registerConflictCheck("doc_values", b -> b.field("doc_values", false)); checker.registerConflictCheck("doc_values", b -> b.field("doc_values", false));
checker.registerConflictCheck("index", b -> b.field("index", false)); checker.registerConflictCheck("index", b -> b.field("index", false));
checker.registerUpdateCheck(b -> b.field("orientation", "right"), m -> { checker.registerUpdateCheck(b -> b.field("orientation", "right"), m -> {
GeoShapeWithDocValuesFieldMapper gsfm = (GeoShapeWithDocValuesFieldMapper) m; AbstractShapeGeometryFieldMapper<?> gsfm = (AbstractShapeGeometryFieldMapper<?>) m;
assertEquals(Orientation.RIGHT, gsfm.orientation()); assertEquals(Orientation.RIGHT, gsfm.orientation());
}); });
checker.registerUpdateCheck(b -> b.field("ignore_malformed", true), m -> { checker.registerUpdateCheck(b -> b.field("ignore_malformed", true), m -> {
GeoShapeWithDocValuesFieldMapper gpfm = (GeoShapeWithDocValuesFieldMapper) m; AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
assertTrue(gpfm.ignoreMalformed()); assertTrue(gpfm.ignoreMalformed());
}); });
checker.registerUpdateCheck(b -> b.field("ignore_z_value", false), m -> { checker.registerUpdateCheck(b -> b.field("ignore_z_value", false), m -> {
GeoShapeWithDocValuesFieldMapper gpfm = (GeoShapeWithDocValuesFieldMapper) m; AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
assertFalse(gpfm.ignoreZValue()); assertFalse(gpfm.ignoreZValue());
}); });
checker.registerUpdateCheck(b -> b.field("coerce", true), m -> { checker.registerUpdateCheck(b -> b.field("coerce", true), m -> {
GeoShapeWithDocValuesFieldMapper gpfm = (GeoShapeWithDocValuesFieldMapper) m; AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
assertTrue(gpfm.coerce()); assertTrue(gpfm.coerce());
}); });
} }
@Override protected AbstractShapeGeometryFieldType<?> fieldType(Mapper fieldMapper) {
protected Collection<Plugin> getPlugins() { AbstractShapeGeometryFieldMapper<?> shapeFieldMapper = (AbstractShapeGeometryFieldMapper<?>) fieldMapper;
return Collections.singletonList(new LocalStateSpatialPlugin()); return (AbstractShapeGeometryFieldType<?>) shapeFieldMapper.fieldType();
}
protected Class<? extends AbstractShapeGeometryFieldMapper<?>> fieldMapperClass() {
return GeoShapeWithDocValuesFieldMapper.class;
} }
public void testDefaultConfiguration() throws IOException { public void testDefaultConfiguration() throws IOException {
DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping)); DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping));
Mapper fieldMapper = mapper.mappers().getMapper("field"); Mapper fieldMapper = mapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
GeoShapeWithDocValuesFieldMapper geoShapeFieldMapper = (GeoShapeWithDocValuesFieldMapper) fieldMapper; AbstractShapeGeometryFieldType<?> fieldType = fieldType(fieldMapper);
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(Orientation.RIGHT)); assertThat(fieldType.orientation(), equalTo(Orientation.RIGHT));
assertTrue(geoShapeFieldMapper.fieldType().hasDocValues()); assertTrue(fieldType.hasDocValues());
} }
public void testDefaultDocValueConfigurationOnPre7_8() throws IOException { public void testDefaultDocValueConfigurationOnPre7_8() throws IOException {
Version oldVersion = VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_7_0); Version oldVersion = VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_7_0);
DocumentMapper defaultMapper = createDocumentMapper(oldVersion, fieldMapping(this::minimalMapping)); DocumentMapper defaultMapper = createDocumentMapper(oldVersion, fieldMapping(this::minimalMapping));
Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
GeoShapeWithDocValuesFieldMapper geoShapeFieldMapper = (GeoShapeWithDocValuesFieldMapper) fieldMapper; GeoShapeWithDocValuesFieldMapper geoShapeFieldMapper = (GeoShapeWithDocValuesFieldMapper) fieldMapper;
assertFalse(geoShapeFieldMapper.fieldType().hasDocValues()); assertFalse(geoShapeFieldMapper.fieldType().hasDocValues());
@ -114,26 +109,28 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
public void testOrientationParsing() throws IOException { public void testOrientationParsing() throws IOException {
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("orientation", "left"); b.field("orientation", "left");
})); }));
Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); 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.CLOCKWISE));
assertThat(orientation, equalTo(Orientation.LEFT)); assertThat(orientation, equalTo(Orientation.LEFT));
assertThat(orientation, equalTo(Orientation.CW)); assertThat(orientation, equalTo(Orientation.CW));
// explicit right orientation test // explicit right orientation test
defaultMapper = createDocumentMapper(fieldMapping(b -> { defaultMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("orientation", "right"); b.field("orientation", "right");
})); }));
fieldMapper = defaultMapper.mappers().getMapper("field"); fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); 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.COUNTER_CLOCKWISE));
assertThat(orientation, equalTo(Orientation.RIGHT)); assertThat(orientation, equalTo(Orientation.RIGHT));
assertThat(orientation, equalTo(Orientation.CCW)); assertThat(orientation, equalTo(Orientation.CCW));
@ -145,23 +142,23 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
public void testCoerceParsing() throws IOException { public void testCoerceParsing() throws IOException {
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("coerce", true); b.field("coerce", true);
})); }));
Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
boolean coerce = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).coerce(); boolean coerce = ((AbstractShapeGeometryFieldMapper<?>) fieldMapper).coerce();
assertThat(coerce, equalTo(true)); assertThat(coerce, equalTo(true));
defaultMapper = createDocumentMapper(fieldMapping(b -> { defaultMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("coerce", false); b.field("coerce", false);
})); }));
fieldMapper = defaultMapper.mappers().getMapper("field"); fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
coerce = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).coerce(); coerce = ((AbstractShapeGeometryFieldMapper<?>) fieldMapper).coerce();
assertThat(coerce, equalTo(false)); assertThat(coerce, equalTo(false));
} }
@ -171,24 +168,24 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
*/ */
public void testIgnoreZValue() throws IOException { public void testIgnoreZValue() throws IOException {
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("ignore_z_value", true); b.field("ignore_z_value", true);
})); }));
Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
boolean ignoreZValue = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreZValue(); boolean ignoreZValue = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreZValue();
assertThat(ignoreZValue, equalTo(true)); assertThat(ignoreZValue, equalTo(true));
// explicit false accept_z_value test // explicit false accept_z_value test
defaultMapper = createDocumentMapper(fieldMapping(b -> { defaultMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("ignore_z_value", false); b.field("ignore_z_value", false);
})); }));
fieldMapper = defaultMapper.mappers().getMapper("field"); fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
ignoreZValue = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreZValue(); ignoreZValue = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreZValue();
assertThat(ignoreZValue, equalTo(false)); assertThat(ignoreZValue, equalTo(false));
} }
@ -198,45 +195,45 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
public void testIgnoreMalformedParsing() throws IOException { public void testIgnoreMalformedParsing() throws IOException {
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("ignore_malformed", true); b.field("ignore_malformed", true);
})); }));
Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
boolean ignoreMalformed = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreMalformed(); boolean ignoreMalformed = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreMalformed();
assertThat(ignoreMalformed, equalTo(true)); assertThat(ignoreMalformed, equalTo(true));
// explicit false ignore_malformed test // explicit false ignore_malformed test
defaultMapper = createDocumentMapper(fieldMapping(b -> { defaultMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("ignore_malformed", false); b.field("ignore_malformed", false);
})); }));
fieldMapper = defaultMapper.mappers().getMapper("field"); fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
ignoreMalformed = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreMalformed(); ignoreMalformed = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreMalformed();
assertThat(ignoreMalformed, equalTo(false)); assertThat(ignoreMalformed, equalTo(false));
} }
public void testIgnoreMalformedValues() throws IOException { public void testIgnoreMalformedValues() throws IOException {
DocumentMapper ignoreMapper = createDocumentMapper(fieldMapping(b -> { DocumentMapper ignoreMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("ignore_malformed", true); b.field("ignore_malformed", true);
})); }));
DocumentMapper failMapper = createDocumentMapper(fieldMapping(b -> { DocumentMapper failMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("ignore_malformed", false); b.field("ignore_malformed", false);
})); }));
{ {
BytesReference arrayedDoc = BytesReference.bytes( 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); SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON);
ParsedDocument document = ignoreMapper.parse(sourceToParse); 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)); MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse));
assertThat(exception.getCause().getMessage(), containsString("Unknown geometry type: bad")); assertThat(exception.getCause().getMessage(), containsString("Unknown geometry type: bad"));
} }
@ -245,7 +242,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
XContentFactory.jsonBuilder() XContentFactory.jsonBuilder()
.startObject() .startObject()
.field( .field(
"field", FIELD_NAME,
"POLYGON ((18.9401790919516 -33.9681188869036, 18.9401790919516 -33.9681188869036, 18.9401790919517 " "POLYGON ((18.9401790919516 -33.9681188869036, 18.9401790919516 -33.9681188869036, 18.9401790919517 "
+ "-33.9681188869036, 18.9401790919517 -33.9681188869036, 18.9401790919516 -33.9681188869036))" + "-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); SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON);
ParsedDocument document = ignoreMapper.parse(sourceToParse); 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)); MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse));
assertThat(exception.getCause().getMessage(), containsString("at least three non-collinear points required")); 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 { public void testDocValues() throws IOException {
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("doc_values", true); b.field("doc_values", true);
})); }));
Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
boolean hasDocValues = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).fieldType().hasDocValues(); boolean hasDocValues = ((AbstractGeometryFieldMapper<?>) fieldMapper).fieldType().hasDocValues();
assertTrue(hasDocValues); assertTrue(hasDocValues);
// explicit false doc_values // explicit false doc_values
defaultMapper = createDocumentMapper(fieldMapping(b -> { defaultMapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("doc_values", false); b.field("doc_values", false);
})); }));
fieldMapper = defaultMapper.mappers().getMapper("field"); fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
hasDocValues = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).fieldType().hasDocValues(); hasDocValues = ((AbstractGeometryFieldMapper<?>) fieldMapper).fieldType().hasDocValues();
assertFalse(hasDocValues); assertFalse(hasDocValues);
} }
public void testGeoShapeMapperMerge() throws Exception { public void testShapeMapperMerge() throws Exception {
MapperService mapperService = createMapperService(fieldMapping(b -> { MapperService mapperService = createMapperService(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("orientation", "ccw"); b.field("orientation", "ccw");
})); }));
merge(mapperService, fieldMapping(b -> { merge(mapperService, fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("orientation", "cw"); b.field("orientation", "cw");
})); }));
Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper("field"); Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
GeoShapeWithDocValuesFieldMapper geoShapeFieldMapper = (GeoShapeWithDocValuesFieldMapper) fieldMapper; AbstractShapeGeometryFieldType<?> fieldType = fieldType(fieldMapper);
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(Orientation.CW)); assertThat(fieldType.orientation(), equalTo(Orientation.CW));
} }
public void testInvalidCurrentVersion() { public void testInvalidCurrentVersion() {
@ -309,7 +306,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
MapperParsingException.class, MapperParsingException.class,
() -> super.createMapperService( () -> super.createMapperService(
Version.CURRENT, Version.CURRENT,
fieldMapping((b) -> { b.field("type", "geo_shape").field("strategy", "recursive"); }) fieldMapping((b) -> { b.field("type", getFieldName()).field("strategy", "recursive"); })
) )
); );
assertThat( assertThat(
@ -320,17 +317,17 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
public void testGeoShapeLegacyMerge() throws Exception { public void testGeoShapeLegacyMerge() throws Exception {
Version version = VersionUtils.randomPreviousCompatibleVersion(random(), Version.V_8_0_0); 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( Exception e = expectThrows(
IllegalArgumentException.class, 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]")); assertThat(e.getMessage(), containsString("mapper [field] of type [geo_shape] cannot change strategy from [BKD] to [recursive]"));
assertFieldWarnings("strategy"); assertFieldWarnings("strategy");
MapperService lm = createMapperService(version, fieldMapping(b -> b.field("type", "geo_shape").field("strategy", "recursive"))); MapperService lm = createMapperService(version, fieldMapping(b -> b.field("type", getFieldName()).field("strategy", "recursive")));
e = expectThrows(IllegalArgumentException.class, () -> merge(lm, fieldMapping(b -> b.field("type", "geo_shape")))); 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]")); assertThat(e.getMessage(), containsString("mapper [field] of type [geo_shape] cannot change strategy from [recursive] to [BKD]"));
assertFieldWarnings("strategy"); assertFieldWarnings("strategy");
} }
@ -345,7 +342,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
public void testSerializeDefaults() throws Exception { public void testSerializeDefaults() throws Exception {
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(this::minimalMapping)); 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("\"orientation\":\"" + Orientation.RIGHT + "\""));
assertTrue(serialized, serialized.contains("\"doc_values\":true")); assertTrue(serialized, serialized.contains("\"doc_values\":true"));
} }
@ -353,22 +350,20 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
public void testSerializeDocValues() throws IOException { public void testSerializeDocValues() throws IOException {
boolean docValues = randomBoolean(); boolean docValues = randomBoolean();
DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> { DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> {
b.field("type", "geo_shape"); b.field("type", getFieldName());
b.field("doc_values", docValues); 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("\"orientation\":\"" + Orientation.RIGHT + "\""));
assertTrue(serialized, serialized.contains("\"doc_values\":" + docValues)); assertTrue(serialized, serialized.contains("\"doc_values\":" + docValues));
} }
public void testGeoShapeArrayParsing() throws Exception { public void testShapeArrayParsing() throws Exception {
DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping)); DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping));
BytesReference arrayedDoc = BytesReference.bytes( SourceToParse sourceToParse = source(b -> {
XContentFactory.jsonBuilder() b.startArray("shape")
.startObject()
.startArray("shape")
.startObject() .startObject()
.field("type", "Point") .field("type", "Point")
.startArray("coordinates") .startArray("coordinates")
@ -383,11 +378,9 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
.value(-15.0) .value(-15.0)
.endArray() .endArray()
.endObject() .endObject()
.endArray() .endArray();
.endObject() });
);
SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON);
ParsedDocument document = mapper.parse(sourceToParse); ParsedDocument document = mapper.parse(sourceToParse);
assertThat(document.docs(), hasSize(1)); assertThat(document.docs(), hasSize(1));
IndexableField[] fields = document.docs().get(0).getFields("shape.type"); 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.startObject("keyword").field("type", "keyword").endObject();
b.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 { 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")); 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) { if (includeDefaults) {
ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true")); ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true"));
return Strings.toString(mapper, params); 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); return toXContentString(mapper, true);
} }

View file

@ -12,6 +12,9 @@ import org.elasticsearch.Version;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.Orientation; 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.DocumentMapper;
import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.Mapper; import org.elasticsearch.index.mapper.Mapper;
@ -68,40 +71,51 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
@Override @Override
protected void registerParameters(ParameterChecker checker) throws IOException { 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.registerConflictCheck("index", b -> b.field("index", false));
checker.registerUpdateCheck(b -> b.field("orientation", "right"), m -> { checker.registerUpdateCheck(b -> b.field("orientation", "right"), m -> {
ShapeFieldMapper gsfm = (ShapeFieldMapper) m; AbstractShapeGeometryFieldMapper<?> gsfm = (AbstractShapeGeometryFieldMapper<?>) m;
assertEquals(Orientation.RIGHT, gsfm.orientation()); assertEquals(Orientation.RIGHT, gsfm.orientation());
}); });
checker.registerUpdateCheck(b -> b.field("ignore_malformed", true), m -> { checker.registerUpdateCheck(b -> b.field("ignore_malformed", true), m -> {
ShapeFieldMapper gpfm = (ShapeFieldMapper) m; AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
assertTrue(gpfm.ignoreMalformed()); assertTrue(gpfm.ignoreMalformed());
}); });
checker.registerUpdateCheck(b -> b.field("ignore_z_value", false), m -> { checker.registerUpdateCheck(b -> b.field("ignore_z_value", false), m -> {
ShapeFieldMapper gpfm = (ShapeFieldMapper) m; AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
assertFalse(gpfm.ignoreZValue()); assertFalse(gpfm.ignoreZValue());
}); });
checker.registerUpdateCheck(b -> b.field("coerce", true), m -> { checker.registerUpdateCheck(b -> b.field("coerce", true), m -> {
ShapeFieldMapper gpfm = (ShapeFieldMapper) m; AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
assertTrue(gpfm.coerce()); 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 { public void testDefaultConfiguration() throws IOException {
DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping)); DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping));
Mapper fieldMapper = mapper.mappers().getMapper(FIELD_NAME); Mapper fieldMapper = mapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
ShapeFieldMapper shapeFieldMapper = (ShapeFieldMapper) fieldMapper; AbstractShapeGeometryFieldType<?> fieldType = fieldType(fieldMapper);
assertThat(shapeFieldMapper.fieldType().orientation(), equalTo(Orientation.RIGHT)); assertThat(fieldType.orientation(), equalTo(Orientation.RIGHT));
assertTrue(shapeFieldMapper.fieldType().hasDocValues()); assertTrue(fieldType.hasDocValues());
} }
public void testDefaultDocValueConfigurationOnPre8_4() throws IOException { 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); Version oldVersion = VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_8_3_0);
DocumentMapper defaultMapper = createDocumentMapper(oldVersion, fieldMapping(this::minimalMapping)); DocumentMapper defaultMapper = createDocumentMapper(oldVersion, fieldMapping(this::minimalMapping));
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
ShapeFieldMapper shapeFieldMapper = (ShapeFieldMapper) fieldMapper; ShapeFieldMapper shapeFieldMapper = (ShapeFieldMapper) fieldMapper;
assertFalse(shapeFieldMapper.fieldType().hasDocValues()); assertFalse(shapeFieldMapper.fieldType().hasDocValues());
@ -117,9 +131,10 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.field("orientation", "left"); b.field("orientation", "left");
})); }));
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); 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.CLOCKWISE));
assertThat(orientation, equalTo(Orientation.LEFT)); assertThat(orientation, equalTo(Orientation.LEFT));
assertThat(orientation, equalTo(Orientation.CW)); assertThat(orientation, equalTo(Orientation.CW));
@ -130,9 +145,10 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.field("orientation", "right"); b.field("orientation", "right");
})); }));
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); 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.COUNTER_CLOCKWISE));
assertThat(orientation, equalTo(Orientation.RIGHT)); assertThat(orientation, equalTo(Orientation.RIGHT));
assertThat(orientation, equalTo(Orientation.CCW)); assertThat(orientation, equalTo(Orientation.CCW));
@ -148,9 +164,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.field("coerce", true); b.field("coerce", true);
})); }));
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); 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)); assertThat(coerce, equalTo(true));
defaultMapper = createDocumentMapper(fieldMapping(b -> { defaultMapper = createDocumentMapper(fieldMapping(b -> {
@ -158,9 +174,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.field("coerce", false); b.field("coerce", false);
})); }));
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); 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)); assertThat(coerce, equalTo(false));
} }
@ -174,9 +190,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.field("ignore_z_value", true); b.field("ignore_z_value", true);
})); }));
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); 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)); assertThat(ignoreZValue, equalTo(true));
// explicit false accept_z_value test // explicit false accept_z_value test
@ -185,9 +201,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.field("ignore_z_value", false); b.field("ignore_z_value", false);
})); }));
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); 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)); assertThat(ignoreZValue, equalTo(false));
} }
@ -201,9 +217,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.field("ignore_malformed", true); b.field("ignore_malformed", true);
})); }));
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); 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)); assertThat(ignoreMalformed, equalTo(true));
// explicit false ignore_malformed test // explicit false ignore_malformed test
@ -212,9 +228,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.field("ignore_malformed", false); b.field("ignore_malformed", false);
})); }));
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); 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)); assertThat(ignoreMalformed, equalTo(false));
} }
@ -231,11 +247,11 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
{ {
BytesReference arrayedDoc = BytesReference.bytes( 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); SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON);
ParsedDocument document = ignoreMapper.parse(sourceToParse); 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)); MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse));
assertThat(exception.getCause().getMessage(), containsString("Unknown geometry type: bad")); assertThat(exception.getCause().getMessage(), containsString("Unknown geometry type: bad"));
} }
@ -244,7 +260,7 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
XContentFactory.jsonBuilder() XContentFactory.jsonBuilder()
.startObject() .startObject()
.field( .field(
"field", FIELD_NAME,
"POLYGON ((18.9401790919516 -33.9681188869036, 18.9401790919516 -33.9681188869036, 18.9401790919517 " "POLYGON ((18.9401790919516 -33.9681188869036, 18.9401790919516 -33.9681188869036, 18.9401790919517 "
+ "-33.9681188869036, 18.9401790919517 -33.9681188869036, 18.9401790919516 -33.9681188869036))" + "-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); SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON);
ParsedDocument document = ignoreMapper.parse(sourceToParse); 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)); MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse));
assertThat(exception.getCause().getMessage(), containsString("at least three non-collinear points required")); 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("type", getFieldName());
b.field("doc_values", true); b.field("doc_values", true);
})); }));
Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
boolean hasDocValues = ((ShapeFieldMapper) fieldMapper).fieldType().hasDocValues(); boolean hasDocValues = ((AbstractGeometryFieldMapper<?>) fieldMapper).fieldType().hasDocValues();
assertTrue(hasDocValues); assertTrue(hasDocValues);
// explicit false doc_values // explicit false doc_values
@ -278,10 +294,10 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.field("type", getFieldName()); b.field("type", getFieldName());
b.field("doc_values", false); b.field("doc_values", false);
})); }));
fieldMapper = defaultMapper.mappers().getMapper("field"); fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
hasDocValues = ((ShapeFieldMapper) fieldMapper).fieldType().hasDocValues(); hasDocValues = ((AbstractGeometryFieldMapper<?>) fieldMapper).fieldType().hasDocValues();
assertFalse(hasDocValues); assertFalse(hasDocValues);
} }
@ -297,10 +313,10 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
})); }));
Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper(FIELD_NAME); Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper(FIELD_NAME);
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); assertThat(fieldMapper, instanceOf(fieldMapperClass()));
ShapeFieldMapper shapeFieldMapper = (ShapeFieldMapper) fieldMapper; AbstractShapeGeometryFieldType<?> fieldType = fieldType(fieldMapper);
assertThat(shapeFieldMapper.fieldType().orientation(), equalTo(Orientation.CW)); assertThat(fieldType.orientation(), equalTo(Orientation.CW));
} }
public void testSerializeDefaults() throws Exception { public void testSerializeDefaults() throws Exception {
@ -316,7 +332,7 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.field("type", getFieldName()); b.field("type", getFieldName());
b.field("doc_values", docValues); 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("\"orientation\":\"" + Orientation.RIGHT + "\""));
assertTrue(serialized, serialized.contains("\"doc_values\":" + docValues)); assertTrue(serialized, serialized.contains("\"doc_values\":" + docValues));
} }
@ -357,7 +373,7 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
b.startObject("keyword").field("type", "keyword").endObject(); b.startObject("keyword").field("type", "keyword").endObject();
b.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 { 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")); 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) { if (includeDefaults) {
ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true")); ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true"));
return Strings.toString(mapper, params); 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); return toXContentString(mapper, true);
} }

View file

@ -42,6 +42,7 @@ public class GeoHexAggregatorTests extends GeoGridAggregatorTestCase<InternalGeo
@Override @Override
protected List<ValuesSourceType> getSupportedValuesSourceTypes() { protected List<ValuesSourceType> getSupportedValuesSourceTypes() {
// TODO: why is shape here already, it is not supported yet
return List.of(GeoShapeValuesSourceType.instance(), CoreValuesSourceType.GEOPOINT); 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.GeoPoint;
import org.elasticsearch.common.geo.GeometryNormalizer; import org.elasticsearch.common.geo.GeometryNormalizer;
import org.elasticsearch.common.geo.Orientation; import org.elasticsearch.common.geo.Orientation;
import org.elasticsearch.common.geo.SpatialPoint;
import org.elasticsearch.geo.GeometryTestUtils; import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MappedFieldType;
@ -210,10 +211,10 @@ public class GeoShapeCentroidAggregatorTests extends AggregatorTestCase {
InternalGeoCentroid result = searchAndReduce(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType); InternalGeoCentroid result = searchAndReduce(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType);
assertEquals("my_agg", result.getName()); assertEquals("my_agg", result.getName());
GeoPoint centroid = result.centroid(); SpatialPoint centroid = result.centroid();
assertNotNull(centroid); assertNotNull(centroid);
assertEquals(expectedCentroid.getLat(), centroid.getLat(), GEOHASH_TOLERANCE); assertEquals(expectedCentroid.getX(), centroid.getX(), GEOHASH_TOLERANCE);
assertEquals(expectedCentroid.getLon(), centroid.getLon(), GEOHASH_TOLERANCE); assertEquals(expectedCentroid.getY(), centroid.getY(), GEOHASH_TOLERANCE);
assertTrue(AggregationInspectionHelper.hasValue(result)); assertTrue(AggregationInspectionHelper.hasValue(result));
} }
} }

View file

@ -42,8 +42,8 @@ enum GridType {
throws IOException { throws IOException {
final Rectangle r = gridAggregation.toRectangle(key); final Rectangle r = gridAggregation.toRectangle(key);
final InternalGeoCentroid centroid = bucket.getAggregations().get(RestVectorTileAction.CENTROID_AGG_NAME); 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 featureLon = Math.min(Math.max(centroid.centroid().getX(), r.getMinLon()), r.getMaxLon());
final double featureLat = Math.min(Math.max(centroid.centroid().lat(), r.getMinLat()), r.getMaxLat()); final double featureLat = Math.min(Math.max(centroid.centroid().getY(), r.getMinLat()), r.getMaxLat());
return featureFactory.point(featureLon, featureLat); return featureFactory.point(featureLon, featureLat);
} }
}; };