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