mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-04-24 23:27:25 -04:00
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:
parent
97f0e98c3a
commit
b68d62ba1e
83 changed files with 2148 additions and 1496 deletions
|
@ -11,8 +11,8 @@ package org.elasticsearch.script.expression;
|
|||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.search.DoubleValues;
|
||||
import org.elasticsearch.index.fielddata.IndexFieldData;
|
||||
import org.elasticsearch.index.fielddata.LeafGeoPointFieldData;
|
||||
import org.elasticsearch.index.fielddata.MultiGeoPointValues;
|
||||
import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -28,7 +28,7 @@ final class GeoEmptyValueSource extends FieldDataBasedDoubleValuesSource {
|
|||
@Override
|
||||
public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) {
|
||||
LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf);
|
||||
final MultiGeoPointValues values = leafData.getGeoPointValues();
|
||||
final MultiGeoPointValues values = leafData.getPointValues(); // shade
|
||||
return new DoubleValues() {
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
|
|
|
@ -11,8 +11,8 @@ package org.elasticsearch.script.expression;
|
|||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.search.DoubleValues;
|
||||
import org.elasticsearch.index.fielddata.IndexFieldData;
|
||||
import org.elasticsearch.index.fielddata.LeafGeoPointFieldData;
|
||||
import org.elasticsearch.index.fielddata.MultiGeoPointValues;
|
||||
import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -28,7 +28,7 @@ final class GeoLatitudeValueSource extends FieldDataBasedDoubleValuesSource {
|
|||
@Override
|
||||
public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) {
|
||||
LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf);
|
||||
final MultiGeoPointValues values = leafData.getGeoPointValues();
|
||||
final MultiGeoPointValues values = leafData.getPointValues();
|
||||
return new DoubleValues() {
|
||||
@Override
|
||||
public double doubleValue() throws IOException {
|
||||
|
|
|
@ -11,8 +11,8 @@ package org.elasticsearch.script.expression;
|
|||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.search.DoubleValues;
|
||||
import org.elasticsearch.index.fielddata.IndexFieldData;
|
||||
import org.elasticsearch.index.fielddata.LeafGeoPointFieldData;
|
||||
import org.elasticsearch.index.fielddata.MultiGeoPointValues;
|
||||
import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -28,7 +28,7 @@ final class GeoLongitudeValueSource extends FieldDataBasedDoubleValuesSource {
|
|||
@Override
|
||||
public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) {
|
||||
LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf);
|
||||
final MultiGeoPointValues values = leafData.getGeoPointValues();
|
||||
final MultiGeoPointValues values = leafData.getPointValues();
|
||||
return new DoubleValues() {
|
||||
@Override
|
||||
public double doubleValue() throws IOException {
|
||||
|
@ -53,8 +53,7 @@ final class GeoLongitudeValueSource extends FieldDataBasedDoubleValuesSource {
|
|||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
GeoLongitudeValueSource other = (GeoLongitudeValueSource) obj;
|
||||
if (fieldData.equals(other.fieldData) == false) return false;
|
||||
return true;
|
||||
return fieldData.equals(other.fieldData);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -218,7 +218,7 @@ public class MissingValueIT extends ESIntegTestCase {
|
|||
assertSearchResponse(response);
|
||||
GeoCentroid centroid = response.getAggregations().get("centroid");
|
||||
GeoPoint point = new GeoPoint(1.5, 1.5);
|
||||
assertThat(point.lat(), closeTo(centroid.centroid().lat(), 1E-5));
|
||||
assertThat(point.lon(), closeTo(centroid.centroid().lon(), 1E-5));
|
||||
assertThat(point.getY(), closeTo(centroid.centroid().getY(), 1E-5));
|
||||
assertThat(point.getX(), closeTo(centroid.centroid().getX(), 1E-5));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,8 +45,7 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
|
|||
assertThat(response.getHits().getTotalHits().value, equalTo(0L));
|
||||
assertThat(geoCentroid, notNullValue());
|
||||
assertThat(geoCentroid.getName(), equalTo(aggName));
|
||||
GeoPoint centroid = geoCentroid.centroid();
|
||||
assertThat(centroid, equalTo(null));
|
||||
assertThat(geoCentroid.centroid(), equalTo(null));
|
||||
assertEquals(0, geoCentroid.count());
|
||||
}
|
||||
|
||||
|
@ -59,8 +58,7 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
|
|||
GeoCentroid geoCentroid = response.getAggregations().get(aggName);
|
||||
assertThat(geoCentroid, notNullValue());
|
||||
assertThat(geoCentroid.getName(), equalTo(aggName));
|
||||
GeoPoint centroid = geoCentroid.centroid();
|
||||
assertThat(centroid, equalTo(null));
|
||||
assertThat(geoCentroid.centroid(), equalTo(null));
|
||||
assertEquals(0, geoCentroid.count());
|
||||
}
|
||||
|
||||
|
@ -73,9 +71,7 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
|
|||
GeoCentroid geoCentroid = response.getAggregations().get(aggName);
|
||||
assertThat(geoCentroid, notNullValue());
|
||||
assertThat(geoCentroid.getName(), equalTo(aggName));
|
||||
GeoPoint centroid = geoCentroid.centroid();
|
||||
assertThat(centroid.lat(), closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE));
|
||||
assertThat(centroid.lon(), closeTo(singleCentroid.lon(), GEOHASH_TOLERANCE));
|
||||
assertSameCentroid(geoCentroid.centroid(), singleCentroid);
|
||||
assertEquals(numDocs, geoCentroid.count());
|
||||
}
|
||||
|
||||
|
@ -89,13 +85,11 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
|
|||
GeoCentroid geoCentroid = response.getAggregations().get(aggName);
|
||||
assertThat(geoCentroid, notNullValue());
|
||||
assertThat(geoCentroid.getName(), equalTo(aggName));
|
||||
GeoPoint centroid = geoCentroid.centroid();
|
||||
assertThat(centroid.lat(), closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE));
|
||||
assertThat(centroid.lon(), closeTo(singleCentroid.lon(), GEOHASH_TOLERANCE));
|
||||
assertSameCentroid(geoCentroid.centroid(), singleCentroid);
|
||||
assertEquals(numDocs, geoCentroid.count());
|
||||
}
|
||||
|
||||
public void testSingleValueFieldGetProperty() throws Exception {
|
||||
public void testSingleValueFieldGetProperty() {
|
||||
SearchResponse response = client().prepareSearch(IDX_NAME)
|
||||
.setQuery(matchAllQuery())
|
||||
.addAggregation(global("global").subAggregation(geoCentroid(aggName).field(SINGLE_VALUED_FIELD_NAME)))
|
||||
|
@ -113,9 +107,7 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
|
|||
assertThat(geoCentroid, notNullValue());
|
||||
assertThat(geoCentroid.getName(), equalTo(aggName));
|
||||
assertThat((GeoCentroid) ((InternalAggregation) global).getProperty(aggName), sameInstance(geoCentroid));
|
||||
GeoPoint centroid = geoCentroid.centroid();
|
||||
assertThat(centroid.lat(), closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE));
|
||||
assertThat(centroid.lon(), closeTo(singleCentroid.lon(), GEOHASH_TOLERANCE));
|
||||
assertSameCentroid(geoCentroid.centroid(), singleCentroid);
|
||||
assertThat(
|
||||
((GeoPoint) ((InternalAggregation) global).getProperty(aggName + ".value")).lat(),
|
||||
closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE)
|
||||
|
@ -139,13 +131,11 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
|
|||
GeoCentroid geoCentroid = searchResponse.getAggregations().get(aggName);
|
||||
assertThat(geoCentroid, notNullValue());
|
||||
assertThat(geoCentroid.getName(), equalTo(aggName));
|
||||
GeoPoint centroid = geoCentroid.centroid();
|
||||
assertThat(centroid.lat(), closeTo(multiCentroid.lat(), GEOHASH_TOLERANCE));
|
||||
assertThat(centroid.lon(), closeTo(multiCentroid.lon(), GEOHASH_TOLERANCE));
|
||||
assertSameCentroid(geoCentroid.centroid(), multiCentroid);
|
||||
assertEquals(2 * numDocs, geoCentroid.count());
|
||||
}
|
||||
|
||||
public void testSingleValueFieldAsSubAggToGeohashGrid() throws Exception {
|
||||
public void testSingleValueFieldAsSubAggToGeohashGrid() {
|
||||
SearchResponse response = client().prepareSearch(HIGH_CARD_IDX_NAME)
|
||||
.addAggregation(
|
||||
geohashGrid("geoGrid").field(SINGLE_VALUED_FIELD_NAME).subAggregation(geoCentroid(aggName).field(SINGLE_VALUED_FIELD_NAME))
|
||||
|
@ -161,16 +151,7 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
|
|||
String geohash = cell.getKeyAsString();
|
||||
GeoPoint expectedCentroid = expectedCentroidsForGeoHash.get(geohash);
|
||||
GeoCentroid centroidAgg = cell.getAggregations().get(aggName);
|
||||
assertThat(
|
||||
"Geohash " + geohash + " has wrong centroid latitude ",
|
||||
expectedCentroid.lat(),
|
||||
closeTo(centroidAgg.centroid().lat(), GEOHASH_TOLERANCE)
|
||||
);
|
||||
assertThat(
|
||||
"Geohash " + geohash + " has wrong centroid longitude",
|
||||
expectedCentroid.lon(),
|
||||
closeTo(centroidAgg.centroid().lon(), GEOHASH_TOLERANCE)
|
||||
);
|
||||
assertSameCentroid(centroidAgg.centroid(), expectedCentroid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ package org.elasticsearch.search.geo;
|
|||
import org.apache.lucene.geo.GeoEncodingUtils;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.document.DocumentField;
|
||||
import org.elasticsearch.common.geo.GeoBoundingBox;
|
||||
import org.elasticsearch.common.geo.BoundingBox;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.geo.GeometryTestUtils;
|
||||
import org.elasticsearch.index.fielddata.ScriptDocValues;
|
||||
|
@ -66,52 +66,52 @@ public class GeoPointScriptDocValuesIT extends ESSingleNodeTestCase {
|
|||
|
||||
private double scriptHeight(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
ScriptDocValues.Geometry geometry = assertGeometry(doc);
|
||||
if (geometry.size() == 0) {
|
||||
return Double.NaN;
|
||||
} else {
|
||||
GeoBoundingBox boundingBox = geometry.getBoundingBox();
|
||||
BoundingBox<GeoPoint> boundingBox = geometry.getBoundingBox();
|
||||
return boundingBox.topLeft().lat() - boundingBox.bottomRight().lat();
|
||||
}
|
||||
}
|
||||
|
||||
private double scriptWidth(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
ScriptDocValues.Geometry geometry = assertGeometry(doc);
|
||||
if (geometry.size() == 0) {
|
||||
return Double.NaN;
|
||||
} else {
|
||||
GeoBoundingBox boundingBox = geometry.getBoundingBox();
|
||||
BoundingBox<GeoPoint> boundingBox = geometry.getBoundingBox();
|
||||
return boundingBox.bottomRight().lon() - boundingBox.topLeft().lon();
|
||||
}
|
||||
}
|
||||
|
||||
private double scriptLat(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
ScriptDocValues.Geometry geometry = assertGeometry(doc);
|
||||
return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lat();
|
||||
}
|
||||
|
||||
private double scriptLon(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
ScriptDocValues.Geometry geometry = assertGeometry(doc);
|
||||
return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lon();
|
||||
}
|
||||
|
||||
private double scriptLabelLat(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
ScriptDocValues.Geometry geometry = assertGeometry(doc);
|
||||
return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lat();
|
||||
}
|
||||
|
||||
private double scriptLabelLon(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
ScriptDocValues.Geometry geometry = assertGeometry(doc);
|
||||
return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lon();
|
||||
}
|
||||
|
||||
private ScriptDocValues.Geometry<?> assertGeometry(Map<?, ?> doc) {
|
||||
ScriptDocValues.Geometry<?> geometry = (ScriptDocValues.Geometry<?>) doc.get("location");
|
||||
private ScriptDocValues.Geometry assertGeometry(Map<?, ?> doc) {
|
||||
ScriptDocValues.Geometry geometry = (ScriptDocValues.Geometry) doc.get("location");
|
||||
if (geometry.size() == 0) {
|
||||
assertThat(geometry.getBoundingBox(), Matchers.nullValue());
|
||||
assertThat(geometry.getCentroid(), Matchers.nullValue());
|
||||
|
|
|
@ -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() + ")";
|
||||
}
|
||||
}
|
|
@ -10,108 +10,58 @@ package org.elasticsearch.common.geo;
|
|||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.geometry.Geometry;
|
||||
import org.elasticsearch.geometry.Rectangle;
|
||||
import org.elasticsearch.geometry.ShapeType;
|
||||
import org.elasticsearch.geometry.utils.StandardValidator;
|
||||
import org.elasticsearch.geometry.utils.WellKnownText;
|
||||
import org.elasticsearch.xcontent.ParseField;
|
||||
import org.elasticsearch.xcontent.ToXContentFragment;
|
||||
import org.elasticsearch.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A class representing a Geo-Bounding-Box for use by Geo queries and aggregations
|
||||
* that deal with extents/rectangles representing rectangular areas of interest.
|
||||
*/
|
||||
public class GeoBoundingBox implements ToXContentFragment, Writeable {
|
||||
static final ParseField TOP_RIGHT_FIELD = new ParseField("top_right");
|
||||
static final ParseField BOTTOM_LEFT_FIELD = new ParseField("bottom_left");
|
||||
static final ParseField TOP_FIELD = new ParseField("top");
|
||||
static final ParseField BOTTOM_FIELD = new ParseField("bottom");
|
||||
static final ParseField LEFT_FIELD = new ParseField("left");
|
||||
static final ParseField RIGHT_FIELD = new ParseField("right");
|
||||
static final ParseField WKT_FIELD = new ParseField("wkt");
|
||||
public static final ParseField BOUNDS_FIELD = new ParseField("bounds");
|
||||
public class GeoBoundingBox extends BoundingBox<GeoPoint> {
|
||||
public static final ParseField LAT_FIELD = new ParseField("lat");
|
||||
public static final ParseField LON_FIELD = new ParseField("lon");
|
||||
public static final ParseField TOP_LEFT_FIELD = new ParseField("top_left");
|
||||
public static final ParseField BOTTOM_RIGHT_FIELD = new ParseField("bottom_right");
|
||||
|
||||
private final GeoPoint topLeft;
|
||||
private final GeoPoint bottomRight;
|
||||
|
||||
public GeoBoundingBox(GeoPoint topLeft, GeoPoint bottomRight) {
|
||||
this.topLeft = topLeft;
|
||||
this.bottomRight = bottomRight;
|
||||
super(topLeft, bottomRight);
|
||||
}
|
||||
|
||||
public GeoBoundingBox(StreamInput input) throws IOException {
|
||||
this.topLeft = input.readGeoPoint();
|
||||
this.bottomRight = input.readGeoPoint();
|
||||
}
|
||||
|
||||
public boolean isUnbounded() {
|
||||
return Double.isNaN(topLeft.lon())
|
||||
|| Double.isNaN(topLeft.lat())
|
||||
|| Double.isNaN(bottomRight.lon())
|
||||
|| Double.isNaN(bottomRight.lat());
|
||||
super(input.readGeoPoint(), input.readGeoPoint());
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPoint topLeft() {
|
||||
return topLeft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPoint bottomRight() {
|
||||
return bottomRight;
|
||||
}
|
||||
|
||||
public double top() {
|
||||
return topLeft.lat();
|
||||
}
|
||||
|
||||
public double bottom() {
|
||||
return bottomRight.lat();
|
||||
}
|
||||
|
||||
public double left() {
|
||||
return topLeft.lon();
|
||||
}
|
||||
|
||||
public double right() {
|
||||
return bottomRight.lon();
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
toXContentFragment(builder, true);
|
||||
public XContentBuilder toXContentFragment(XContentBuilder builder) throws IOException {
|
||||
builder.startObject(TOP_LEFT_FIELD.getPreferredName());
|
||||
builder.field(LAT_FIELD.getPreferredName(), topLeft.getY());
|
||||
builder.field(LON_FIELD.getPreferredName(), topLeft.getX());
|
||||
builder.endObject();
|
||||
builder.startObject(BOTTOM_RIGHT_FIELD.getPreferredName());
|
||||
builder.field(LAT_FIELD.getPreferredName(), bottomRight.getY());
|
||||
builder.field(LON_FIELD.getPreferredName(), bottomRight.getX());
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
public XContentBuilder toXContentFragment(XContentBuilder builder, boolean buildLatLonFields) throws IOException {
|
||||
if (buildLatLonFields) {
|
||||
builder.startObject(TOP_LEFT_FIELD.getPreferredName());
|
||||
builder.field(LAT_FIELD.getPreferredName(), topLeft.lat());
|
||||
builder.field(LON_FIELD.getPreferredName(), topLeft.lon());
|
||||
builder.endObject();
|
||||
} else {
|
||||
builder.array(TOP_LEFT_FIELD.getPreferredName(), topLeft.lon(), topLeft.lat());
|
||||
}
|
||||
if (buildLatLonFields) {
|
||||
builder.startObject(BOTTOM_RIGHT_FIELD.getPreferredName());
|
||||
builder.field(LAT_FIELD.getPreferredName(), bottomRight.lat());
|
||||
builder.field(LON_FIELD.getPreferredName(), bottomRight.lon());
|
||||
builder.endObject();
|
||||
} else {
|
||||
builder.array(BOTTOM_RIGHT_FIELD.getPreferredName(), bottomRight.lon(), bottomRight.lat());
|
||||
}
|
||||
/**
|
||||
* There exists a special case where we use an array format for building the XContent for the bounds.
|
||||
* Specifically the GeoBoundingBoxQueryBuilder makes use of this. All other cases build a keyed map.
|
||||
*/
|
||||
public XContentBuilder toXContentFragmentWithArray(XContentBuilder builder) throws IOException {
|
||||
builder.array(TOP_LEFT_FIELD.getPreferredName(), topLeft.getX(), topLeft.getY());
|
||||
builder.array(BOTTOM_RIGHT_FIELD.getPreferredName(), bottomRight.getX(), bottomRight.getY());
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
@ -141,106 +91,36 @@ public class GeoBoundingBox implements ToXContentFragment, Writeable {
|
|||
out.writeGeoPoint(bottomRight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
GeoBoundingBox that = (GeoBoundingBox) o;
|
||||
return topLeft.equals(that.topLeft) && bottomRight.equals(that.bottomRight);
|
||||
protected static class GeoBoundsParser extends BoundsParser<GeoBoundingBox> {
|
||||
GeoBoundsParser(XContentParser parser) {
|
||||
super(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(topLeft, bottomRight);
|
||||
protected GeoBoundingBox createWithEnvelope() {
|
||||
GeoPoint topLeft = new GeoPoint(envelope.getMaxLat(), envelope.getMinLon());
|
||||
GeoPoint bottomRight = new GeoPoint(envelope.getMinLat(), envelope.getMaxLon());
|
||||
return new GeoBoundingBox(topLeft, bottomRight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BBOX (" + topLeft.lon() + ", " + bottomRight.lon() + ", " + topLeft.lat() + ", " + bottomRight.lat() + ")";
|
||||
protected GeoBoundingBox createWithBounds() {
|
||||
GeoPoint topLeft = new GeoPoint(top, left);
|
||||
GeoPoint bottomRight = new GeoPoint(bottom, right);
|
||||
return new GeoBoundingBox(topLeft, bottomRight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SpatialPoint parsePointWith(XContentParser parser, GeoUtils.EffectivePoint effectivePoint) throws IOException {
|
||||
return GeoUtils.parseGeoPoint(parser, false, effectivePoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the bounding box and returns bottom, top, left, right coordinates
|
||||
*/
|
||||
public static GeoBoundingBox parseBoundingBox(XContentParser parser) throws IOException, ElasticsearchParseException {
|
||||
XContentParser.Token token = parser.currentToken();
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new ElasticsearchParseException("failed to parse bounding box. Expected start object but found [{}]", token);
|
||||
}
|
||||
|
||||
double top = Double.NaN;
|
||||
double bottom = Double.NaN;
|
||||
double left = Double.NaN;
|
||||
double right = Double.NaN;
|
||||
|
||||
String currentFieldName;
|
||||
Rectangle envelope = null;
|
||||
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
parser.nextToken();
|
||||
if (WKT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||
try {
|
||||
Geometry geometry = WellKnownText.fromWKT(StandardValidator.instance(true), true, parser.text());
|
||||
if (ShapeType.ENVELOPE.equals(geometry.type()) == false) {
|
||||
throw new ElasticsearchParseException(
|
||||
"failed to parse WKT bounding box. [" + geometry.type() + "] found. expected [" + ShapeType.ENVELOPE + "]"
|
||||
);
|
||||
}
|
||||
envelope = (Rectangle) geometry;
|
||||
} catch (ParseException | IllegalArgumentException e) {
|
||||
throw new ElasticsearchParseException("failed to parse WKT bounding box", e);
|
||||
}
|
||||
} else if (TOP_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||
top = parser.doubleValue();
|
||||
} else if (BOTTOM_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||
bottom = parser.doubleValue();
|
||||
} else if (LEFT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||
left = parser.doubleValue();
|
||||
} else if (RIGHT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||
right = parser.doubleValue();
|
||||
} else {
|
||||
if (TOP_LEFT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||
GeoPoint sparse = GeoUtils.parseGeoPoint(parser, false, GeoUtils.EffectivePoint.TOP_LEFT);
|
||||
top = sparse.getLat();
|
||||
left = sparse.getLon();
|
||||
} else if (BOTTOM_RIGHT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||
GeoPoint sparse = GeoUtils.parseGeoPoint(parser, false, GeoUtils.EffectivePoint.BOTTOM_RIGHT);
|
||||
bottom = sparse.getLat();
|
||||
right = sparse.getLon();
|
||||
} else if (TOP_RIGHT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||
GeoPoint sparse = GeoUtils.parseGeoPoint(parser, false, GeoUtils.EffectivePoint.TOP_RIGHT);
|
||||
top = sparse.getLat();
|
||||
right = sparse.getLon();
|
||||
} else if (BOTTOM_LEFT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) {
|
||||
GeoPoint sparse = GeoUtils.parseGeoPoint(parser, false, GeoUtils.EffectivePoint.BOTTOM_LEFT);
|
||||
bottom = sparse.getLat();
|
||||
left = sparse.getLon();
|
||||
} else {
|
||||
throw new ElasticsearchParseException("failed to parse bounding box. unexpected field [{}]", currentFieldName);
|
||||
GeoBoundsParser bounds = new GeoBoundsParser(parser);
|
||||
return bounds.parseBoundingBox();
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import java.io.IOException;
|
|||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
public class GeoPoint implements ToXContentFragment {
|
||||
public class GeoPoint implements SpatialPoint, ToXContentFragment {
|
||||
|
||||
protected double lat;
|
||||
protected double lon;
|
||||
|
@ -52,8 +52,8 @@ public class GeoPoint implements ToXContentFragment {
|
|||
this.lon = lon;
|
||||
}
|
||||
|
||||
public GeoPoint(GeoPoint template) {
|
||||
this(template.getLat(), template.getLon());
|
||||
public GeoPoint(SpatialPoint template) {
|
||||
this(template.getY(), template.getX());
|
||||
}
|
||||
|
||||
public GeoPoint reset(double lat, double lon) {
|
||||
|
@ -189,6 +189,16 @@ public class GeoPoint implements ToXContentFragment {
|
|||
return this.lon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getX() {
|
||||
return this.lon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getY() {
|
||||
return this.lat;
|
||||
}
|
||||
|
||||
public String geohash() {
|
||||
return Geohash.stringEncode(lon, lat);
|
||||
}
|
||||
|
|
|
@ -537,7 +537,7 @@ public class GeoUtils {
|
|||
@Override
|
||||
public double doubleValue() throws IOException {
|
||||
final GeoPoint from = fromPoints[0];
|
||||
final GeoPoint to = singleValues.geoPointValue();
|
||||
final GeoPoint to = singleValues.pointValue();
|
||||
return distance.calculate(from.lat(), from.lon(), to.lat(), to.lon(), unit);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -14,6 +14,7 @@ import org.apache.lucene.index.NumericDocValues;
|
|||
import org.apache.lucene.index.SortedNumericDocValues;
|
||||
import org.apache.lucene.index.SortedSetDocValues;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.elasticsearch.common.geo.SpatialPoint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -86,7 +87,7 @@ public enum FieldData {
|
|||
* Returns a {@link DocValueBits} representing all documents from <code>pointValues</code> that have
|
||||
* a value.
|
||||
*/
|
||||
public static DocValueBits docsWithValue(final MultiGeoPointValues pointValues) {
|
||||
public static DocValueBits docsWithValue(final MultiPointValues<? extends SpatialPoint> pointValues) {
|
||||
return new DocValueBits() {
|
||||
@Override
|
||||
public boolean advanceExact(int doc) throws IOException {
|
||||
|
@ -217,7 +218,7 @@ public enum FieldData {
|
|||
* if the wrapped {@link SortedNumericDocValues} is a singleton.
|
||||
*/
|
||||
public static GeoPointValues unwrapSingleton(MultiGeoPointValues values) {
|
||||
return values.getGeoPointValues();
|
||||
return values.getPointValues();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,7 +12,7 @@ import org.apache.lucene.index.LeafReaderContext;
|
|||
import org.apache.lucene.index.SortedNumericDocValues;
|
||||
import org.apache.lucene.search.SortField;
|
||||
import org.elasticsearch.common.util.BigArrays;
|
||||
import org.elasticsearch.index.fielddata.plain.AbstractLeafGeoPointFieldData;
|
||||
import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData;
|
||||
import org.elasticsearch.indices.breaker.CircuitBreakerService;
|
||||
import org.elasticsearch.script.GeoPointFieldScript;
|
||||
import org.elasticsearch.script.field.ToScriptFieldFactory;
|
||||
|
@ -23,7 +23,7 @@ import org.elasticsearch.search.aggregations.support.ValuesSourceType;
|
|||
import org.elasticsearch.search.sort.BucketedSort;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
|
||||
public class GeoPointScriptFieldData implements IndexGeoPointFieldData {
|
||||
public final class GeoPointScriptFieldData implements IndexGeoPointFieldData {
|
||||
public static class Builder implements IndexFieldData.Builder {
|
||||
private final String name;
|
||||
private final GeoPointFieldScript.LeafFactory leafFactory;
|
||||
|
@ -89,9 +89,9 @@ public class GeoPointScriptFieldData implements IndexGeoPointFieldData {
|
|||
}
|
||||
|
||||
@Override
|
||||
public LeafGeoPointFieldData load(LeafReaderContext context) {
|
||||
public LeafPointFieldData<MultiGeoPointValues> load(LeafReaderContext context) {
|
||||
GeoPointFieldScript script = leafFactory.newInstance(context);
|
||||
return new AbstractLeafGeoPointFieldData(toScriptFieldFactory) {
|
||||
return new LeafGeoPointFieldData(toScriptFieldFactory) {
|
||||
@Override
|
||||
public SortedNumericDocValues getSortedNumericDocValues() {
|
||||
return new GeoPointScriptDocValues(script);
|
||||
|
@ -110,7 +110,7 @@ public class GeoPointScriptFieldData implements IndexGeoPointFieldData {
|
|||
}
|
||||
|
||||
@Override
|
||||
public LeafGeoPointFieldData loadDirect(LeafReaderContext context) {
|
||||
public LeafPointFieldData<MultiGeoPointValues> loadDirect(LeafReaderContext context) {
|
||||
return load(context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,28 +16,20 @@ import java.io.IOException;
|
|||
/**
|
||||
* Per-document geo-point values.
|
||||
*/
|
||||
public final class GeoPointValues {
|
||||
public final class GeoPointValues extends PointValues<GeoPoint> {
|
||||
|
||||
private final GeoPoint point = new GeoPoint();
|
||||
private final NumericDocValues values;
|
||||
|
||||
GeoPointValues(NumericDocValues values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance this instance to the given document id
|
||||
* @return true if there is a value for this document
|
||||
*/
|
||||
public boolean advanceExact(int doc) throws IOException {
|
||||
return values.advanceExact(doc);
|
||||
super(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link GeoPoint} associated with the current document.
|
||||
* The returned {@link GeoPoint} might be reused across calls.
|
||||
*/
|
||||
public GeoPoint geoPointValue() throws IOException {
|
||||
@Override
|
||||
public GeoPoint pointValue() throws IOException {
|
||||
return point.resetFromEncoded(values.longValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,4 +11,4 @@ package org.elasticsearch.index.fielddata;
|
|||
/**
|
||||
* Specialization of {@link IndexFieldData} for geo points.
|
||||
*/
|
||||
public interface IndexGeoPointFieldData extends IndexFieldData<LeafGeoPointFieldData> {}
|
||||
public interface IndexGeoPointFieldData extends IndexPointFieldData<MultiGeoPointValues> {}
|
||||
|
|
|
@ -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>> {}
|
|
@ -8,21 +8,20 @@
|
|||
package org.elasticsearch.index.fielddata;
|
||||
|
||||
import org.apache.lucene.index.SortedNumericDocValues;
|
||||
import org.elasticsearch.common.geo.SpatialPoint;
|
||||
|
||||
/**
|
||||
* {@link LeafFieldData} specialization for geo points.
|
||||
* {@link LeafFieldData} specialization for geo points and points.
|
||||
*/
|
||||
public abstract class LeafGeoPointFieldData implements LeafFieldData {
|
||||
public abstract class LeafPointFieldData<T extends MultiPointValues<? extends SpatialPoint>> implements LeafFieldData {
|
||||
|
||||
/**
|
||||
* Return geo-point values.
|
||||
* Return geo-point or point values.
|
||||
*/
|
||||
public final MultiGeoPointValues getGeoPointValues() {
|
||||
return new MultiGeoPointValues(getSortedNumericDocValues());
|
||||
}
|
||||
public abstract T getPointValues();
|
||||
|
||||
/**
|
||||
* Return the internal representation of geo_point doc values as a {@link SortedNumericDocValues}.
|
||||
* Return the internal representation of geo_point or point doc values as a {@link SortedNumericDocValues}.
|
||||
* A point is encoded as a long that can be decoded by using
|
||||
* {@link org.elasticsearch.common.geo.GeoPoint#resetFromEncoded(long)}
|
||||
*/
|
|
@ -18,62 +18,35 @@ import java.io.IOException;
|
|||
* A stateful lightweight per document set of {@link GeoPoint} values.
|
||||
* To iterate over values in a document use the following pattern:
|
||||
* <pre>
|
||||
* GeoPointValues values = ..;
|
||||
* values.setDocId(docId);
|
||||
* final int numValues = values.count();
|
||||
* MultiGeoPointValues values = ..;
|
||||
* values.advanceExact(docId);
|
||||
* final int numValues = values.docValueCount();
|
||||
* for (int i = 0; i < numValues; i++) {
|
||||
* GeoPoint value = values.valueAt(i);
|
||||
* GeoPoint value = values.nextValue();
|
||||
* // process value
|
||||
* }
|
||||
* </pre>
|
||||
* The set of values associated with a document might contain duplicates and
|
||||
* comes in a non-specified order.
|
||||
*/
|
||||
public final class MultiGeoPointValues {
|
||||
|
||||
public class MultiGeoPointValues extends MultiPointValues<GeoPoint> {
|
||||
private final GeoPoint point = new GeoPoint();
|
||||
private final SortedNumericDocValues numericValues;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MultiGeoPointValues} instance
|
||||
*/
|
||||
public MultiGeoPointValues(SortedNumericDocValues numericValues) {
|
||||
this.numericValues = numericValues;
|
||||
super(numericValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance this instance to the given document id
|
||||
* @return true if there is a value for this document
|
||||
*/
|
||||
public boolean advanceExact(int doc) throws IOException {
|
||||
return numericValues.advanceExact(doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of geo points the current document has.
|
||||
*/
|
||||
public int docValueCount() {
|
||||
return numericValues.docValueCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next value associated with the current document. This must not be
|
||||
* called more than {@link #docValueCount()} times.
|
||||
*
|
||||
* Note: the returned {@link GeoPoint} might be shared across invocations.
|
||||
*
|
||||
* @return the next value for the current docID set to {@link #advanceExact(int)}.
|
||||
*/
|
||||
@Override
|
||||
public GeoPoint nextValue() throws IOException {
|
||||
return point.resetFromEncoded(numericValues.nextValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single-valued view of the {@link MultiGeoPointValues} if possible, otherwise null.
|
||||
* Returns a single-valued view of the {@link MultiPointValues} if possible, otherwise null.
|
||||
*/
|
||||
GeoPointValues getGeoPointValues() {
|
||||
@Override
|
||||
protected GeoPointValues getPointValues() {
|
||||
final NumericDocValues singleton = DocValues.unwrapSingleton(numericValues);
|
||||
return singleton != null ? new GeoPointValues(singleton) : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -11,9 +11,11 @@ package org.elasticsearch.index.fielddata;
|
|||
import org.apache.lucene.util.ArrayUtil;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.BytesRefBuilder;
|
||||
import org.elasticsearch.common.geo.BoundingBox;
|
||||
import org.elasticsearch.common.geo.GeoBoundingBox;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.GeoUtils;
|
||||
import org.elasticsearch.common.geo.SpatialPoint;
|
||||
import org.elasticsearch.geometry.utils.Geohash;
|
||||
import org.elasticsearch.script.field.DocValuesScriptFieldFactory;
|
||||
|
||||
|
@ -220,9 +222,9 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
|
|||
}
|
||||
}
|
||||
|
||||
public abstract static class Geometry<T> extends ScriptDocValues<T> {
|
||||
public abstract static class BaseGeometry<T extends SpatialPoint, V> extends ScriptDocValues<V> {
|
||||
|
||||
public Geometry(Supplier<T> supplier) {
|
||||
public BaseGeometry(Supplier<V> supplier) {
|
||||
super(supplier);
|
||||
}
|
||||
|
||||
|
@ -230,35 +232,52 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
|
|||
public abstract int getDimensionalType();
|
||||
|
||||
/** Returns the bounding box of this geometry */
|
||||
public abstract GeoBoundingBox getBoundingBox();
|
||||
public abstract BoundingBox<T> getBoundingBox();
|
||||
|
||||
/** Returns the suggested label position */
|
||||
public abstract GeoPoint getLabelPosition();
|
||||
public abstract T getLabelPosition();
|
||||
|
||||
/** Returns the centroid of this geometry */
|
||||
public abstract GeoPoint getCentroid();
|
||||
public abstract T getCentroid();
|
||||
}
|
||||
|
||||
public interface Geometry {
|
||||
/** Returns the dimensional type of this geometry */
|
||||
int getDimensionalType();
|
||||
|
||||
/** Returns the bounding box of this geometry */
|
||||
GeoBoundingBox getBoundingBox();
|
||||
|
||||
/** Returns the suggested label position */
|
||||
GeoPoint getLabelPosition();
|
||||
|
||||
/** Returns the centroid of this geometry */
|
||||
GeoPoint getCentroid();
|
||||
|
||||
/** returns the size of the geometry */
|
||||
int size();
|
||||
|
||||
/** Returns the width of the bounding box diagonal in the spherical Mercator projection (meters) */
|
||||
public abstract double getMercatorWidth();
|
||||
double getMercatorWidth();
|
||||
|
||||
/** Returns the height of the bounding box diagonal in the spherical Mercator projection (meters) */
|
||||
public abstract double getMercatorHeight();
|
||||
double getMercatorHeight();
|
||||
}
|
||||
|
||||
public interface GeometrySupplier<T> extends Supplier<T> {
|
||||
public interface GeometrySupplier<T extends SpatialPoint, V> extends Supplier<V> {
|
||||
|
||||
GeoPoint getInternalCentroid();
|
||||
T getInternalCentroid();
|
||||
|
||||
GeoBoundingBox getInternalBoundingBox();
|
||||
BoundingBox<T> getInternalBoundingBox();
|
||||
|
||||
GeoPoint getInternalLabelPosition();
|
||||
T getInternalLabelPosition();
|
||||
}
|
||||
|
||||
public static class GeoPoints extends Geometry<GeoPoint> {
|
||||
public static class GeoPoints extends BaseGeometry<GeoPoint, GeoPoint> implements Geometry {
|
||||
|
||||
private final GeometrySupplier<GeoPoint> geometrySupplier;
|
||||
private final GeometrySupplier<GeoPoint, GeoPoint> geometrySupplier;
|
||||
|
||||
public GeoPoints(GeometrySupplier<GeoPoint> supplier) {
|
||||
public GeoPoints(GeometrySupplier<GeoPoint, GeoPoint> supplier) {
|
||||
super(supplier);
|
||||
geometrySupplier = supplier;
|
||||
}
|
||||
|
@ -366,7 +385,7 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
|
|||
|
||||
@Override
|
||||
public GeoBoundingBox getBoundingBox() {
|
||||
return size() == 0 ? null : geometrySupplier.getInternalBoundingBox();
|
||||
return size() == 0 ? null : (GeoBoundingBox) geometrySupplier.getInternalBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ import java.io.IOException;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
final class LatLonPointDVLeafFieldData extends AbstractLeafGeoPointFieldData {
|
||||
final class LatLonPointDVLeafFieldData extends LeafGeoPointFieldData {
|
||||
private final LeafReader reader;
|
||||
private final String fieldName;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,27 +8,32 @@
|
|||
package org.elasticsearch.index.fielddata.plain;
|
||||
|
||||
import org.elasticsearch.index.fielddata.FieldData;
|
||||
import org.elasticsearch.index.fielddata.LeafGeoPointFieldData;
|
||||
import org.elasticsearch.index.fielddata.LeafPointFieldData;
|
||||
import org.elasticsearch.index.fielddata.MultiGeoPointValues;
|
||||
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
|
||||
import org.elasticsearch.script.field.DocValuesScriptFieldFactory;
|
||||
import org.elasticsearch.script.field.ToScriptFieldFactory;
|
||||
|
||||
public abstract class AbstractLeafGeoPointFieldData extends LeafGeoPointFieldData {
|
||||
public abstract class LeafGeoPointFieldData extends LeafPointFieldData<MultiGeoPointValues> {
|
||||
|
||||
protected final ToScriptFieldFactory<MultiGeoPointValues> toScriptFieldFactory;
|
||||
|
||||
public AbstractLeafGeoPointFieldData(ToScriptFieldFactory<MultiGeoPointValues> toScriptFieldFactory) {
|
||||
public LeafGeoPointFieldData(ToScriptFieldFactory<MultiGeoPointValues> toScriptFieldFactory) {
|
||||
this.toScriptFieldFactory = toScriptFieldFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MultiGeoPointValues getPointValues() {
|
||||
return new MultiGeoPointValues(getSortedNumericDocValues());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SortedBinaryDocValues getBytesValues() {
|
||||
return FieldData.toString(getGeoPointValues());
|
||||
return FieldData.toString(getPointValues());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocValuesScriptFieldFactory getScriptFieldFactory(String name) {
|
||||
return toScriptFieldFactory.getScriptFieldFactory(getGeoPointValues(), name);
|
||||
return toScriptFieldFactory.getScriptFieldFactory(getPointValues(), name);
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ import org.elasticsearch.geometry.Point;
|
|||
import org.elasticsearch.index.fielddata.FieldDataContext;
|
||||
import org.elasticsearch.index.fielddata.IndexFieldData;
|
||||
import org.elasticsearch.index.fielddata.SourceValueFetcherMultiGeoPointIndexFieldData;
|
||||
import org.elasticsearch.index.fielddata.plain.AbstractLatLonPointIndexFieldData;
|
||||
import org.elasticsearch.index.fielddata.plain.LatLonPointIndexFieldData;
|
||||
import org.elasticsearch.index.query.SearchExecutionContext;
|
||||
import org.elasticsearch.script.GeoPointFieldScript;
|
||||
import org.elasticsearch.script.Script;
|
||||
|
@ -378,7 +378,7 @@ public class GeoPointFieldMapper extends AbstractPointGeometryFieldMapper<GeoPoi
|
|||
}
|
||||
|
||||
if ((operation == FielddataOperation.SEARCH || operation == FielddataOperation.SCRIPT) && hasDocValues()) {
|
||||
return new AbstractLatLonPointIndexFieldData.Builder(name(), CoreValuesSourceType.GEOPOINT, GeoPointDocValuesField::new);
|
||||
return new LatLonPointIndexFieldData.Builder(name(), CoreValuesSourceType.GEOPOINT, GeoPointDocValuesField::new);
|
||||
}
|
||||
|
||||
if (operation == FielddataOperation.SCRIPT) {
|
||||
|
|
|
@ -307,7 +307,7 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
|
|||
builder.startObject(NAME);
|
||||
|
||||
builder.startObject(fieldName);
|
||||
geoBoundingBox.toXContentFragment(builder, false);
|
||||
geoBoundingBox.toXContentFragmentWithArray(builder);
|
||||
builder.endObject();
|
||||
builder.field(VALIDATION_METHOD_FIELD.getPreferredName(), validationMethod);
|
||||
builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
|
||||
|
|
|
@ -382,7 +382,7 @@ public abstract class DecayFunctionBuilder<DFB extends DecayFunctionBuilder<DFB>
|
|||
|
||||
@Override
|
||||
protected NumericDoubleValues distance(LeafReaderContext context) {
|
||||
final MultiGeoPointValues geoPointValues = fieldData.load(context).getGeoPointValues();
|
||||
final MultiGeoPointValues geoPointValues = fieldData.load(context).getPointValues();
|
||||
return FieldData.replaceMissing(mode.select(new SortingNumericDoubleValues() {
|
||||
@Override
|
||||
public boolean advanceExact(int docId) throws IOException {
|
||||
|
@ -413,7 +413,7 @@ public abstract class DecayFunctionBuilder<DFB extends DecayFunctionBuilder<DFB>
|
|||
protected String getDistanceString(LeafReaderContext ctx, int docId) throws IOException {
|
||||
StringBuilder values = new StringBuilder(mode.name());
|
||||
values.append(" of: [");
|
||||
final MultiGeoPointValues geoPointValues = fieldData.load(ctx).getGeoPointValues();
|
||||
final MultiGeoPointValues geoPointValues = fieldData.load(ctx).getPointValues();
|
||||
if (geoPointValues.advanceExact(docId)) {
|
||||
final int num = geoPointValues.docValueCount();
|
||||
for (int i = 0; i < num; i++) {
|
||||
|
|
|
@ -8,105 +8,62 @@
|
|||
|
||||
package org.elasticsearch.script.field;
|
||||
|
||||
import org.apache.lucene.util.ArrayUtil;
|
||||
import org.elasticsearch.common.geo.GeoBoundingBox;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.GeoUtils;
|
||||
import org.elasticsearch.index.fielddata.MultiGeoPointValues;
|
||||
import org.elasticsearch.index.fielddata.ScriptDocValues;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class GeoPointDocValuesField extends AbstractScriptFieldFactory<GeoPoint>
|
||||
implements
|
||||
Field<GeoPoint>,
|
||||
DocValuesScriptFieldFactory,
|
||||
ScriptDocValues.GeometrySupplier<GeoPoint> {
|
||||
|
||||
protected final MultiGeoPointValues input;
|
||||
protected final String name;
|
||||
|
||||
protected GeoPoint[] values = new GeoPoint[0];
|
||||
protected int count;
|
||||
|
||||
public class GeoPointDocValuesField extends PointDocValuesField<GeoPoint> {
|
||||
// maintain bwc by making centroid and bounding box available to ScriptDocValues.GeoPoints
|
||||
private ScriptDocValues.GeoPoints geoPoints = null;
|
||||
private final GeoPoint centroid = new GeoPoint();
|
||||
private final GeoBoundingBox boundingBox = new GeoBoundingBox(new GeoPoint(), new GeoPoint());
|
||||
private int labelIndex = 0;
|
||||
|
||||
public GeoPointDocValuesField(MultiGeoPointValues input, String name) {
|
||||
this.input = input;
|
||||
this.name = name;
|
||||
super(input, name, GeoPoint::new, new GeoBoundingBox(new GeoPoint(), new GeoPoint()), new GeoPoint[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNextDocId(int docId) throws IOException {
|
||||
if (input.advanceExact(docId)) {
|
||||
resize(input.docValueCount());
|
||||
if (count == 1) {
|
||||
setSingleValue();
|
||||
} else {
|
||||
setMultiValue();
|
||||
}
|
||||
} else {
|
||||
resize(0);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
protected void resetPointAt(int i, GeoPoint point) {
|
||||
values[i].reset(point.lat(), point.lon());
|
||||
centroidLat += point.getLat();
|
||||
centroidLon += point.getLon();
|
||||
maxLon = Math.max(maxLon, values[i].getLon());
|
||||
minLon = Math.min(minLon, values[i].getLon());
|
||||
maxLat = Math.max(maxLat, values[i].getLat());
|
||||
minLat = Math.min(minLat, values[i].getLat());
|
||||
labelIndex = closestPoint(labelIndex, i, (minLat + maxLat) / 2, (minLon + maxLon) / 2);
|
||||
}
|
||||
centroid.reset(centroidLat / count, centroidLon / count);
|
||||
boundingBox.topLeft().reset(maxLat, minLon);
|
||||
boundingBox.bottomRight().reset(minLat, maxLon);
|
||||
}
|
||||
|
||||
private int closestPoint(int a, int b, double lat, double lon) {
|
||||
if (a == b) {
|
||||
return a;
|
||||
@Override
|
||||
protected void resetCentroidAndBounds(GeoPoint point, GeoPoint topLeft, GeoPoint bottomRight) {
|
||||
centroid.reset(point.lat() / count, point.lon() / count);
|
||||
boundingBox.topLeft().reset(topLeft.lat(), topLeft.lon());
|
||||
boundingBox.bottomRight().reset(bottomRight.lat(), bottomRight.lon());
|
||||
}
|
||||
double distA = GeoUtils.planeDistance(lat, lon, values[a].lat(), values[a].lon());
|
||||
double distB = GeoUtils.planeDistance(lat, lon, values[b].lat(), values[b].lon());
|
||||
return distA < distB ? a : b;
|
||||
|
||||
@Override
|
||||
protected 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
|
||||
|
@ -117,73 +74,4 @@ public class GeoPointDocValuesField extends AbstractScriptFieldFactory<GeoPoint>
|
|||
|
||||
return geoPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPoint getInternal(int index) {
|
||||
return values[index];
|
||||
}
|
||||
|
||||
// maintain bwc by making centroid available to ScriptDocValues.GeoPoints
|
||||
@Override
|
||||
public GeoPoint getInternalCentroid() {
|
||||
return centroid;
|
||||
}
|
||||
|
||||
// maintain bwc by making bounding box available to ScriptDocValues.GeoPoints
|
||||
@Override
|
||||
public GeoBoundingBox getInternalBoundingBox() {
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPoint getInternalLabelPosition() {
|
||||
return values[labelIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public GeoPoint get(GeoPoint defaultValue) {
|
||||
return get(0, defaultValue);
|
||||
}
|
||||
|
||||
public GeoPoint get(int index, GeoPoint defaultValue) {
|
||||
if (isEmpty() || index < 0 || index >= count) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return values[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<GeoPoint> iterator() {
|
||||
return new Iterator<GeoPoint>() {
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPoint next() {
|
||||
if (hasNext() == false) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return values[index++];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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++];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -158,7 +158,7 @@ public class GeoTileGridValuesSourceBuilder extends CompositeValuesSourceBuilder
|
|||
builder.field("precision", precision);
|
||||
if (geoBoundingBox.isUnbounded() == false) {
|
||||
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
|
||||
geoBoundingBox.toXContentFragment(builder, true);
|
||||
geoBoundingBox.toXContentFragment(builder);
|
||||
builder.endObject();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,7 +169,7 @@ public abstract class CellIdSource extends ValuesSource.Numeric {
|
|||
|
||||
@Override
|
||||
public boolean advanceExact(int docId) throws IOException {
|
||||
return geoValues.advanceExact(docId) && advance(geoValues.geoPointValue());
|
||||
return geoValues.advanceExact(docId) && advance(geoValues.pointValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -231,7 +231,7 @@ public abstract class GeoGridAggregationBuilder extends ValuesSourceAggregationB
|
|||
}
|
||||
if (geoBoundingBox.isUnbounded() == false) {
|
||||
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
|
||||
geoBoundingBox.toXContentFragment(builder, true);
|
||||
geoBoundingBox.toXContentFragment(builder);
|
||||
builder.endObject();
|
||||
}
|
||||
return builder;
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -8,14 +8,7 @@
|
|||
|
||||
package org.elasticsearch.search.aggregations.metrics;
|
||||
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.search.aggregations.Aggregation;
|
||||
|
||||
/**
|
||||
* Interface for {@link GeoCentroidAggregator}
|
||||
*/
|
||||
public interface GeoCentroid extends Aggregation {
|
||||
GeoPoint centroid();
|
||||
|
||||
long count();
|
||||
}
|
||||
public interface GeoCentroid extends CentroidAggregation {}
|
||||
|
|
|
@ -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 + '}';
|
||||
}
|
||||
}
|
|
@ -165,7 +165,7 @@ public class InternalGeoBounds extends InternalAggregation implements GeoBounds
|
|||
GeoBoundingBox bbox = resolveGeoBoundingBox();
|
||||
if (bbox != null) {
|
||||
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
|
||||
bbox.toXContentFragment(builder, true);
|
||||
bbox.toXContentFragment(builder);
|
||||
builder.endObject();
|
||||
}
|
||||
return builder;
|
||||
|
|
|
@ -11,80 +11,70 @@ package org.elasticsearch.search.aggregations.metrics;
|
|||
import org.apache.lucene.geo.GeoEncodingUtils;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.SpatialPoint;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.search.aggregations.AggregationReduceContext;
|
||||
import org.elasticsearch.search.aggregations.InternalAggregation;
|
||||
import org.elasticsearch.search.aggregations.support.SamplingContext;
|
||||
import org.elasticsearch.xcontent.ParseField;
|
||||
import org.elasticsearch.xcontent.XContentBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Serialization and merge logic for {@link GeoCentroidAggregator}.
|
||||
*/
|
||||
public class InternalGeoCentroid extends InternalAggregation implements GeoCentroid {
|
||||
private final GeoPoint centroid;
|
||||
private final long count;
|
||||
public class InternalGeoCentroid extends InternalCentroid implements GeoCentroid {
|
||||
|
||||
public static long encodeLatLon(double lat, double lon) {
|
||||
private static long encodeLatLon(double lat, double lon) {
|
||||
return (Integer.toUnsignedLong(GeoEncodingUtils.encodeLatitude(lat)) << 32) | Integer.toUnsignedLong(
|
||||
GeoEncodingUtils.encodeLongitude(lon)
|
||||
);
|
||||
}
|
||||
|
||||
public static double decodeLatitude(long encodedLatLon) {
|
||||
private static double decodeLatitude(long encodedLatLon) {
|
||||
return GeoEncodingUtils.decodeLatitude((int) (encodedLatLon >>> 32));
|
||||
}
|
||||
|
||||
public static double decodeLongitude(long encodedLatLon) {
|
||||
private static double decodeLongitude(long encodedLatLon) {
|
||||
return GeoEncodingUtils.decodeLongitude((int) (encodedLatLon & 0xFFFFFFFFL));
|
||||
}
|
||||
|
||||
public InternalGeoCentroid(String name, GeoPoint centroid, long count, Map<String, Object> metadata) {
|
||||
super(name, metadata);
|
||||
assert (centroid == null) == (count == 0);
|
||||
this.centroid = centroid;
|
||||
assert count >= 0;
|
||||
this.count = count;
|
||||
public InternalGeoCentroid(String name, SpatialPoint centroid, long count, Map<String, Object> metadata) {
|
||||
super(
|
||||
name,
|
||||
centroid,
|
||||
count,
|
||||
metadata,
|
||||
new FieldExtractor("lat", SpatialPoint::getY),
|
||||
new FieldExtractor("lon", SpatialPoint::getX)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from a stream.
|
||||
*/
|
||||
public InternalGeoCentroid(StreamInput in) throws IOException {
|
||||
super(in);
|
||||
count = in.readVLong();
|
||||
if (in.readBoolean()) {
|
||||
if (in.getVersion().onOrAfter(Version.V_7_2_0)) {
|
||||
centroid = new GeoPoint(in.readDouble(), in.readDouble());
|
||||
} else {
|
||||
final long hash = in.readLong();
|
||||
centroid = new GeoPoint(decodeLatitude(hash), decodeLongitude(hash));
|
||||
super(in, new FieldExtractor("lat", SpatialPoint::getY), new FieldExtractor("lon", SpatialPoint::getX));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GeoPoint centroidFromStream(StreamInput in) throws IOException {
|
||||
if (in.getVersion().onOrAfter(Version.V_7_2_0)) {
|
||||
return new GeoPoint(in.readDouble(), in.readDouble());
|
||||
} else {
|
||||
centroid = null;
|
||||
final long hash = in.readLong();
|
||||
return new GeoPoint(decodeLatitude(hash), decodeLongitude(hash));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doWriteTo(StreamOutput out) throws IOException {
|
||||
out.writeVLong(count);
|
||||
if (centroid != null) {
|
||||
out.writeBoolean(true);
|
||||
protected void centroidToStream(StreamOutput out) throws IOException {
|
||||
if (out.getVersion().onOrAfter(Version.V_7_2_0)) {
|
||||
out.writeDouble(centroid.lat());
|
||||
out.writeDouble(centroid.lon());
|
||||
out.writeDouble(centroid.getY());
|
||||
out.writeDouble(centroid.getX());
|
||||
} else {
|
||||
out.writeLong(encodeLatLon(centroid.lat(), centroid.lon()));
|
||||
}
|
||||
} else {
|
||||
out.writeBoolean(false);
|
||||
out.writeLong(encodeLatLon(centroid.getY(), centroid.getX()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,35 +84,23 @@ public class InternalGeoCentroid extends InternalAggregation implements GeoCentr
|
|||
}
|
||||
|
||||
@Override
|
||||
public GeoPoint centroid() {
|
||||
return centroid;
|
||||
protected double extractDouble(String name) {
|
||||
return switch (name) {
|
||||
case "lat" -> centroid.getY();
|
||||
case "lon" -> centroid.getX();
|
||||
default -> throw new IllegalArgumentException("Found unknown path element [" + name + "] in [" + getName() + "]");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return count;
|
||||
protected InternalGeoCentroid copyWith(SpatialPoint result, long count) {
|
||||
return new InternalGeoCentroid(name, result, count, getMetadata());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalGeoCentroid reduce(List<InternalAggregation> aggregations, AggregationReduceContext reduceContext) {
|
||||
double lonSum = Double.NaN;
|
||||
double latSum = Double.NaN;
|
||||
long totalCount = 0;
|
||||
for (InternalAggregation aggregation : aggregations) {
|
||||
InternalGeoCentroid centroidAgg = (InternalGeoCentroid) aggregation;
|
||||
if (centroidAgg.count > 0) {
|
||||
totalCount += centroidAgg.count;
|
||||
if (Double.isNaN(lonSum)) {
|
||||
lonSum = centroidAgg.count * centroidAgg.centroid.getLon();
|
||||
latSum = centroidAgg.count * centroidAgg.centroid.getLat();
|
||||
} else {
|
||||
lonSum += (centroidAgg.count * centroidAgg.centroid.getLon());
|
||||
latSum += (centroidAgg.count * centroidAgg.centroid.getLat());
|
||||
}
|
||||
}
|
||||
}
|
||||
final GeoPoint result = (Double.isNaN(lonSum)) ? null : new GeoPoint(latSum / totalCount, lonSum / totalCount);
|
||||
return new InternalGeoCentroid(name, result, totalCount, getMetadata());
|
||||
protected InternalGeoCentroid copyWith(double firstSum, double secondSum, long totalCount) {
|
||||
final GeoPoint result = (Double.isNaN(firstSum)) ? null : new GeoPoint(firstSum / totalCount, secondSum / totalCount);
|
||||
return copyWith(result, totalCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -130,66 +108,8 @@ public class InternalGeoCentroid extends InternalAggregation implements GeoCentr
|
|||
return new InternalGeoCentroid(name, centroid, samplingContext.scaleUp(count), getMetadata());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean mustReduceOnSingleInternalAgg() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(List<String> path) {
|
||||
if (path.isEmpty()) {
|
||||
return this;
|
||||
} else if (path.size() == 1) {
|
||||
String coordinate = path.get(0);
|
||||
return switch (coordinate) {
|
||||
case "value" -> centroid;
|
||||
case "lat" -> centroid.lat();
|
||||
case "lon" -> centroid.lon();
|
||||
case "count" -> count;
|
||||
default -> throw new IllegalArgumentException("Found unknown path element [" + coordinate + "] in [" + getName() + "]");
|
||||
};
|
||||
} else {
|
||||
throw new IllegalArgumentException("path not supported for [" + getName() + "]: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
static class Fields {
|
||||
static final ParseField CENTROID = new ParseField("location");
|
||||
static final ParseField COUNT = new ParseField("count");
|
||||
static final ParseField CENTROID_LAT = new ParseField("lat");
|
||||
static final ParseField CENTROID_LON = new ParseField("lon");
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
|
||||
if (centroid != null) {
|
||||
builder.startObject(Fields.CENTROID.getPreferredName());
|
||||
{
|
||||
builder.field(Fields.CENTROID_LAT.getPreferredName(), centroid.lat());
|
||||
builder.field(Fields.CENTROID_LON.getPreferredName(), centroid.lon());
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
builder.field(Fields.COUNT.getPreferredName(), count);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
if (super.equals(obj) == false) return false;
|
||||
InternalGeoCentroid that = (InternalGeoCentroid) obj;
|
||||
return count == that.count && Objects.equals(centroid, that.centroid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), centroid, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InternalGeoCentroid{" + "centroid=" + centroid + ", count=" + count + '}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public class ParsedGeoBounds extends ParsedAggregation implements GeoBounds {
|
|||
public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
|
||||
if (geoBoundingBox != null) {
|
||||
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
|
||||
geoBoundingBox.toXContentFragment(builder, true);
|
||||
geoBoundingBox.toXContentFragment(builder);
|
||||
builder.endObject();
|
||||
}
|
||||
return builder;
|
||||
|
|
|
@ -42,14 +42,14 @@ public class ParsedGeoCentroid extends ParsedAggregation implements GeoCentroid
|
|||
@Override
|
||||
public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
|
||||
if (centroid != null) {
|
||||
builder.startObject(Fields.CENTROID.getPreferredName());
|
||||
builder.startObject(InternalCentroid.Fields.CENTROID.getPreferredName());
|
||||
{
|
||||
builder.field(Fields.CENTROID_LAT.getPreferredName(), centroid.lat());
|
||||
builder.field(Fields.CENTROID_LON.getPreferredName(), centroid.lon());
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
builder.field(Fields.COUNT.getPreferredName(), count);
|
||||
builder.field(InternalCentroid.Fields.COUNT.getPreferredName(), count);
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
@ -67,8 +67,8 @@ public class ParsedGeoCentroid extends ParsedAggregation implements GeoCentroid
|
|||
|
||||
static {
|
||||
declareAggregationFields(PARSER);
|
||||
PARSER.declareObject((agg, centroid) -> agg.centroid = centroid, GEO_POINT_PARSER, Fields.CENTROID);
|
||||
PARSER.declareLong((agg, count) -> agg.count = count, Fields.COUNT);
|
||||
PARSER.declareObject((agg, centroid) -> agg.centroid = centroid, GEO_POINT_PARSER, InternalCentroid.Fields.CENTROID);
|
||||
PARSER.declareLong((agg, count) -> agg.count = count, InternalCentroid.Fields.COUNT);
|
||||
|
||||
GEO_POINT_PARSER.declareDouble(GeoPoint::resetLat, Fields.CENTROID_LAT);
|
||||
GEO_POINT_PARSER.declareDouble(GeoPoint::resetLon, Fields.CENTROID_LON);
|
||||
|
|
|
@ -29,9 +29,9 @@ import org.elasticsearch.search.aggregations.bucket.terms.UnmappedSignificantTer
|
|||
import org.elasticsearch.search.aggregations.bucket.terms.UnmappedTerms;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalAvg;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalCardinality;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalCentroid;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalExtendedStats;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalGeoBounds;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalGeoCentroid;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalHDRPercentileRanks;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalHDRPercentiles;
|
||||
import org.elasticsearch.search.aggregations.metrics.InternalMedianAbsoluteDeviation;
|
||||
|
@ -167,7 +167,7 @@ public class AggregationInspectionHelper {
|
|||
return (agg.topLeft() == null && agg.bottomRight() == null) == false;
|
||||
}
|
||||
|
||||
public static boolean hasValue(InternalGeoCentroid agg) {
|
||||
public static boolean hasValue(InternalCentroid agg) {
|
||||
return agg.centroid() != null && agg.count() > 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -162,15 +162,14 @@ public enum CoreValuesSourceType implements ValuesSourceType {
|
|||
|
||||
@Override
|
||||
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(
|
||||
"Expected geo_point type on field [" + fieldContext.field() + "], but got [" + fieldContext.fieldType().typeName() + "]"
|
||||
);
|
||||
}
|
||||
|
||||
return new ValuesSource.GeoPoint.Fielddata((IndexGeoPointFieldData) fieldContext.indexFieldData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValuesSource replaceMissing(
|
||||
ValuesSource valuesSource,
|
||||
|
|
|
@ -36,7 +36,7 @@ import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
|
|||
import org.elasticsearch.index.fielddata.MultiGeoPointValues;
|
||||
import org.elasticsearch.index.fielddata.NumericDoubleValues;
|
||||
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
|
||||
import org.elasticsearch.index.fielddata.plain.AbstractLatLonPointIndexFieldData.LatLonPointIndexFieldData;
|
||||
import org.elasticsearch.index.fielddata.plain.LatLonPointIndexFieldData;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.index.query.GeoValidationMethod;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
|
@ -642,7 +642,7 @@ public class GeoDistanceSortBuilder extends SortBuilder<GeoDistanceSortBuilder>
|
|||
}
|
||||
|
||||
private NumericDoubleValues getNumericDoubleValues(LeafReaderContext context) throws IOException {
|
||||
final MultiGeoPointValues geoPointValues = geoIndexFieldData.load(context).getGeoPointValues();
|
||||
final MultiGeoPointValues geoPointValues = geoIndexFieldData.load(context).getPointValues();
|
||||
final SortedNumericDoubleValues distanceValues = GeoUtils.distanceValues(geoDistance, unit, geoPointValues, localPoints);
|
||||
if (nested == null) {
|
||||
return FieldData.replaceMissing(sortMode.select(distanceValues), Double.POSITIVE_INFINITY);
|
||||
|
|
|
@ -12,7 +12,7 @@ import org.apache.lucene.document.Field;
|
|||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.elasticsearch.index.fielddata.plain.AbstractLeafGeoPointFieldData;
|
||||
import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -149,7 +149,7 @@ public class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
|
|||
LeafFieldData fieldData = indexFieldData.load(readerContext);
|
||||
assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed()));
|
||||
|
||||
MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues();
|
||||
MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues();
|
||||
assertValues(fieldValues, 0);
|
||||
assertValues(fieldValues, 1);
|
||||
assertValues(fieldValues, 2);
|
||||
|
@ -165,7 +165,7 @@ public class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
|
|||
LeafFieldData fieldData = indexFieldData.load(readerContext);
|
||||
assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed()));
|
||||
|
||||
MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues();
|
||||
MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues();
|
||||
assertValues(fieldValues, 0);
|
||||
assertMissing(fieldValues, 1);
|
||||
assertValues(fieldValues, 2);
|
||||
|
@ -181,7 +181,7 @@ public class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
|
|||
LeafFieldData fieldData = indexFieldData.load(readerContext);
|
||||
assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed()));
|
||||
|
||||
MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues();
|
||||
MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues();
|
||||
assertValues(fieldValues, 0);
|
||||
assertValues(fieldValues, 1);
|
||||
assertValues(fieldValues, 2);
|
||||
|
@ -197,7 +197,7 @@ public class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
|
|||
LeafFieldData fieldData = indexFieldData.load(readerContext);
|
||||
assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed()));
|
||||
|
||||
MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues();
|
||||
MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues();
|
||||
|
||||
assertValues(fieldValues, 0);
|
||||
assertMissing(fieldValues, 1);
|
||||
|
|
|
@ -74,7 +74,7 @@ public class GeoPointScriptFieldTypeTests extends AbstractNonTextScriptFieldType
|
|||
|
||||
@Override
|
||||
public LeafCollector getLeafCollector(LeafReaderContext context) {
|
||||
MultiGeoPointValues dv = ifd.load(context).getGeoPointValues();
|
||||
MultiGeoPointValues dv = ifd.load(context).getPointValues();
|
||||
return new LeafCollector() {
|
||||
@Override
|
||||
public void setScorer(Scorable scorer) {}
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.elasticsearch.action.search.SearchResponse;
|
|||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.document.DocumentField;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.SpatialPoint;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.geometry.utils.Geohash;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
|
@ -32,6 +33,7 @@ import java.util.Map;
|
|||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
|
||||
import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.hamcrest.Matchers.closeTo;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
@ESIntegTestCase.SuiteScopeTestCase
|
||||
|
@ -285,4 +287,18 @@ public abstract class AbstractGeoTestCase extends ESIntegTestCase {
|
|||
currentBound.resetLon(geoPoint.lon());
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertSameCentroid(SpatialPoint centroid, SpatialPoint expectedCentroid) {
|
||||
String[] names = centroid.getClass() == GeoPoint.class ? new String[] { "longitude", "latitude" } : new String[] { "x", "y" };
|
||||
assertThat(
|
||||
"Mismatching value for '" + names[0] + "' field of centroid",
|
||||
centroid.getX(),
|
||||
closeTo(expectedCentroid.getX(), GEOHASH_TOLERANCE)
|
||||
);
|
||||
assertThat(
|
||||
"Mismatching value for '" + names[1] + "' field of centroid",
|
||||
centroid.getY(),
|
||||
closeTo(expectedCentroid.getY(), GEOHASH_TOLERANCE)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.apache.lucene.search.MatchAllDocsQuery;
|
|||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.tests.index.RandomIndexWriter;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.SpatialPoint;
|
||||
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.search.aggregations.AggregationBuilder;
|
||||
|
@ -152,10 +153,10 @@ public class GeoCentroidAggregatorTests extends AggregatorTestCase {
|
|||
InternalGeoCentroid result = searchAndReduce(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType);
|
||||
|
||||
assertEquals("my_agg", result.getName());
|
||||
GeoPoint centroid = result.centroid();
|
||||
SpatialPoint centroid = result.centroid();
|
||||
assertNotNull(centroid);
|
||||
assertEquals(expectedCentroid.getLat(), centroid.getLat(), GEOHASH_TOLERANCE);
|
||||
assertEquals(expectedCentroid.getLon(), centroid.getLon(), GEOHASH_TOLERANCE);
|
||||
assertEquals(expectedCentroid.getX(), centroid.getX(), GEOHASH_TOLERANCE);
|
||||
assertEquals(expectedCentroid.getY(), centroid.getY(), GEOHASH_TOLERANCE);
|
||||
assertTrue(AggregationInspectionHelper.hasValue(result));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.elasticsearch.search.aggregations.metrics;
|
|||
|
||||
import org.apache.lucene.geo.GeoEncodingUtils;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.SpatialPoint;
|
||||
import org.elasticsearch.common.util.Maps;
|
||||
import org.elasticsearch.search.aggregations.ParsedAggregation;
|
||||
import org.elasticsearch.search.aggregations.support.SamplingContext;
|
||||
|
@ -48,14 +49,14 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
|
|||
long totalCount = 0;
|
||||
for (InternalGeoCentroid input : inputs) {
|
||||
if (input.count() > 0) {
|
||||
lonSum += (input.count() * input.centroid().getLon());
|
||||
latSum += (input.count() * input.centroid().getLat());
|
||||
lonSum += (input.count() * input.centroid().getX());
|
||||
latSum += (input.count() * input.centroid().getY());
|
||||
}
|
||||
totalCount += input.count();
|
||||
}
|
||||
if (totalCount > 0) {
|
||||
assertEquals(latSum / totalCount, reduced.centroid().getLat(), 1E-5D);
|
||||
assertEquals(lonSum / totalCount, reduced.centroid().getLon(), 1E-5D);
|
||||
assertEquals(latSum / totalCount, reduced.centroid().getY(), 1E-5D);
|
||||
assertEquals(lonSum / totalCount, reduced.centroid().getX(), 1E-5D);
|
||||
}
|
||||
assertEquals(totalCount, reduced.count());
|
||||
}
|
||||
|
@ -67,8 +68,8 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
|
|||
|
||||
@Override
|
||||
protected void assertSampled(InternalGeoCentroid sampled, InternalGeoCentroid reduced, SamplingContext samplingContext) {
|
||||
assertEquals(sampled.centroid().getLat(), reduced.centroid().getLat(), 1e-12);
|
||||
assertEquals(sampled.centroid().getLon(), reduced.centroid().getLon(), 1e-12);
|
||||
assertEquals(sampled.centroid().getY(), reduced.centroid().getY(), 1e-12);
|
||||
assertEquals(sampled.centroid().getX(), reduced.centroid().getX(), 1e-12);
|
||||
assertEquals(sampled.count(), samplingContext.scaleUp(reduced.count()), 0);
|
||||
}
|
||||
|
||||
|
@ -79,7 +80,7 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
|
|||
Long.MAX_VALUE,
|
||||
Collections.emptyMap()
|
||||
);
|
||||
InternalGeoCentroid reducedGeoCentroid = maxValueGeoCentroid.reduce(Collections.singletonList(maxValueGeoCentroid), null);
|
||||
InternalCentroid reducedGeoCentroid = maxValueGeoCentroid.reduce(Collections.singletonList(maxValueGeoCentroid), null);
|
||||
assertThat(reducedGeoCentroid.count(), equalTo(Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
|
@ -95,7 +96,7 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
|
|||
@Override
|
||||
protected InternalGeoCentroid mutateInstance(InternalGeoCentroid instance) {
|
||||
String name = instance.getName();
|
||||
GeoPoint centroid = instance.centroid();
|
||||
SpatialPoint centroid = instance.centroid();
|
||||
long count = instance.count();
|
||||
Map<String, Object> metadata = instance.getMetadata();
|
||||
switch (between(0, 3)) {
|
||||
|
@ -115,9 +116,9 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
|
|||
} else {
|
||||
GeoPoint newCentroid = new GeoPoint(centroid);
|
||||
if (randomBoolean()) {
|
||||
newCentroid.resetLat(centroid.getLat() / 2.0);
|
||||
newCentroid.resetLat(centroid.getY() / 2.0);
|
||||
} else {
|
||||
newCentroid.resetLon(centroid.getLon() / 2.0);
|
||||
newCentroid.resetLon(centroid.getX() / 2.0);
|
||||
}
|
||||
centroid = newCentroid;
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ public class GeoTileGroupSource extends SingleGroupSource {
|
|||
}
|
||||
if (geoBoundingBox != null) {
|
||||
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
|
||||
geoBoundingBox.toXContentFragment(builder, true);
|
||||
geoBoundingBox.toXContentFragment(builder);
|
||||
builder.endObject();
|
||||
}
|
||||
builder.endObject();
|
||||
|
|
|
@ -202,7 +202,7 @@ class AggregationToJsonProcessor {
|
|||
queueDocToWrite(keyValuePairs, docCount);
|
||||
}
|
||||
|
||||
addedLeafKeys.forEach(k -> keyValuePairs.remove(k));
|
||||
addedLeafKeys.forEach(keyValuePairs::remove);
|
||||
}
|
||||
|
||||
private void processDateHistogram(Histogram agg) throws IOException {
|
||||
|
@ -400,7 +400,7 @@ class AggregationToJsonProcessor {
|
|||
|
||||
private boolean processGeoCentroid(GeoCentroid agg) {
|
||||
if (agg.count() > 0) {
|
||||
keyValuePairs.put(agg.getName(), agg.centroid().getLat() + "," + agg.centroid().getLon());
|
||||
keyValuePairs.put(agg.getName(), agg.centroid().getY() + "," + agg.centroid().getX());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -9,7 +9,8 @@ package org.elasticsearch.xpack.spatial.search;
|
|||
import org.apache.lucene.geo.Circle;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.document.DocumentField;
|
||||
import org.elasticsearch.common.geo.GeoBoundingBox;
|
||||
import org.elasticsearch.common.geo.BoundingBox;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.Orientation;
|
||||
import org.elasticsearch.geo.GeometryTestUtils;
|
||||
import org.elasticsearch.geometry.Geometry;
|
||||
|
@ -20,7 +21,6 @@ import org.elasticsearch.geometry.Point;
|
|||
import org.elasticsearch.geometry.Polygon;
|
||||
import org.elasticsearch.geometry.utils.GeographyValidator;
|
||||
import org.elasticsearch.geometry.utils.WellKnownText;
|
||||
import org.elasticsearch.index.fielddata.ScriptDocValues;
|
||||
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.script.MockScriptPlugin;
|
||||
|
@ -34,6 +34,8 @@ import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin;
|
|||
import org.elasticsearch.xpack.spatial.index.fielddata.DimensionalShapeType;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.plain.AbstractAtomicGeoShapeShapeFieldData;
|
||||
import org.elasticsearch.xpack.spatial.util.GeoTestUtils;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
|
@ -79,52 +81,53 @@ public class GeoShapeScriptDocValuesIT extends ESSingleNodeTestCase {
|
|||
|
||||
private double scriptHeight(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
|
||||
if (geometry.size() == 0) {
|
||||
return Double.NaN;
|
||||
} else {
|
||||
GeoBoundingBox boundingBox = geometry.getBoundingBox();
|
||||
BoundingBox<GeoPoint> boundingBox = geometry.getBoundingBox();
|
||||
return boundingBox.topLeft().lat() - boundingBox.bottomRight().lat();
|
||||
}
|
||||
}
|
||||
|
||||
private double scriptWidth(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
|
||||
if (geometry.size() == 0) {
|
||||
return Double.NaN;
|
||||
} else {
|
||||
GeoBoundingBox boundingBox = geometry.getBoundingBox();
|
||||
BoundingBox<GeoPoint> boundingBox = geometry.getBoundingBox();
|
||||
return boundingBox.bottomRight().lon() - boundingBox.topLeft().lon();
|
||||
}
|
||||
}
|
||||
|
||||
private double scriptLat(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
|
||||
return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lat();
|
||||
}
|
||||
|
||||
private double scriptLon(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
|
||||
return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lon();
|
||||
}
|
||||
|
||||
private double scriptLabelLat(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
|
||||
return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lat();
|
||||
}
|
||||
|
||||
private double scriptLabelLon(Map<String, Object> vars) {
|
||||
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
|
||||
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
|
||||
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
|
||||
return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lon();
|
||||
}
|
||||
|
||||
private ScriptDocValues.Geometry<?> assertGeometry(Map<?, ?> doc) {
|
||||
ScriptDocValues.Geometry<?> geometry = (ScriptDocValues.Geometry<?>) doc.get("location");
|
||||
private LeafShapeFieldData.ShapeScriptValues<GeoPoint> assertGeometry(Map<?, ?> doc) {
|
||||
AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues geometry =
|
||||
(AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues) doc.get("location");
|
||||
if (geometry.size() == 0) {
|
||||
assertThat(geometry.getBoundingBox(), Matchers.nullValue());
|
||||
assertThat(geometry.getCentroid(), Matchers.nullValue());
|
||||
|
@ -267,8 +270,8 @@ public class GeoShapeScriptDocValuesIT extends ESSingleNodeTestCase {
|
|||
.get();
|
||||
assertSearchResponse(searchResponse);
|
||||
Map<String, DocumentField> fields = searchResponse.getHits().getHits()[0].getFields();
|
||||
assertThat(fields.get("lat").getValue(), equalTo(value.lat()));
|
||||
assertThat(fields.get("lon").getValue(), equalTo(value.lon()));
|
||||
assertThat(fields.get("lat").getValue(), equalTo(value.getY()));
|
||||
assertThat(fields.get("lon").getValue(), equalTo(value.getX()));
|
||||
assertThat(fields.get("height").getValue(), equalTo(value.boundingBox().maxY() - value.boundingBox().minY()));
|
||||
assertThat(fields.get("width").getValue(), equalTo(value.boundingBox().maxX() - value.boundingBox().minX()));
|
||||
|
||||
|
@ -285,16 +288,21 @@ public class GeoShapeScriptDocValuesIT extends ESSingleNodeTestCase {
|
|||
doTestLabelPosition(fields, expectedLabelPosition);
|
||||
} else if (fallbackToCentroid && value.dimensionalShapeType() == DimensionalShapeType.POLYGON) {
|
||||
// Use the centroid for all polygons, unless overwritten for specific cases
|
||||
doTestLabelPosition(fields, GeoTestUtils.geoShapeValue(new Point(value.lon(), value.lat())));
|
||||
doTestLabelPosition(fields, GeoTestUtils.geoShapeValue(new Point(value.getX(), value.getY())));
|
||||
}
|
||||
}
|
||||
|
||||
private void doTestLabelPosition(Map<String, DocumentField> fields, GeoShapeValues.GeoShapeValue expectedLabelPosition)
|
||||
throws IOException {
|
||||
assertEquals("Unexpected latitude for label position,", expectedLabelPosition.lat(), fields.get("label_lat").getValue(), 0.0000001);
|
||||
assertEquals(
|
||||
"Unexpected latitude for label position,",
|
||||
expectedLabelPosition.getY(),
|
||||
fields.get("label_lat").getValue(),
|
||||
0.0000001
|
||||
);
|
||||
assertEquals(
|
||||
"Unexpected longitude for label position,",
|
||||
expectedLabelPosition.lon(),
|
||||
expectedLabelPosition.getX(),
|
||||
fields.get("label_lon").getValue(),
|
||||
0.0000001
|
||||
);
|
||||
|
|
|
@ -7,24 +7,16 @@
|
|||
|
||||
package org.elasticsearch.xpack.spatial.index.fielddata;
|
||||
|
||||
import org.apache.lucene.document.ShapeField;
|
||||
import org.apache.lucene.geo.Component2D;
|
||||
import org.apache.lucene.geo.LatLonGeometry;
|
||||
import org.apache.lucene.geo.Point;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.Orientation;
|
||||
import org.elasticsearch.geometry.Geometry;
|
||||
import org.elasticsearch.geometry.utils.GeographyValidator;
|
||||
import org.elasticsearch.geometry.utils.WellKnownText;
|
||||
import org.elasticsearch.index.mapper.GeoShapeIndexer;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
|
||||
import org.elasticsearch.xcontent.ToXContentFragment;
|
||||
import org.elasticsearch.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.xpack.spatial.index.mapper.BinaryShapeDocValuesField;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* A stateful lightweight per document geo values.
|
||||
|
@ -41,7 +33,7 @@ import java.text.ParseException;
|
|||
*
|
||||
* There is just one value for one document.
|
||||
*/
|
||||
public abstract class GeoShapeValues {
|
||||
public abstract class GeoShapeValues extends ShapeValues {
|
||||
|
||||
public static GeoShapeValues EMPTY = new GeoShapeValues() {
|
||||
private final GeoShapeValuesSourceType DEFAULT_VALUES_SOURCE_TYPE = GeoShapeValuesSourceType.instance();
|
||||
|
@ -65,194 +57,20 @@ public abstract class GeoShapeValues {
|
|||
/**
|
||||
* Creates a new {@link GeoShapeValues} instance
|
||||
*/
|
||||
protected GeoShapeValues() {}
|
||||
|
||||
/**
|
||||
* Advance this instance to the given document id
|
||||
* @return true if there is a value for this document
|
||||
*/
|
||||
public abstract boolean advanceExact(int doc) throws IOException;
|
||||
|
||||
public abstract ValuesSourceType valuesSourceType();
|
||||
|
||||
/**
|
||||
* Return the value associated with the current document.
|
||||
*
|
||||
* Note: the returned {@link GeoShapeValue} might be shared across invocations.
|
||||
*
|
||||
* @return the value for the current docID set to {@link #advanceExact(int)}.
|
||||
*/
|
||||
public abstract GeoShapeValue value() throws IOException;
|
||||
protected GeoShapeValues() {
|
||||
super(CoordinateEncoder.GEO, GeoShapeValues.GeoShapeValue::new, new GeoShapeIndexer(Orientation.CCW, "missing"));
|
||||
}
|
||||
|
||||
/** thin wrapper around a {@link GeometryDocValueReader} which encodes / decodes values using
|
||||
* the Geo decoder */
|
||||
public static class GeoShapeValue implements ToXContentFragment {
|
||||
private static final GeoShapeIndexer MISSING_GEOSHAPE_INDEXER = new GeoShapeIndexer(Orientation.CCW, "missing");
|
||||
private final GeometryDocValueReader reader;
|
||||
private final BoundingBox boundingBox;
|
||||
private final Tile2DVisitor tile2DVisitor;
|
||||
private final LatLonGeometryRelationVisitor component2DRelationVisitor;
|
||||
|
||||
public static class GeoShapeValue extends ShapeValues.ShapeValue {
|
||||
public GeoShapeValue() {
|
||||
this.reader = new GeometryDocValueReader();
|
||||
this.boundingBox = new BoundingBox();
|
||||
this.tile2DVisitor = new Tile2DVisitor();
|
||||
this.component2DRelationVisitor = new LatLonGeometryRelationVisitor(CoordinateEncoder.GEO);
|
||||
}
|
||||
|
||||
/**
|
||||
* reset the geometry.
|
||||
*/
|
||||
public void reset(BytesRef bytesRef) throws IOException {
|
||||
this.reader.reset(bytesRef);
|
||||
this.boundingBox.reset(reader.getExtent(), CoordinateEncoder.GEO);
|
||||
}
|
||||
|
||||
public BoundingBox boundingBox() {
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a label position that is within the shape.
|
||||
*/
|
||||
public GeoPoint labelPosition() throws IOException {
|
||||
// For polygons we prefer to use the centroid, as long as it is within the polygon
|
||||
if (reader.getDimensionalShapeType() == DimensionalShapeType.POLYGON) {
|
||||
Component2DVisitor visitor = Component2DVisitor.getVisitor(
|
||||
LatLonGeometry.create(new Point(lat(), lon())),
|
||||
ShapeField.QueryRelation.INTERSECTS,
|
||||
CoordinateEncoder.GEO
|
||||
);
|
||||
reader.visit(visitor);
|
||||
if (visitor.matches()) {
|
||||
return new GeoPoint(lat(), lon());
|
||||
}
|
||||
}
|
||||
// For all other cases, use the first triangle (or line or point) in the tree which will always intersect the shape
|
||||
LabelPositionVisitor<GeoPoint> visitor = new LabelPositionVisitor<>(CoordinateEncoder.GEO, (x, y) -> new GeoPoint(y, x));
|
||||
reader.visit(visitor);
|
||||
return visitor.labelPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the {@link GeoRelation} between the current shape and a bounding box provided in
|
||||
* the encoded space.
|
||||
*/
|
||||
public GeoRelation relate(int minX, int maxX, int minY, int maxY) throws IOException {
|
||||
tile2DVisitor.reset(minX, minY, maxX, maxY);
|
||||
reader.visit(tile2DVisitor);
|
||||
return tile2DVisitor.relation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the {@link GeoRelation} between the current shape and a {@link LatLonGeometry}. It only supports
|
||||
* simple geometries, therefore it will fail if the LatLonGeometry is a {@link org.apache.lucene.geo.Rectangle}
|
||||
* that crosses the dateline.
|
||||
*/
|
||||
public GeoRelation relate(LatLonGeometry latLonGeometry) throws IOException {
|
||||
component2DRelationVisitor.reset(latLonGeometry);
|
||||
reader.visit(component2DRelationVisitor);
|
||||
return component2DRelationVisitor.relation();
|
||||
}
|
||||
|
||||
public DimensionalShapeType dimensionalShapeType() {
|
||||
return reader.getDimensionalShapeType();
|
||||
}
|
||||
|
||||
public double weight() throws IOException {
|
||||
return reader.getSumCentroidWeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the latitude of the centroid of the shape
|
||||
*/
|
||||
public double lat() throws IOException {
|
||||
return CoordinateEncoder.GEO.decodeY(reader.getCentroidY());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the longitude of the centroid of the shape
|
||||
*/
|
||||
public double lon() throws IOException {
|
||||
return CoordinateEncoder.GEO.decodeX(reader.getCentroidX());
|
||||
}
|
||||
|
||||
public static GeoShapeValue missing(String missing) {
|
||||
try {
|
||||
final Geometry geometry = WellKnownText.fromWKT(GeographyValidator.instance(true), true, missing);
|
||||
final BinaryShapeDocValuesField field = new BinaryShapeDocValuesField("missing", CoordinateEncoder.GEO);
|
||||
field.add(MISSING_GEOSHAPE_INDEXER.indexShape(geometry), geometry);
|
||||
final GeoShapeValue value = new GeoShapeValue();
|
||||
value.reset(field.binaryValue());
|
||||
return value;
|
||||
} catch (IOException | ParseException e) {
|
||||
throw new IllegalArgumentException("Can't apply missing value [" + missing + "]", e);
|
||||
}
|
||||
super(CoordinateEncoder.GEO, (x, y) -> new GeoPoint(y, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
throw new IllegalArgumentException("cannot write xcontent for geo_shape doc value");
|
||||
}
|
||||
}
|
||||
|
||||
public static class BoundingBox {
|
||||
public double top;
|
||||
public double bottom;
|
||||
public double negLeft;
|
||||
public double negRight;
|
||||
public double posLeft;
|
||||
public double posRight;
|
||||
|
||||
private BoundingBox() {}
|
||||
|
||||
private void reset(Extent extent, CoordinateEncoder coordinateEncoder) {
|
||||
this.top = coordinateEncoder.decodeY(extent.top);
|
||||
this.bottom = coordinateEncoder.decodeY(extent.bottom);
|
||||
|
||||
if (extent.negLeft == Integer.MAX_VALUE && extent.negRight == Integer.MIN_VALUE) {
|
||||
this.negLeft = Double.POSITIVE_INFINITY;
|
||||
this.negRight = Double.NEGATIVE_INFINITY;
|
||||
} else {
|
||||
this.negLeft = coordinateEncoder.decodeX(extent.negLeft);
|
||||
this.negRight = coordinateEncoder.decodeX(extent.negRight);
|
||||
}
|
||||
|
||||
if (extent.posLeft == Integer.MAX_VALUE && extent.posRight == Integer.MIN_VALUE) {
|
||||
this.posLeft = Double.POSITIVE_INFINITY;
|
||||
this.posRight = Double.NEGATIVE_INFINITY;
|
||||
} else {
|
||||
this.posLeft = coordinateEncoder.decodeX(extent.posLeft);
|
||||
this.posRight = coordinateEncoder.decodeX(extent.posRight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the minimum y-coordinate of the extent
|
||||
*/
|
||||
public double minY() {
|
||||
return bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximum y-coordinate of the extent
|
||||
*/
|
||||
public double maxY() {
|
||||
return top;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the absolute minimum x-coordinate of the extent, whether it is positive or negative.
|
||||
*/
|
||||
public double minX() {
|
||||
return Math.min(negLeft, posLeft);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the absolute maximum x-coordinate of the extent, whether it is positive or negative.
|
||||
*/
|
||||
public double maxX() {
|
||||
return Math.max(negRight, posRight);
|
||||
protected Component2D centroidAsComponent2D() throws IOException {
|
||||
return LatLonGeometry.create(new Point(getY(), getX()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@ package org.elasticsearch.xpack.spatial.index.fielddata;
|
|||
import org.elasticsearch.index.fielddata.IndexFieldData;
|
||||
|
||||
/**
|
||||
* Specialization of {@link IndexFieldData} for geo shapes.
|
||||
* Specialization of {@link IndexFieldData} for geo shapes and shapes.
|
||||
*/
|
||||
public interface IndexGeoShapeFieldData extends IndexFieldData<LeafGeoShapeFieldData> {}
|
||||
public interface IndexShapeFieldData extends IndexFieldData<LeafShapeFieldData> {}
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package org.elasticsearch.xpack.spatial.index.fielddata;
|
||||
|
||||
import org.elasticsearch.common.geo.SpatialPoint;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
|
@ -15,12 +17,12 @@ import java.util.function.BiFunction;
|
|||
*
|
||||
* TODO: We could instead choose the point closer to the centroid which improves unbalanced trees
|
||||
*/
|
||||
public class LabelPositionVisitor<T> extends TriangleTreeReader.DecodedVisitor {
|
||||
public class LabelPositionVisitor extends TriangleTreeReader.DecodedVisitor {
|
||||
|
||||
private T labelPosition;
|
||||
private final BiFunction<Double, Double, T> pointMaker;
|
||||
private SpatialPoint labelPosition;
|
||||
private final BiFunction<Double, Double, SpatialPoint> pointMaker;
|
||||
|
||||
public LabelPositionVisitor(CoordinateEncoder encoder, BiFunction<Double, Double, T> pointMaker) {
|
||||
public LabelPositionVisitor(CoordinateEncoder encoder, BiFunction<Double, Double, SpatialPoint> pointMaker) {
|
||||
super(encoder);
|
||||
this.pointMaker = pointMaker;
|
||||
}
|
||||
|
@ -75,7 +77,7 @@ public class LabelPositionVisitor<T> extends TriangleTreeReader.DecodedVisitor {
|
|||
return labelPosition == null;
|
||||
}
|
||||
|
||||
public T labelPosition() {
|
||||
public SpatialPoint labelPosition() {
|
||||
return labelPosition;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,81 +7,48 @@
|
|||
|
||||
package org.elasticsearch.xpack.spatial.index.fielddata.plain;
|
||||
|
||||
import org.apache.lucene.util.Accountable;
|
||||
import org.elasticsearch.common.geo.GeoBoundingBox;
|
||||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.index.fielddata.ScriptDocValues;
|
||||
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
|
||||
import org.elasticsearch.script.field.DocValuesScriptFieldFactory;
|
||||
import org.elasticsearch.script.field.ToScriptFieldFactory;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues.GeoShapeValue;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.LeafGeoShapeFieldData;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
|
||||
import static org.elasticsearch.common.geo.SphericalMercatorUtils.latToSphericalMercator;
|
||||
import static org.elasticsearch.common.geo.SphericalMercatorUtils.lonToSphericalMercator;
|
||||
|
||||
public abstract class AbstractAtomicGeoShapeShapeFieldData implements LeafGeoShapeFieldData {
|
||||
public abstract class AbstractAtomicGeoShapeShapeFieldData extends LeafShapeFieldData {
|
||||
|
||||
private final ToScriptFieldFactory<GeoShapeValues> toScriptFieldFactory;
|
||||
|
||||
public AbstractAtomicGeoShapeShapeFieldData(ToScriptFieldFactory<GeoShapeValues> toScriptFieldFactory) {
|
||||
this.toScriptFieldFactory = toScriptFieldFactory;
|
||||
public AbstractAtomicGeoShapeShapeFieldData(ToScriptFieldFactory<ShapeValues> toScriptFieldFactory) {
|
||||
super(toScriptFieldFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SortedBinaryDocValues getBytesValues() {
|
||||
throw new UnsupportedOperationException("scripts and term aggs are not supported by geo_shape doc values");
|
||||
public static LeafShapeFieldData empty(final int maxDoc, ToScriptFieldFactory<ShapeValues> toScriptFieldFactory) {
|
||||
return new LeafShapeFieldData.Empty<>(toScriptFieldFactory, GeoShapeValues.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final DocValuesScriptFieldFactory getScriptFieldFactory(String name) {
|
||||
return toScriptFieldFactory.getScriptFieldFactory(getGeoShapeValues(), name);
|
||||
}
|
||||
public static final class GeoShapeScriptValues extends LeafShapeFieldData.ShapeScriptValues<GeoPoint>
|
||||
implements
|
||||
ScriptDocValues.Geometry {
|
||||
|
||||
public static LeafGeoShapeFieldData empty(final int maxDoc, ToScriptFieldFactory<GeoShapeValues> toScriptFieldFactory) {
|
||||
return new AbstractAtomicGeoShapeShapeFieldData(toScriptFieldFactory) {
|
||||
|
||||
@Override
|
||||
public long ramBytesUsed() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Accountable> getChildResources() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
|
||||
@Override
|
||||
public GeoShapeValues getGeoShapeValues() {
|
||||
return GeoShapeValues.EMPTY;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final class GeoShapeScriptValues extends ScriptDocValues.Geometry<GeoShapeValue> {
|
||||
|
||||
private final GeometrySupplier<GeoShapeValue> gsSupplier;
|
||||
|
||||
public GeoShapeScriptValues(GeometrySupplier<GeoShapeValue> supplier) {
|
||||
public GeoShapeScriptValues(GeometrySupplier<GeoPoint, ShapeValues.ShapeValue> supplier) {
|
||||
super(supplier);
|
||||
this.gsSupplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDimensionalType() {
|
||||
return gsSupplier.getInternal(0) == null ? -1 : gsSupplier.getInternal(0).dimensionalShapeType().ordinal();
|
||||
public GeoShapeValues.GeoShapeValue get(int index) {
|
||||
return (GeoShapeValues.GeoShapeValue) super.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPoint getCentroid() {
|
||||
return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalCentroid();
|
||||
public GeoShapeValues.GeoShapeValue getValue() {
|
||||
return (GeoShapeValues.GeoShapeValue) super.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoBoundingBox getBoundingBox() {
|
||||
return (GeoBoundingBox) super.getBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -93,29 +60,5 @@ public abstract class AbstractAtomicGeoShapeShapeFieldData implements LeafGeoSha
|
|||
public double getMercatorHeight() {
|
||||
return latToSphericalMercator(getBoundingBox().top()) - latToSphericalMercator(getBoundingBox().bottom());
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoBoundingBox getBoundingBox() {
|
||||
return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPoint getLabelPosition() {
|
||||
return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalLabelPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoShapeValues.GeoShapeValue get(int index) {
|
||||
return gsSupplier.getInternal(0);
|
||||
}
|
||||
|
||||
public GeoShapeValues.GeoShapeValue getValue() {
|
||||
return gsSupplier.getInternal(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return supplier.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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?"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,17 +14,19 @@ import org.apache.lucene.util.Accountable;
|
|||
import org.elasticsearch.script.field.ToScriptFieldFactory;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
final class LatLonShapeDVAtomicShapeFieldData extends AbstractAtomicGeoShapeShapeFieldData {
|
||||
final class LatLonShapeDVAtomicShapeFieldData extends LeafShapeFieldData {
|
||||
private final LeafReader reader;
|
||||
private final String fieldName;
|
||||
|
||||
LatLonShapeDVAtomicShapeFieldData(LeafReader reader, String fieldName, ToScriptFieldFactory<GeoShapeValues> toScriptFieldFactory) {
|
||||
LatLonShapeDVAtomicShapeFieldData(LeafReader reader, String fieldName, ToScriptFieldFactory<ShapeValues> toScriptFieldFactory) {
|
||||
super(toScriptFieldFactory);
|
||||
this.reader = reader;
|
||||
this.fieldName = fieldName;
|
||||
|
@ -46,7 +48,7 @@ final class LatLonShapeDVAtomicShapeFieldData extends AbstractAtomicGeoShapeShap
|
|||
}
|
||||
|
||||
@Override
|
||||
public GeoShapeValues getGeoShapeValues() {
|
||||
public ShapeValues getShapeValues() {
|
||||
try {
|
||||
final BinaryDocValues binaryValues = DocValues.getBinary(reader, fieldName);
|
||||
final GeoShapeValues.GeoShapeValue geoShapeValue = new GeoShapeValues.GeoShapeValue();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -46,8 +46,10 @@ import org.elasticsearch.script.field.DocValuesScriptFieldFactory;
|
|||
import org.elasticsearch.script.field.Field;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.CoordinateEncoder;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.plain.AbstractAtomicGeoShapeShapeFieldData;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.plain.AbstractLatLonShapeIndexFieldData;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.plain.LatLonShapeIndexFieldData;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -183,7 +185,11 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
|
|||
@Override
|
||||
public IndexFieldData.Builder fielddataBuilder(FieldDataContext fieldDataContext) {
|
||||
failIfNoDocValues();
|
||||
return new AbstractLatLonShapeIndexFieldData.Builder(name(), GeoShapeValuesSourceType.instance(), GeoShapeDocValuesField::new);
|
||||
return (cache, breakerService) -> new LatLonShapeIndexFieldData(
|
||||
name(),
|
||||
GeoShapeValuesSourceType.instance(),
|
||||
GeoShapeDocValuesField::new
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -340,13 +346,13 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
|
|||
super.checkIncomingMergeType(mergeWith);
|
||||
}
|
||||
|
||||
public static class GeoShapeDocValuesField extends AbstractScriptFieldFactory<GeoShapeValues.GeoShapeValue>
|
||||
public static class GeoShapeDocValuesField extends AbstractScriptFieldFactory<ShapeValues.ShapeValue>
|
||||
implements
|
||||
Field<GeoShapeValues.GeoShapeValue>,
|
||||
Field<ShapeValues.ShapeValue>,
|
||||
DocValuesScriptFieldFactory,
|
||||
ScriptDocValues.GeometrySupplier<GeoShapeValues.GeoShapeValue> {
|
||||
ScriptDocValues.GeometrySupplier<GeoPoint, ShapeValues.ShapeValue> {
|
||||
|
||||
private final GeoShapeValues in;
|
||||
private final ShapeValues in;
|
||||
protected final String name;
|
||||
|
||||
private GeoShapeValues.GeoShapeValue value;
|
||||
|
@ -354,9 +360,9 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
|
|||
// maintain bwc by making bounding box and centroid available to GeoShapeValues (ScriptDocValues)
|
||||
private final GeoPoint centroid = new GeoPoint();
|
||||
private final GeoBoundingBox boundingBox = new GeoBoundingBox(new GeoPoint(), new GeoPoint());
|
||||
private AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues geoShapeScriptValues;
|
||||
private LeafShapeFieldData.ShapeScriptValues<GeoPoint> geoShapeScriptValues;
|
||||
|
||||
public GeoShapeDocValuesField(GeoShapeValues in, String name) {
|
||||
public GeoShapeDocValuesField(ShapeValues in, String name) {
|
||||
this.in = in;
|
||||
this.name = name;
|
||||
}
|
||||
|
@ -364,8 +370,8 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
|
|||
@Override
|
||||
public void setNextDocId(int docId) throws IOException {
|
||||
if (in.advanceExact(docId)) {
|
||||
value = in.value();
|
||||
centroid.reset(value.lat(), value.lon());
|
||||
value = (GeoShapeValues.GeoShapeValue) in.value();
|
||||
centroid.reset(value.getY(), value.getX());
|
||||
boundingBox.topLeft().reset(value.boundingBox().maxY(), value.boundingBox().minX());
|
||||
boundingBox.bottomRight().reset(value.boundingBox().minY(), value.boundingBox().maxX());
|
||||
} else {
|
||||
|
@ -374,7 +380,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
|
|||
}
|
||||
|
||||
@Override
|
||||
public ScriptDocValues<GeoShapeValues.GeoShapeValue> toScriptDocValues() {
|
||||
public ScriptDocValues<ShapeValues.ShapeValue> toScriptDocValues() {
|
||||
if (geoShapeScriptValues == null) {
|
||||
geoShapeScriptValues = new AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues(this);
|
||||
}
|
||||
|
@ -383,7 +389,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
|
|||
}
|
||||
|
||||
@Override
|
||||
public GeoShapeValues.GeoShapeValue getInternal(int index) {
|
||||
public ShapeValues.ShapeValue getInternal(int index) {
|
||||
if (index != 0) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
@ -406,7 +412,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
|
|||
@Override
|
||||
public GeoPoint getInternalLabelPosition() {
|
||||
try {
|
||||
return value.labelPosition();
|
||||
return new GeoPoint(value.labelPosition());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to parse geo shape label position: " + e.getMessage(), e);
|
||||
}
|
||||
|
@ -440,8 +446,8 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
|
|||
}
|
||||
|
||||
@Override
|
||||
public Iterator<GeoShapeValues.GeoShapeValue> iterator() {
|
||||
return new Iterator<GeoShapeValues.GeoShapeValue>() {
|
||||
public Iterator<ShapeValues.ShapeValue> iterator() {
|
||||
return new Iterator<>() {
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
|
@ -450,7 +456,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
|
|||
}
|
||||
|
||||
@Override
|
||||
public GeoShapeValues.GeoShapeValue next() {
|
||||
public ShapeValues.ShapeValue next() {
|
||||
if (hasNext() == false) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.elasticsearch.geometry.Rectangle;
|
|||
import org.elasticsearch.geometry.utils.Geohash;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -33,7 +34,7 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int setValues(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geoValue) throws IOException {
|
||||
public int setValues(GeoShapeCellValues values, ShapeValues.ShapeValue geoValue) throws IOException {
|
||||
|
||||
if (precision == 0) {
|
||||
return 1;
|
||||
|
@ -51,11 +52,8 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
|
|||
return setValuesByRasterization("", values, 0, geoValue);
|
||||
}
|
||||
|
||||
protected int setValuesByBruteForceScan(
|
||||
GeoShapeCellValues values,
|
||||
GeoShapeValues.GeoShapeValue geoValue,
|
||||
GeoShapeValues.BoundingBox bounds
|
||||
) throws IOException {
|
||||
protected int setValuesByBruteForceScan(GeoShapeCellValues values, ShapeValues.ShapeValue geoValue, GeoShapeValues.BoundingBox bounds)
|
||||
throws IOException {
|
||||
// TODO: This way to discover cells inside of a bounding box seems not to work as expected. I can
|
||||
// see that eventually we will be visiting twice the same cell which should not happen.
|
||||
int idx = 0;
|
||||
|
@ -80,9 +78,9 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets a singular doc-value for the {@link GeoShapeValues.GeoShapeValue}.
|
||||
* Sets a singular doc-value for the {@link ShapeValues.ShapeValue}.
|
||||
*/
|
||||
protected int setValue(GeoShapeCellValues docValues, GeoShapeValues.GeoShapeValue geoValue, GeoShapeValues.BoundingBox bounds)
|
||||
protected int setValue(GeoShapeCellValues docValues, ShapeValues.ShapeValue geoValue, GeoShapeValues.BoundingBox bounds)
|
||||
throws IOException {
|
||||
String hash = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision);
|
||||
if (relateTile(geoValue, hash) != GeoRelation.QUERY_DISJOINT) {
|
||||
|
@ -93,7 +91,7 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
|
|||
return 0;
|
||||
}
|
||||
|
||||
private GeoRelation relateTile(GeoShapeValues.GeoShapeValue geoValue, String hash) throws IOException {
|
||||
private GeoRelation relateTile(ShapeValues.ShapeValue geoValue, String hash) throws IOException {
|
||||
if (validHash(hash)) {
|
||||
final Rectangle rectangle = Geohash.toBoundingBox(hash);
|
||||
int minX = GeoEncodingUtils.encodeLongitude(rectangle.getMinLon());
|
||||
|
@ -105,26 +103,26 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
|
|||
return GeoRelation.QUERY_DISJOINT;
|
||||
}
|
||||
|
||||
protected int setValuesByRasterization(String hash, GeoShapeCellValues values, int valuesIndex, GeoShapeValues.GeoShapeValue geoValue)
|
||||
protected int setValuesByRasterization(String hash, GeoShapeCellValues values, int valuesIndex, ShapeValues.ShapeValue geoValue)
|
||||
throws IOException {
|
||||
String[] hashes = Geohash.getSubGeohashes(hash);
|
||||
for (int i = 0; i < hashes.length; i++) {
|
||||
GeoRelation relation = relateTile(geoValue, hashes[i]);
|
||||
for (String s : hashes) {
|
||||
GeoRelation relation = relateTile(geoValue, s);
|
||||
if (relation == GeoRelation.QUERY_CROSSES) {
|
||||
if (hashes[i].length() == precision) {
|
||||
if (s.length() == precision) {
|
||||
values.resizeCell(valuesIndex + 1);
|
||||
values.add(valuesIndex++, Geohash.longEncode(hashes[i]));
|
||||
values.add(valuesIndex++, Geohash.longEncode(s));
|
||||
} else {
|
||||
valuesIndex = setValuesByRasterization(hashes[i], values, valuesIndex, geoValue);
|
||||
valuesIndex = setValuesByRasterization(s, values, valuesIndex, geoValue);
|
||||
}
|
||||
} else if (relation == GeoRelation.QUERY_INSIDE) {
|
||||
if (hashes[i].length() == precision) {
|
||||
if (s.length() == precision) {
|
||||
values.resizeCell(valuesIndex + 1);
|
||||
values.add(valuesIndex++, Geohash.longEncode(hashes[i]));
|
||||
values.add(valuesIndex++, Geohash.longEncode(s));
|
||||
} else {
|
||||
int numTilesAtPrecision = getNumTilesAtPrecision(precision, hash.length());
|
||||
values.resizeCell(getNewSize(valuesIndex, numTilesAtPrecision + 1));
|
||||
valuesIndex = setValuesForFullyContainedTile(hashes[i], values, valuesIndex, precision);
|
||||
valuesIndex = setValuesForFullyContainedTile(s, values, valuesIndex, precision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,12 +147,12 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
|
|||
|
||||
protected int setValuesForFullyContainedTile(String hash, GeoShapeCellValues values, int valuesIndex, int targetPrecision) {
|
||||
String[] hashes = Geohash.getSubGeohashes(hash);
|
||||
for (int i = 0; i < hashes.length; i++) {
|
||||
if (validHash(hashes[i])) {
|
||||
if (hashes[i].length() == targetPrecision) {
|
||||
values.add(valuesIndex++, Geohash.longEncode(hashes[i]));
|
||||
for (String s : hashes) {
|
||||
if (validHash(s)) {
|
||||
if (s.length() == targetPrecision) {
|
||||
values.add(valuesIndex++, Geohash.longEncode(s));
|
||||
} else {
|
||||
valuesIndex = setValuesForFullyContainedTile(hashes[i], values, valuesIndex, targetPrecision);
|
||||
valuesIndex = setValuesForFullyContainedTile(s, values, valuesIndex, targetPrecision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.apache.lucene.geo.GeoEncodingUtils;
|
|||
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -47,7 +48,7 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
|
|||
* @return the number of tiles set by the shape
|
||||
*/
|
||||
@Override
|
||||
public int setValues(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geoValue) throws IOException {
|
||||
public int setValues(GeoShapeCellValues values, ShapeValues.ShapeValue geoValue) throws IOException {
|
||||
GeoShapeValues.BoundingBox bounds = geoValue.boundingBox();
|
||||
assert bounds.minX() <= bounds.maxX();
|
||||
|
||||
|
@ -75,7 +76,7 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
|
|||
}
|
||||
}
|
||||
|
||||
private GeoRelation relateTile(GeoShapeValues.GeoShapeValue geoValue, int xTile, int yTile, int precision) throws IOException {
|
||||
private GeoRelation relateTile(ShapeValues.ShapeValue geoValue, int xTile, int yTile, int precision) throws IOException {
|
||||
if (validTile(xTile, yTile, precision)) {
|
||||
final double tiles = 1 << precision;
|
||||
final int minX = GeoEncodingUtils.encodeLongitude(GeoTileUtils.tileToLon(xTile, tiles));
|
||||
|
@ -112,7 +113,7 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
|
|||
*/
|
||||
protected int setValuesByBruteForceScan(
|
||||
GeoShapeCellValues values,
|
||||
GeoShapeValues.GeoShapeValue geoValue,
|
||||
ShapeValues.ShapeValue geoValue,
|
||||
int minXTile,
|
||||
int minYTile,
|
||||
int maxXTile,
|
||||
|
@ -137,7 +138,7 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
|
|||
int zTile,
|
||||
GeoShapeCellValues values,
|
||||
int valuesIndex,
|
||||
GeoShapeValues.GeoShapeValue geoValue
|
||||
ShapeValues.ShapeValue geoValue
|
||||
) throws IOException {
|
||||
zTile++;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
package org.elasticsearch.xpack.spatial.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -44,7 +44,7 @@ public abstract class GeoGridTiler {
|
|||
*
|
||||
* @return the number of cells the geoValue intersects
|
||||
*/
|
||||
public abstract int setValues(GeoShapeCellValues docValues, GeoShapeValues.GeoShapeValue geoValue) throws IOException;
|
||||
public abstract int setValues(GeoShapeCellValues docValues, ShapeValues.ShapeValue geoValue) throws IOException;
|
||||
|
||||
/** Maximum number of cells that can be created by this tiler */
|
||||
protected abstract long getMaxCells();
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
|
|||
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSource;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSource;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
|
||||
|
||||
|
@ -46,7 +46,7 @@ public class GeoShapeCellIdSource extends ValuesSource.Numeric {
|
|||
|
||||
@Override
|
||||
public SortedNumericDocValues longValues(LeafReaderContext ctx) {
|
||||
GeoShapeValues geoValues = valuesSource.geoShapeValues(ctx);
|
||||
ShapeValues geoValues = valuesSource.shapeValues(ctx);
|
||||
ValuesSourceType vs = geoValues.valuesSourceType();
|
||||
if (GeoShapeValuesSourceType.instance() == vs) {
|
||||
// docValues are geo shapes
|
||||
|
|
|
@ -7,17 +7,17 @@
|
|||
|
||||
package org.elasticsearch.xpack.spatial.search.aggregations.bucket.geogrid;
|
||||
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
/** Sorted numeric doc values for geo shapes */
|
||||
class GeoShapeCellValues extends ByteTrackingSortingNumericDocValues {
|
||||
private final GeoShapeValues geoShapeValues;
|
||||
private final ShapeValues geoShapeValues;
|
||||
protected final GeoGridTiler tiler;
|
||||
|
||||
protected GeoShapeCellValues(GeoShapeValues geoShapeValues, GeoGridTiler tiler, LongConsumer circuitBreakerConsumer) {
|
||||
protected GeoShapeCellValues(ShapeValues geoShapeValues, GeoGridTiler tiler, LongConsumer circuitBreakerConsumer) {
|
||||
super(circuitBreakerConsumer);
|
||||
this.geoShapeValues = geoShapeValues;
|
||||
this.tiler = tiler;
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.elasticsearch.search.aggregations.metrics.MetricsAggregator;
|
|||
import org.elasticsearch.search.aggregations.support.AggregationContext;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSource;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -66,13 +67,13 @@ public final class GeoShapeBoundsAggregator extends MetricsAggregator {
|
|||
if (valuesSource == null) {
|
||||
return LeafBucketCollector.NO_OP_COLLECTOR;
|
||||
}
|
||||
final GeoShapeValues values = valuesSource.geoShapeValues(aggCtx.getLeafReaderContext());
|
||||
final ShapeValues values = valuesSource.shapeValues(aggCtx.getLeafReaderContext());
|
||||
return new LeafBucketCollectorBase(sub, values) {
|
||||
@Override
|
||||
public void collect(int doc, long bucket) throws IOException {
|
||||
if (values.advanceExact(doc)) {
|
||||
maybeResize(bucket);
|
||||
final GeoShapeValues.GeoShapeValue value = values.value();
|
||||
final GeoShapeValues.ShapeValue value = values.value();
|
||||
final GeoShapeValues.BoundingBox bounds = value.boundingBox();
|
||||
tops.set(bucket, Math.max(tops.get(bucket), bounds.top));
|
||||
bottoms.set(bucket, Math.min(bottoms.get(bucket), bounds.bottom));
|
||||
|
|
|
@ -23,7 +23,7 @@ import org.elasticsearch.search.aggregations.metrics.MetricsAggregator;
|
|||
import org.elasticsearch.search.aggregations.support.AggregationContext;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceConfig;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.DimensionalShapeType;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSource;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -65,7 +65,7 @@ public final class GeoShapeCentroidAggregator extends MetricsAggregator {
|
|||
if (valuesSource == null) {
|
||||
return LeafBucketCollector.NO_OP_COLLECTOR;
|
||||
}
|
||||
final GeoShapeValues values = valuesSource.geoShapeValues(aggCtx.getLeafReaderContext());
|
||||
final ShapeValues values = valuesSource.shapeValues(aggCtx.getLeafReaderContext());
|
||||
final CompensatedSum compensatedSumLat = new CompensatedSum(0, 0);
|
||||
final CompensatedSum compensatedSumLon = new CompensatedSum(0, 0);
|
||||
final CompensatedSum compensatedSumWeight = new CompensatedSum(0, 0);
|
||||
|
@ -80,14 +80,14 @@ public final class GeoShapeCentroidAggregator extends MetricsAggregator {
|
|||
// Compute the sum of double values with Kahan summation algorithm which is more
|
||||
// accurate than naive summation.
|
||||
final DimensionalShapeType shapeType = DimensionalShapeType.fromOrdinalByte(dimensionalShapeTypes.get(bucket));
|
||||
final GeoShapeValues.GeoShapeValue value = values.value();
|
||||
final ShapeValues.ShapeValue value = values.value();
|
||||
final int compares = shapeType.compareTo(value.dimensionalShapeType());
|
||||
// update the sum
|
||||
if (compares < 0) {
|
||||
// shape with higher dimensional value
|
||||
final double coordinateWeight = value.weight();
|
||||
compensatedSumLat.reset(coordinateWeight * value.lat(), 0.0);
|
||||
compensatedSumLon.reset(coordinateWeight * value.lon(), 0.0);
|
||||
compensatedSumLat.reset(coordinateWeight * value.getY(), 0.0);
|
||||
compensatedSumLon.reset(coordinateWeight * value.getX(), 0.0);
|
||||
compensatedSumWeight.reset(coordinateWeight, 0.0);
|
||||
dimensionalShapeTypes.set(bucket, (byte) value.dimensionalShapeType().ordinal());
|
||||
} else if (compares == 0) {
|
||||
|
@ -96,8 +96,8 @@ public final class GeoShapeCentroidAggregator extends MetricsAggregator {
|
|||
compensatedSumLon.reset(lonSum.get(bucket), lonCompensations.get(bucket));
|
||||
compensatedSumWeight.reset(weightSum.get(bucket), weightCompensations.get(bucket));
|
||||
final double coordinateWeight = value.weight();
|
||||
compensatedSumLat.add(coordinateWeight * value.lat());
|
||||
compensatedSumLon.add(coordinateWeight * value.lon());
|
||||
compensatedSumLat.add(coordinateWeight * value.getY());
|
||||
compensatedSumLon.add(coordinateWeight * value.getX());
|
||||
compensatedSumWeight.add(coordinateWeight);
|
||||
} else {
|
||||
// do not modify centroid calculation since shape is of lower dimension than the running dimension
|
||||
|
|
|
@ -9,55 +9,34 @@ package org.elasticsearch.xpack.spatial.search.aggregations.support;
|
|||
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.elasticsearch.common.Rounding;
|
||||
import org.elasticsearch.index.fielddata.DocValueBits;
|
||||
import org.elasticsearch.index.fielddata.FieldData;
|
||||
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
|
||||
import org.elasticsearch.search.aggregations.AggregationExecutionException;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSource;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.IndexGeoShapeFieldData;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.IndexShapeFieldData;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
|
||||
public abstract class GeoShapeValuesSource extends ValuesSource {
|
||||
public abstract class GeoShapeValuesSource extends ShapeValuesSource {
|
||||
public static final GeoShapeValuesSource EMPTY = new GeoShapeValuesSource() {
|
||||
|
||||
@Override
|
||||
public GeoShapeValues geoShapeValues(LeafReaderContext context) {
|
||||
public GeoShapeValues shapeValues(LeafReaderContext context) {
|
||||
return GeoShapeValues.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException {
|
||||
return FieldData.emptySortedBinary();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public abstract GeoShapeValues geoShapeValues(LeafReaderContext context);
|
||||
|
||||
@Override
|
||||
protected Function<Rounding, Rounding.Prepared> roundingPreparer() throws IOException {
|
||||
throw new AggregationExecutionException("can't round a [geo_shape]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocValueBits docsWithValue(LeafReaderContext context) throws IOException {
|
||||
GeoShapeValues values = geoShapeValues(context);
|
||||
return new DocValueBits() {
|
||||
@Override
|
||||
public boolean advanceExact(int doc) throws IOException {
|
||||
return values.advanceExact(doc);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class Fielddata extends GeoShapeValuesSource {
|
||||
|
||||
protected final IndexGeoShapeFieldData indexFieldData;
|
||||
protected final IndexShapeFieldData indexFieldData;
|
||||
|
||||
public Fielddata(IndexGeoShapeFieldData indexFieldData) {
|
||||
public Fielddata(IndexShapeFieldData indexFieldData) {
|
||||
this.indexFieldData = indexFieldData;
|
||||
}
|
||||
|
||||
|
@ -66,8 +45,8 @@ public abstract class GeoShapeValuesSource extends ValuesSource {
|
|||
return indexFieldData.load(context).getBytesValues();
|
||||
}
|
||||
|
||||
public GeoShapeValues geoShapeValues(LeafReaderContext context) {
|
||||
return indexFieldData.load(context).getGeoShapeValues();
|
||||
public ShapeValues shapeValues(LeafReaderContext context) {
|
||||
return indexFieldData.load(context).getShapeValues();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ package org.elasticsearch.xpack.spatial.search.aggregations.support;
|
|||
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
|
||||
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
|
||||
import org.elasticsearch.script.AggregationScript;
|
||||
|
@ -18,15 +16,16 @@ import org.elasticsearch.search.DocValueFormat;
|
|||
import org.elasticsearch.search.aggregations.support.AggregationContext;
|
||||
import org.elasticsearch.search.aggregations.support.FieldContext;
|
||||
import org.elasticsearch.search.aggregations.support.MissingValues;
|
||||
import org.elasticsearch.search.aggregations.support.ValueType;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSource;
|
||||
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.IndexGeoShapeFieldData;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.IndexShapeFieldData;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues;
|
||||
import org.elasticsearch.xpack.spatial.index.fielddata.plain.LatLonShapeIndexFieldData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
|
||||
public class GeoShapeValuesSourceType extends ShapeValuesSourceType {
|
||||
|
||||
static GeoShapeValuesSourceType INSTANCE = new GeoShapeValuesSourceType();
|
||||
|
||||
|
@ -39,17 +38,11 @@ public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
|
|||
return GeoShapeValuesSource.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValuesSource getScript(AggregationScript.LeafFactory script, ValueType scriptValueType) {
|
||||
// TODO (support scripts)
|
||||
throw new UnsupportedOperationException("geo_shape");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValuesSource getField(FieldContext fieldContext, AggregationScript.LeafFactory script, AggregationContext context) {
|
||||
boolean isGeoPoint = fieldContext.indexFieldData() instanceof IndexGeoPointFieldData;
|
||||
boolean isGeoShape = fieldContext.indexFieldData() instanceof IndexGeoShapeFieldData;
|
||||
if (isGeoPoint == false && isGeoShape == false) {
|
||||
boolean isPoint = fieldContext.indexFieldData() instanceof IndexGeoPointFieldData;
|
||||
boolean isShape = fieldContext.indexFieldData() instanceof IndexShapeFieldData;
|
||||
if (isPoint == false && isShape == false) {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected geo_point or geo_shape type on field ["
|
||||
+ fieldContext.field()
|
||||
|
@ -58,10 +51,10 @@ public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
|
|||
+ "]"
|
||||
);
|
||||
}
|
||||
if (isGeoPoint) {
|
||||
if (isPoint) {
|
||||
return new ValuesSource.GeoPoint.Fielddata((IndexGeoPointFieldData) fieldContext.indexFieldData());
|
||||
}
|
||||
return new GeoShapeValuesSource.Fielddata((IndexGeoShapeFieldData) fieldContext.indexFieldData());
|
||||
return new GeoShapeValuesSource.Fielddata((LatLonShapeIndexFieldData) fieldContext.indexFieldData());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -71,12 +64,12 @@ public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
|
|||
DocValueFormat docValueFormat,
|
||||
AggregationContext context
|
||||
) {
|
||||
GeoShapeValuesSource geoShapeValuesSource = (GeoShapeValuesSource) valuesSource;
|
||||
final GeoShapeValues.GeoShapeValue missing = GeoShapeValues.GeoShapeValue.missing(rawMissing.toString());
|
||||
GeoShapeValuesSource shapeValuesSource = (GeoShapeValuesSource) valuesSource;
|
||||
final ShapeValues.ShapeValue missing = GeoShapeValues.EMPTY.missing(rawMissing.toString());
|
||||
return new GeoShapeValuesSource() {
|
||||
@Override
|
||||
public GeoShapeValues geoShapeValues(LeafReaderContext context) {
|
||||
GeoShapeValues values = geoShapeValuesSource.geoShapeValues(context);
|
||||
public GeoShapeValues shapeValues(LeafReaderContext context) {
|
||||
ShapeValues values = shapeValuesSource.shapeValues(context);
|
||||
return new GeoShapeValues() {
|
||||
|
||||
private boolean exists;
|
||||
|
@ -95,20 +88,20 @@ public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public GeoShapeValue value() throws IOException {
|
||||
public ShapeValue value() throws IOException {
|
||||
return exists ? values.value() : missing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "anon MultiGeoShapeValues of [" + super.toString() + "]";
|
||||
return "anon MultiShapeValues of [" + super.toString() + "]";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException {
|
||||
return MissingValues.replaceMissing(geoShapeValuesSource.bytesValues(context), new BytesRef(missing.toString()));
|
||||
return MissingValues.replaceMissing(valuesSource.bytesValues(context), new BytesRef(missing.toString()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -117,9 +110,4 @@ public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
|
|||
public String typeName() {
|
||||
return "geoshape";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -148,25 +148,25 @@ public class CentroidCalculatorTests extends ESTestCase {
|
|||
{
|
||||
Line line = new Line(new double[] { 180.0, 180.0 }, new double[] { latA, latB });
|
||||
GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line);
|
||||
assertThat(value.lon(), anyOf(equalTo(179.99999991618097), equalTo(-180.0)));
|
||||
assertThat(value.getX(), anyOf(equalTo(179.99999991618097), equalTo(-180.0)));
|
||||
}
|
||||
|
||||
{
|
||||
Line line = new Line(new double[] { -180.0, -180.0 }, new double[] { latA, latB });
|
||||
GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line);
|
||||
assertThat(value.lon(), anyOf(equalTo(179.99999991618097), equalTo(-180.0)));
|
||||
assertThat(value.getX(), anyOf(equalTo(179.99999991618097), equalTo(-180.0)));
|
||||
}
|
||||
|
||||
{
|
||||
Line line = new Line(new double[] { lonA, lonB }, new double[] { 90.0, 90.0 });
|
||||
GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line);
|
||||
assertThat(value.lat(), equalTo(89.99999995809048));
|
||||
assertThat(value.getY(), equalTo(89.99999995809048));
|
||||
}
|
||||
|
||||
{
|
||||
Line line = new Line(new double[] { lonA, lonB }, new double[] { -90.0, -90.0 });
|
||||
GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line);
|
||||
assertThat(value.lat(), equalTo(-90.0));
|
||||
assertThat(value.getY(), equalTo(-90.0));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.apache.lucene.geo.Circle;
|
|||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.GeometryNormalizer;
|
||||
import org.elasticsearch.common.geo.Orientation;
|
||||
import org.elasticsearch.common.geo.SpatialPoint;
|
||||
import org.elasticsearch.geometry.Geometry;
|
||||
import org.elasticsearch.geometry.GeometryCollection;
|
||||
import org.elasticsearch.geometry.LinearRing;
|
||||
|
@ -133,11 +134,11 @@ public class GeometryDocValueTests extends ESTestCase {
|
|||
|
||||
// Label position is the centroid if within the polygon
|
||||
GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(rectangle);
|
||||
GeoPoint labelPosition = shapeValue.labelPosition();
|
||||
double labelLon = ((double) minX + maxX) / 2;
|
||||
double labelLat = ((double) minY + maxY) / 2;
|
||||
assertEquals(labelLon, labelPosition.lon(), 0.0000001);
|
||||
assertEquals(labelLat, labelPosition.lat(), 0.0000001);
|
||||
SpatialPoint labelPosition = shapeValue.labelPosition();
|
||||
double labelX = ((double) minX + maxX) / 2;
|
||||
double labelY = ((double) minY + maxY) / 2;
|
||||
assertEquals(labelX, labelPosition.getX(), 0.0000001);
|
||||
assertEquals(labelY, labelPosition.getY(), 0.0000001);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,10 +156,10 @@ public class GeometryDocValueTests extends ESTestCase {
|
|||
|
||||
// Label position is calculated as the first triangle
|
||||
GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry);
|
||||
GeoPoint labelPosition = shapeValue.labelPosition();
|
||||
SpatialPoint labelPosition = shapeValue.labelPosition();
|
||||
assertThat(
|
||||
"Expect label position to match one of eight triangles in the two rectangles",
|
||||
labelPosition,
|
||||
new GeoPoint(labelPosition),
|
||||
isRectangleLabelPosition(r1, r2)
|
||||
);
|
||||
}
|
||||
|
@ -177,11 +178,11 @@ public class GeometryDocValueTests extends ESTestCase {
|
|||
|
||||
// Label position is the centroid if within the polygon
|
||||
GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry);
|
||||
GeoPoint labelPosition = shapeValue.labelPosition();
|
||||
SpatialPoint labelPosition = shapeValue.labelPosition();
|
||||
double centroidX = CoordinateEncoder.GEO.decodeX(reader.getCentroidX());
|
||||
double centroidY = CoordinateEncoder.GEO.decodeY(reader.getCentroidY());
|
||||
assertEquals(centroidX, labelPosition.lon(), 0.0000001);
|
||||
assertEquals(centroidY, labelPosition.lat(), 0.0000001);
|
||||
assertEquals(centroidX, labelPosition.getX(), 0.0000001);
|
||||
assertEquals(centroidY, labelPosition.getY(), 0.0000001);
|
||||
Circle tolerance = new Circle(centroidY, centroidX, 1);
|
||||
assertTrue("Expect label position to be within the geometry", shapeValue.relate(tolerance) != GeoRelation.QUERY_DISJOINT);
|
||||
}
|
||||
|
@ -192,11 +193,11 @@ public class GeometryDocValueTests extends ESTestCase {
|
|||
|
||||
// Label position is the centroid if within the polygon
|
||||
GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry);
|
||||
GeoPoint labelPosition = shapeValue.labelPosition();
|
||||
SpatialPoint labelPosition = shapeValue.labelPosition();
|
||||
double centroidX = CoordinateEncoder.GEO.decodeX(reader.getCentroidX());
|
||||
double centroidY = CoordinateEncoder.GEO.decodeY(reader.getCentroidY());
|
||||
assertEquals(centroidX, labelPosition.lon(), 0.0000001);
|
||||
assertEquals(centroidY, labelPosition.lat(), 0.0000001);
|
||||
assertEquals(centroidX, labelPosition.getX(), 0.0000001);
|
||||
assertEquals(centroidY, labelPosition.getY(), 0.0000001);
|
||||
Circle tolerance = new Circle(centroidY, centroidX, 1);
|
||||
assertTrue("Expect label position to be within the geometry", shapeValue.relate(tolerance) != GeoRelation.QUERY_DISJOINT);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -11,25 +11,23 @@ import org.elasticsearch.Version;
|
|||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.geo.Orientation;
|
||||
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
||||
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
|
||||
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper.AbstractShapeGeometryFieldType;
|
||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.index.mapper.Mapper;
|
||||
import org.elasticsearch.index.mapper.MapperParsingException;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.index.mapper.MapperTestCase;
|
||||
import org.elasticsearch.index.mapper.ParsedDocument;
|
||||
import org.elasticsearch.index.mapper.SourceToParse;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.test.VersionUtils;
|
||||
import org.elasticsearch.xcontent.ToXContent;
|
||||
import org.elasticsearch.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.xcontent.XContentFactory;
|
||||
import org.elasticsearch.xcontent.XContentType;
|
||||
import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin;
|
||||
import org.junit.AssumptionViolatedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
@ -37,16 +35,11 @@ import static org.hamcrest.Matchers.equalTo;
|
|||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
|
||||
public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
||||
public class GeoShapeWithDocValuesFieldMapperTests extends GeoFieldMapperTests {
|
||||
|
||||
@Override
|
||||
protected void minimalMapping(XContentBuilder b) throws IOException {
|
||||
b.field("type", "geo_shape");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getSampleValueForDocument() {
|
||||
return "POINT (14.0 15.0)";
|
||||
protected String getFieldName() {
|
||||
return "geo_shape";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,45 +57,47 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
checker.registerConflictCheck("doc_values", b -> b.field("doc_values", false));
|
||||
checker.registerConflictCheck("index", b -> b.field("index", false));
|
||||
checker.registerUpdateCheck(b -> b.field("orientation", "right"), m -> {
|
||||
GeoShapeWithDocValuesFieldMapper gsfm = (GeoShapeWithDocValuesFieldMapper) m;
|
||||
AbstractShapeGeometryFieldMapper<?> gsfm = (AbstractShapeGeometryFieldMapper<?>) m;
|
||||
assertEquals(Orientation.RIGHT, gsfm.orientation());
|
||||
});
|
||||
checker.registerUpdateCheck(b -> b.field("ignore_malformed", true), m -> {
|
||||
GeoShapeWithDocValuesFieldMapper gpfm = (GeoShapeWithDocValuesFieldMapper) m;
|
||||
AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
|
||||
assertTrue(gpfm.ignoreMalformed());
|
||||
});
|
||||
checker.registerUpdateCheck(b -> b.field("ignore_z_value", false), m -> {
|
||||
GeoShapeWithDocValuesFieldMapper gpfm = (GeoShapeWithDocValuesFieldMapper) m;
|
||||
AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
|
||||
assertFalse(gpfm.ignoreZValue());
|
||||
});
|
||||
checker.registerUpdateCheck(b -> b.field("coerce", true), m -> {
|
||||
GeoShapeWithDocValuesFieldMapper gpfm = (GeoShapeWithDocValuesFieldMapper) m;
|
||||
AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
|
||||
assertTrue(gpfm.coerce());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Plugin> getPlugins() {
|
||||
return Collections.singletonList(new LocalStateSpatialPlugin());
|
||||
protected AbstractShapeGeometryFieldType<?> fieldType(Mapper fieldMapper) {
|
||||
AbstractShapeGeometryFieldMapper<?> shapeFieldMapper = (AbstractShapeGeometryFieldMapper<?>) fieldMapper;
|
||||
return (AbstractShapeGeometryFieldType<?>) shapeFieldMapper.fieldType();
|
||||
}
|
||||
|
||||
protected Class<? extends AbstractShapeGeometryFieldMapper<?>> fieldMapperClass() {
|
||||
return GeoShapeWithDocValuesFieldMapper.class;
|
||||
}
|
||||
|
||||
public void testDefaultConfiguration() throws IOException {
|
||||
|
||||
DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping));
|
||||
Mapper fieldMapper = mapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
Mapper fieldMapper = mapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
GeoShapeWithDocValuesFieldMapper geoShapeFieldMapper = (GeoShapeWithDocValuesFieldMapper) fieldMapper;
|
||||
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(Orientation.RIGHT));
|
||||
assertTrue(geoShapeFieldMapper.fieldType().hasDocValues());
|
||||
AbstractShapeGeometryFieldType<?> fieldType = fieldType(fieldMapper);
|
||||
assertThat(fieldType.orientation(), equalTo(Orientation.RIGHT));
|
||||
assertTrue(fieldType.hasDocValues());
|
||||
}
|
||||
|
||||
public void testDefaultDocValueConfigurationOnPre7_8() throws IOException {
|
||||
|
||||
Version oldVersion = VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_7_0);
|
||||
DocumentMapper defaultMapper = createDocumentMapper(oldVersion, fieldMapping(this::minimalMapping));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
GeoShapeWithDocValuesFieldMapper geoShapeFieldMapper = (GeoShapeWithDocValuesFieldMapper) fieldMapper;
|
||||
assertFalse(geoShapeFieldMapper.fieldType().hasDocValues());
|
||||
|
@ -114,26 +109,28 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
public void testOrientationParsing() throws IOException {
|
||||
|
||||
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("orientation", "left");
|
||||
}));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
Orientation orientation = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).fieldType().orientation();
|
||||
AbstractShapeGeometryFieldType<?> fieldType = fieldType(fieldMapper);
|
||||
Orientation orientation = fieldType.orientation();
|
||||
assertThat(orientation, equalTo(Orientation.CLOCKWISE));
|
||||
assertThat(orientation, equalTo(Orientation.LEFT));
|
||||
assertThat(orientation, equalTo(Orientation.CW));
|
||||
|
||||
// explicit right orientation test
|
||||
defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("orientation", "right");
|
||||
}));
|
||||
fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
orientation = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).fieldType().orientation();
|
||||
fieldType = fieldType(fieldMapper);
|
||||
orientation = fieldType.orientation();
|
||||
assertThat(orientation, equalTo(Orientation.COUNTER_CLOCKWISE));
|
||||
assertThat(orientation, equalTo(Orientation.RIGHT));
|
||||
assertThat(orientation, equalTo(Orientation.CCW));
|
||||
|
@ -145,23 +142,23 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
public void testCoerceParsing() throws IOException {
|
||||
|
||||
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("coerce", true);
|
||||
}));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
boolean coerce = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).coerce();
|
||||
boolean coerce = ((AbstractShapeGeometryFieldMapper<?>) fieldMapper).coerce();
|
||||
assertThat(coerce, equalTo(true));
|
||||
|
||||
defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("coerce", false);
|
||||
}));
|
||||
fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
coerce = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).coerce();
|
||||
coerce = ((AbstractShapeGeometryFieldMapper<?>) fieldMapper).coerce();
|
||||
assertThat(coerce, equalTo(false));
|
||||
|
||||
}
|
||||
|
@ -171,24 +168,24 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
*/
|
||||
public void testIgnoreZValue() throws IOException {
|
||||
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("ignore_z_value", true);
|
||||
}));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
boolean ignoreZValue = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreZValue();
|
||||
boolean ignoreZValue = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreZValue();
|
||||
assertThat(ignoreZValue, equalTo(true));
|
||||
|
||||
// explicit false accept_z_value test
|
||||
defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("ignore_z_value", false);
|
||||
}));
|
||||
fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
ignoreZValue = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreZValue();
|
||||
ignoreZValue = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreZValue();
|
||||
assertThat(ignoreZValue, equalTo(false));
|
||||
}
|
||||
|
||||
|
@ -198,45 +195,45 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
public void testIgnoreMalformedParsing() throws IOException {
|
||||
|
||||
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("ignore_malformed", true);
|
||||
}));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
boolean ignoreMalformed = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreMalformed();
|
||||
boolean ignoreMalformed = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreMalformed();
|
||||
assertThat(ignoreMalformed, equalTo(true));
|
||||
|
||||
// explicit false ignore_malformed test
|
||||
defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("ignore_malformed", false);
|
||||
}));
|
||||
fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
ignoreMalformed = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreMalformed();
|
||||
ignoreMalformed = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreMalformed();
|
||||
assertThat(ignoreMalformed, equalTo(false));
|
||||
}
|
||||
|
||||
public void testIgnoreMalformedValues() throws IOException {
|
||||
|
||||
DocumentMapper ignoreMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("ignore_malformed", true);
|
||||
}));
|
||||
DocumentMapper failMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("ignore_malformed", false);
|
||||
}));
|
||||
|
||||
{
|
||||
BytesReference arrayedDoc = BytesReference.bytes(
|
||||
XContentFactory.jsonBuilder().startObject().field("field", "Bad shape").endObject()
|
||||
XContentFactory.jsonBuilder().startObject().field(FIELD_NAME, "Bad shape").endObject()
|
||||
);
|
||||
SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON);
|
||||
ParsedDocument document = ignoreMapper.parse(sourceToParse);
|
||||
assertThat(document.docs().get(0).getFields("field").length, equalTo(0));
|
||||
assertThat(document.docs().get(0).getFields(FIELD_NAME).length, equalTo(0));
|
||||
MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse));
|
||||
assertThat(exception.getCause().getMessage(), containsString("Unknown geometry type: bad"));
|
||||
}
|
||||
|
@ -245,7 +242,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
XContentFactory.jsonBuilder()
|
||||
.startObject()
|
||||
.field(
|
||||
"field",
|
||||
FIELD_NAME,
|
||||
"POLYGON ((18.9401790919516 -33.9681188869036, 18.9401790919516 -33.9681188869036, 18.9401790919517 "
|
||||
+ "-33.9681188869036, 18.9401790919517 -33.9681188869036, 18.9401790919516 -33.9681188869036))"
|
||||
)
|
||||
|
@ -253,7 +250,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
);
|
||||
SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON);
|
||||
ParsedDocument document = ignoreMapper.parse(sourceToParse);
|
||||
assertThat(document.docs().get(0).getFields("field").length, equalTo(0));
|
||||
assertThat(document.docs().get(0).getFields(FIELD_NAME).length, equalTo(0));
|
||||
MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse));
|
||||
assertThat(exception.getCause().getMessage(), containsString("at least three non-collinear points required"));
|
||||
}
|
||||
|
@ -265,43 +262,43 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
public void testDocValues() throws IOException {
|
||||
|
||||
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("doc_values", true);
|
||||
}));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
boolean hasDocValues = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).fieldType().hasDocValues();
|
||||
boolean hasDocValues = ((AbstractGeometryFieldMapper<?>) fieldMapper).fieldType().hasDocValues();
|
||||
assertTrue(hasDocValues);
|
||||
|
||||
// explicit false doc_values
|
||||
defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("doc_values", false);
|
||||
}));
|
||||
fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
hasDocValues = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).fieldType().hasDocValues();
|
||||
hasDocValues = ((AbstractGeometryFieldMapper<?>) fieldMapper).fieldType().hasDocValues();
|
||||
assertFalse(hasDocValues);
|
||||
}
|
||||
|
||||
public void testGeoShapeMapperMerge() throws Exception {
|
||||
public void testShapeMapperMerge() throws Exception {
|
||||
MapperService mapperService = createMapperService(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("orientation", "ccw");
|
||||
}));
|
||||
|
||||
merge(mapperService, fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("orientation", "cw");
|
||||
}));
|
||||
|
||||
Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class));
|
||||
Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
GeoShapeWithDocValuesFieldMapper geoShapeFieldMapper = (GeoShapeWithDocValuesFieldMapper) fieldMapper;
|
||||
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(Orientation.CW));
|
||||
AbstractShapeGeometryFieldType<?> fieldType = fieldType(fieldMapper);
|
||||
assertThat(fieldType.orientation(), equalTo(Orientation.CW));
|
||||
}
|
||||
|
||||
public void testInvalidCurrentVersion() {
|
||||
|
@ -309,7 +306,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
MapperParsingException.class,
|
||||
() -> super.createMapperService(
|
||||
Version.CURRENT,
|
||||
fieldMapping((b) -> { b.field("type", "geo_shape").field("strategy", "recursive"); })
|
||||
fieldMapping((b) -> { b.field("type", getFieldName()).field("strategy", "recursive"); })
|
||||
)
|
||||
);
|
||||
assertThat(
|
||||
|
@ -320,17 +317,17 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
|
||||
public void testGeoShapeLegacyMerge() throws Exception {
|
||||
Version version = VersionUtils.randomPreviousCompatibleVersion(random(), Version.V_8_0_0);
|
||||
MapperService m = createMapperService(version, fieldMapping(b -> b.field("type", "geo_shape")));
|
||||
MapperService m = createMapperService(version, fieldMapping(b -> b.field("type", getFieldName())));
|
||||
Exception e = expectThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> merge(m, fieldMapping(b -> b.field("type", "geo_shape").field("strategy", "recursive")))
|
||||
() -> merge(m, fieldMapping(b -> b.field("type", getFieldName()).field("strategy", "recursive")))
|
||||
);
|
||||
|
||||
assertThat(e.getMessage(), containsString("mapper [field] of type [geo_shape] cannot change strategy from [BKD] to [recursive]"));
|
||||
assertFieldWarnings("strategy");
|
||||
|
||||
MapperService lm = createMapperService(version, fieldMapping(b -> b.field("type", "geo_shape").field("strategy", "recursive")));
|
||||
e = expectThrows(IllegalArgumentException.class, () -> merge(lm, fieldMapping(b -> b.field("type", "geo_shape"))));
|
||||
MapperService lm = createMapperService(version, fieldMapping(b -> b.field("type", getFieldName()).field("strategy", "recursive")));
|
||||
e = expectThrows(IllegalArgumentException.class, () -> merge(lm, fieldMapping(b -> b.field("type", getFieldName()))));
|
||||
assertThat(e.getMessage(), containsString("mapper [field] of type [geo_shape] cannot change strategy from [recursive] to [BKD]"));
|
||||
assertFieldWarnings("strategy");
|
||||
}
|
||||
|
@ -345,7 +342,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
|
||||
public void testSerializeDefaults() throws Exception {
|
||||
DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(this::minimalMapping));
|
||||
String serialized = toXContentString((GeoShapeWithDocValuesFieldMapper) defaultMapper.mappers().getMapper("field"));
|
||||
String serialized = toXContentString((GeoShapeWithDocValuesFieldMapper) defaultMapper.mappers().getMapper(FIELD_NAME));
|
||||
assertTrue(serialized, serialized.contains("\"orientation\":\"" + Orientation.RIGHT + "\""));
|
||||
assertTrue(serialized, serialized.contains("\"doc_values\":true"));
|
||||
}
|
||||
|
@ -353,22 +350,20 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
public void testSerializeDocValues() throws IOException {
|
||||
boolean docValues = randomBoolean();
|
||||
DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> {
|
||||
b.field("type", "geo_shape");
|
||||
b.field("type", getFieldName());
|
||||
b.field("doc_values", docValues);
|
||||
}));
|
||||
String serialized = toXContentString((GeoShapeWithDocValuesFieldMapper) mapper.mappers().getMapper("field"));
|
||||
String serialized = toXContentString((GeoShapeWithDocValuesFieldMapper) mapper.mappers().getMapper(FIELD_NAME));
|
||||
assertTrue(serialized, serialized.contains("\"orientation\":\"" + Orientation.RIGHT + "\""));
|
||||
assertTrue(serialized, serialized.contains("\"doc_values\":" + docValues));
|
||||
}
|
||||
|
||||
public void testGeoShapeArrayParsing() throws Exception {
|
||||
public void testShapeArrayParsing() throws Exception {
|
||||
|
||||
DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping));
|
||||
|
||||
BytesReference arrayedDoc = BytesReference.bytes(
|
||||
XContentFactory.jsonBuilder()
|
||||
.startObject()
|
||||
.startArray("shape")
|
||||
SourceToParse sourceToParse = source(b -> {
|
||||
b.startArray("shape")
|
||||
.startObject()
|
||||
.field("type", "Point")
|
||||
.startArray("coordinates")
|
||||
|
@ -383,11 +378,9 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
.value(-15.0)
|
||||
.endArray()
|
||||
.endObject()
|
||||
.endArray()
|
||||
.endObject()
|
||||
);
|
||||
.endArray();
|
||||
});
|
||||
|
||||
SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON);
|
||||
ParsedDocument document = mapper.parse(sourceToParse);
|
||||
assertThat(document.docs(), hasSize(1));
|
||||
IndexableField[] fields = document.docs().get(0).getFields("shape.type");
|
||||
|
@ -401,7 +394,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
b.startObject("keyword").field("type", "keyword").endObject();
|
||||
b.endObject();
|
||||
}));
|
||||
assertWarnings("Adding multifields to [geo_shape] mappers has no effect and will be forbidden in future");
|
||||
assertWarnings("Adding multifields to [" + getFieldName() + "] mappers has no effect and will be forbidden in future");
|
||||
}
|
||||
|
||||
public void testSelfIntersectPolygon() throws IOException {
|
||||
|
@ -413,7 +406,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
assertThat(ex.getCause().getMessage(), containsString("Polygon self-intersection at lat=0.5 lon=0.5"));
|
||||
}
|
||||
|
||||
public String toXContentString(GeoShapeWithDocValuesFieldMapper mapper, boolean includeDefaults) {
|
||||
public String toXContentString(AbstractShapeGeometryFieldMapper<?> mapper, boolean includeDefaults) {
|
||||
if (includeDefaults) {
|
||||
ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true"));
|
||||
return Strings.toString(mapper, params);
|
||||
|
@ -422,7 +415,7 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public String toXContentString(GeoShapeWithDocValuesFieldMapper mapper) throws IOException {
|
||||
public String toXContentString(AbstractShapeGeometryFieldMapper<?> mapper) {
|
||||
return toXContentString(mapper, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ import org.elasticsearch.Version;
|
|||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.geo.Orientation;
|
||||
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
||||
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
|
||||
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper.AbstractShapeGeometryFieldType;
|
||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.index.mapper.Mapper;
|
||||
|
@ -68,40 +71,51 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
|
||||
@Override
|
||||
protected void registerParameters(ParameterChecker checker) throws IOException {
|
||||
checker.registerConflictCheck("doc_values", b -> b.field("doc_values", false));
|
||||
checker.registerConflictCheck("index", b -> b.field("index", false));
|
||||
checker.registerUpdateCheck(b -> b.field("orientation", "right"), m -> {
|
||||
ShapeFieldMapper gsfm = (ShapeFieldMapper) m;
|
||||
AbstractShapeGeometryFieldMapper<?> gsfm = (AbstractShapeGeometryFieldMapper<?>) m;
|
||||
assertEquals(Orientation.RIGHT, gsfm.orientation());
|
||||
});
|
||||
checker.registerUpdateCheck(b -> b.field("ignore_malformed", true), m -> {
|
||||
ShapeFieldMapper gpfm = (ShapeFieldMapper) m;
|
||||
AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
|
||||
assertTrue(gpfm.ignoreMalformed());
|
||||
});
|
||||
checker.registerUpdateCheck(b -> b.field("ignore_z_value", false), m -> {
|
||||
ShapeFieldMapper gpfm = (ShapeFieldMapper) m;
|
||||
AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
|
||||
assertFalse(gpfm.ignoreZValue());
|
||||
});
|
||||
checker.registerUpdateCheck(b -> b.field("coerce", true), m -> {
|
||||
ShapeFieldMapper gpfm = (ShapeFieldMapper) m;
|
||||
AbstractShapeGeometryFieldMapper<?> gpfm = (AbstractShapeGeometryFieldMapper<?>) m;
|
||||
assertTrue(gpfm.coerce());
|
||||
});
|
||||
}
|
||||
|
||||
protected AbstractShapeGeometryFieldType<?> fieldType(Mapper fieldMapper) {
|
||||
AbstractShapeGeometryFieldMapper<?> shapeFieldMapper = (AbstractShapeGeometryFieldMapper<?>) fieldMapper;
|
||||
return (AbstractShapeGeometryFieldType<?>) shapeFieldMapper.fieldType();
|
||||
}
|
||||
|
||||
protected Class<? extends AbstractShapeGeometryFieldMapper<?>> fieldMapperClass() {
|
||||
return ShapeFieldMapper.class;
|
||||
}
|
||||
|
||||
public void testDefaultConfiguration() throws IOException {
|
||||
DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping));
|
||||
Mapper fieldMapper = mapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
ShapeFieldMapper shapeFieldMapper = (ShapeFieldMapper) fieldMapper;
|
||||
assertThat(shapeFieldMapper.fieldType().orientation(), equalTo(Orientation.RIGHT));
|
||||
assertTrue(shapeFieldMapper.fieldType().hasDocValues());
|
||||
AbstractShapeGeometryFieldType<?> fieldType = fieldType(fieldMapper);
|
||||
assertThat(fieldType.orientation(), equalTo(Orientation.RIGHT));
|
||||
assertTrue(fieldType.hasDocValues());
|
||||
}
|
||||
|
||||
public void testDefaultDocValueConfigurationOnPre8_4() throws IOException {
|
||||
// TODO verify which version this test is actually valid for (when PR is actually merged)
|
||||
Version oldVersion = VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_8_3_0);
|
||||
DocumentMapper defaultMapper = createDocumentMapper(oldVersion, fieldMapping(this::minimalMapping));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
ShapeFieldMapper shapeFieldMapper = (ShapeFieldMapper) fieldMapper;
|
||||
assertFalse(shapeFieldMapper.fieldType().hasDocValues());
|
||||
|
@ -117,9 +131,10 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("orientation", "left");
|
||||
}));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
Orientation orientation = ((ShapeFieldMapper) fieldMapper).fieldType().orientation();
|
||||
AbstractShapeGeometryFieldType<?> fieldType = fieldType(fieldMapper);
|
||||
Orientation orientation = fieldType.orientation();
|
||||
assertThat(orientation, equalTo(Orientation.CLOCKWISE));
|
||||
assertThat(orientation, equalTo(Orientation.LEFT));
|
||||
assertThat(orientation, equalTo(Orientation.CW));
|
||||
|
@ -130,9 +145,10 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("orientation", "right");
|
||||
}));
|
||||
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
orientation = ((ShapeFieldMapper) fieldMapper).fieldType().orientation();
|
||||
fieldType = fieldType(fieldMapper);
|
||||
orientation = fieldType.orientation();
|
||||
assertThat(orientation, equalTo(Orientation.COUNTER_CLOCKWISE));
|
||||
assertThat(orientation, equalTo(Orientation.RIGHT));
|
||||
assertThat(orientation, equalTo(Orientation.CCW));
|
||||
|
@ -148,9 +164,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("coerce", true);
|
||||
}));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
boolean coerce = ((ShapeFieldMapper) fieldMapper).coerce();
|
||||
boolean coerce = ((AbstractShapeGeometryFieldMapper<?>) fieldMapper).coerce();
|
||||
assertThat(coerce, equalTo(true));
|
||||
|
||||
defaultMapper = createDocumentMapper(fieldMapping(b -> {
|
||||
|
@ -158,9 +174,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("coerce", false);
|
||||
}));
|
||||
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
coerce = ((ShapeFieldMapper) fieldMapper).coerce();
|
||||
coerce = ((AbstractShapeGeometryFieldMapper<?>) fieldMapper).coerce();
|
||||
assertThat(coerce, equalTo(false));
|
||||
|
||||
}
|
||||
|
@ -174,9 +190,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("ignore_z_value", true);
|
||||
}));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
boolean ignoreZValue = ((ShapeFieldMapper) fieldMapper).ignoreZValue();
|
||||
boolean ignoreZValue = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreZValue();
|
||||
assertThat(ignoreZValue, equalTo(true));
|
||||
|
||||
// explicit false accept_z_value test
|
||||
|
@ -185,9 +201,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("ignore_z_value", false);
|
||||
}));
|
||||
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
ignoreZValue = ((ShapeFieldMapper) fieldMapper).ignoreZValue();
|
||||
ignoreZValue = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreZValue();
|
||||
assertThat(ignoreZValue, equalTo(false));
|
||||
}
|
||||
|
||||
|
@ -201,9 +217,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("ignore_malformed", true);
|
||||
}));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
boolean ignoreMalformed = ((ShapeFieldMapper) fieldMapper).ignoreMalformed();
|
||||
boolean ignoreMalformed = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreMalformed();
|
||||
assertThat(ignoreMalformed, equalTo(true));
|
||||
|
||||
// explicit false ignore_malformed test
|
||||
|
@ -212,9 +228,9 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("ignore_malformed", false);
|
||||
}));
|
||||
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
ignoreMalformed = ((ShapeFieldMapper) fieldMapper).ignoreMalformed();
|
||||
ignoreMalformed = ((AbstractGeometryFieldMapper<?>) fieldMapper).ignoreMalformed();
|
||||
assertThat(ignoreMalformed, equalTo(false));
|
||||
}
|
||||
|
||||
|
@ -231,11 +247,11 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
|
||||
{
|
||||
BytesReference arrayedDoc = BytesReference.bytes(
|
||||
XContentFactory.jsonBuilder().startObject().field("field", "Bad shape").endObject()
|
||||
XContentFactory.jsonBuilder().startObject().field(FIELD_NAME, "Bad shape").endObject()
|
||||
);
|
||||
SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON);
|
||||
ParsedDocument document = ignoreMapper.parse(sourceToParse);
|
||||
assertThat(document.docs().get(0).getFields("field").length, equalTo(0));
|
||||
assertThat(document.docs().get(0).getFields(FIELD_NAME).length, equalTo(0));
|
||||
MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse));
|
||||
assertThat(exception.getCause().getMessage(), containsString("Unknown geometry type: bad"));
|
||||
}
|
||||
|
@ -244,7 +260,7 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
XContentFactory.jsonBuilder()
|
||||
.startObject()
|
||||
.field(
|
||||
"field",
|
||||
FIELD_NAME,
|
||||
"POLYGON ((18.9401790919516 -33.9681188869036, 18.9401790919516 -33.9681188869036, 18.9401790919517 "
|
||||
+ "-33.9681188869036, 18.9401790919517 -33.9681188869036, 18.9401790919516 -33.9681188869036))"
|
||||
)
|
||||
|
@ -252,7 +268,7 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
);
|
||||
SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON);
|
||||
ParsedDocument document = ignoreMapper.parse(sourceToParse);
|
||||
assertThat(document.docs().get(0).getFields("field").length, equalTo(0));
|
||||
assertThat(document.docs().get(0).getFields(FIELD_NAME).length, equalTo(0));
|
||||
MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse));
|
||||
assertThat(exception.getCause().getMessage(), containsString("at least three non-collinear points required"));
|
||||
}
|
||||
|
@ -267,10 +283,10 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("type", getFieldName());
|
||||
b.field("doc_values", true);
|
||||
}));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
boolean hasDocValues = ((ShapeFieldMapper) fieldMapper).fieldType().hasDocValues();
|
||||
boolean hasDocValues = ((AbstractGeometryFieldMapper<?>) fieldMapper).fieldType().hasDocValues();
|
||||
assertTrue(hasDocValues);
|
||||
|
||||
// explicit false doc_values
|
||||
|
@ -278,10 +294,10 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("type", getFieldName());
|
||||
b.field("doc_values", false);
|
||||
}));
|
||||
fieldMapper = defaultMapper.mappers().getMapper("field");
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
hasDocValues = ((ShapeFieldMapper) fieldMapper).fieldType().hasDocValues();
|
||||
hasDocValues = ((AbstractGeometryFieldMapper<?>) fieldMapper).fieldType().hasDocValues();
|
||||
assertFalse(hasDocValues);
|
||||
}
|
||||
|
||||
|
@ -297,10 +313,10 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
}));
|
||||
|
||||
Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper(FIELD_NAME);
|
||||
assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class));
|
||||
assertThat(fieldMapper, instanceOf(fieldMapperClass()));
|
||||
|
||||
ShapeFieldMapper shapeFieldMapper = (ShapeFieldMapper) fieldMapper;
|
||||
assertThat(shapeFieldMapper.fieldType().orientation(), equalTo(Orientation.CW));
|
||||
AbstractShapeGeometryFieldType<?> fieldType = fieldType(fieldMapper);
|
||||
assertThat(fieldType.orientation(), equalTo(Orientation.CW));
|
||||
}
|
||||
|
||||
public void testSerializeDefaults() throws Exception {
|
||||
|
@ -316,7 +332,7 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.field("type", getFieldName());
|
||||
b.field("doc_values", docValues);
|
||||
}));
|
||||
String serialized = toXContentString((ShapeFieldMapper) mapper.mappers().getMapper("field"));
|
||||
String serialized = toXContentString((ShapeFieldMapper) mapper.mappers().getMapper(FIELD_NAME));
|
||||
assertTrue(serialized, serialized.contains("\"orientation\":\"" + Orientation.RIGHT + "\""));
|
||||
assertTrue(serialized, serialized.contains("\"doc_values\":" + docValues));
|
||||
}
|
||||
|
@ -357,7 +373,7 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
b.startObject("keyword").field("type", "keyword").endObject();
|
||||
b.endObject();
|
||||
}));
|
||||
assertWarnings("Adding multifields to [shape] mappers has no effect and will be forbidden in future");
|
||||
assertWarnings("Adding multifields to [" + getFieldName() + "] mappers has no effect and will be forbidden in future");
|
||||
}
|
||||
|
||||
public void testSelfIntersectPolygon() throws IOException {
|
||||
|
@ -369,7 +385,7 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
assertThat(ex.getCause().getMessage(), containsString("Polygon self-intersection at lat=0.5 lon=0.5"));
|
||||
}
|
||||
|
||||
public String toXContentString(ShapeFieldMapper mapper, boolean includeDefaults) {
|
||||
public String toXContentString(AbstractShapeGeometryFieldMapper<?> mapper, boolean includeDefaults) {
|
||||
if (includeDefaults) {
|
||||
ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true"));
|
||||
return Strings.toString(mapper, params);
|
||||
|
@ -378,7 +394,7 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
|
|||
}
|
||||
}
|
||||
|
||||
public String toXContentString(ShapeFieldMapper mapper) {
|
||||
public String toXContentString(AbstractShapeGeometryFieldMapper<?> mapper) {
|
||||
return toXContentString(mapper, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ public class GeoHexAggregatorTests extends GeoGridAggregatorTestCase<InternalGeo
|
|||
|
||||
@Override
|
||||
protected List<ValuesSourceType> getSupportedValuesSourceTypes() {
|
||||
// TODO: why is shape here already, it is not supported yet
|
||||
return List.of(GeoShapeValuesSourceType.instance(), CoreValuesSourceType.GEOPOINT);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.apache.lucene.tests.index.RandomIndexWriter;
|
|||
import org.elasticsearch.common.geo.GeoPoint;
|
||||
import org.elasticsearch.common.geo.GeometryNormalizer;
|
||||
import org.elasticsearch.common.geo.Orientation;
|
||||
import org.elasticsearch.common.geo.SpatialPoint;
|
||||
import org.elasticsearch.geo.GeometryTestUtils;
|
||||
import org.elasticsearch.geometry.Geometry;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
|
@ -210,10 +211,10 @@ public class GeoShapeCentroidAggregatorTests extends AggregatorTestCase {
|
|||
InternalGeoCentroid result = searchAndReduce(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType);
|
||||
|
||||
assertEquals("my_agg", result.getName());
|
||||
GeoPoint centroid = result.centroid();
|
||||
SpatialPoint centroid = result.centroid();
|
||||
assertNotNull(centroid);
|
||||
assertEquals(expectedCentroid.getLat(), centroid.getLat(), GEOHASH_TOLERANCE);
|
||||
assertEquals(expectedCentroid.getLon(), centroid.getLon(), GEOHASH_TOLERANCE);
|
||||
assertEquals(expectedCentroid.getX(), centroid.getX(), GEOHASH_TOLERANCE);
|
||||
assertEquals(expectedCentroid.getY(), centroid.getY(), GEOHASH_TOLERANCE);
|
||||
assertTrue(AggregationInspectionHelper.hasValue(result));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,8 +42,8 @@ enum GridType {
|
|||
throws IOException {
|
||||
final Rectangle r = gridAggregation.toRectangle(key);
|
||||
final InternalGeoCentroid centroid = bucket.getAggregations().get(RestVectorTileAction.CENTROID_AGG_NAME);
|
||||
final double featureLon = Math.min(Math.max(centroid.centroid().lon(), r.getMinLon()), r.getMaxLon());
|
||||
final double featureLat = Math.min(Math.max(centroid.centroid().lat(), r.getMinLat()), r.getMaxLat());
|
||||
final double featureLon = Math.min(Math.max(centroid.centroid().getX(), r.getMinLon()), r.getMaxLon());
|
||||
final double featureLat = Math.min(Math.max(centroid.centroid().getY(), r.getMinLat()), r.getMaxLat());
|
||||
return featureFactory.point(featureLon, featureLat);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue