diff --git a/core/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java b/core/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java index f9926832008..6cbc747b2f9 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java @@ -162,7 +162,7 @@ public class GeoPointFieldMapper extends FieldMapper implements ArrayValueMapper } public static class GeoPointFieldType extends MappedFieldType { - GeoPointFieldType() { + public GeoPointFieldType() { } GeoPointFieldType(GeoPointFieldType ref) { diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBounds.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBounds.java index 5a9df082965..bdb9acd91ab 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBounds.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBounds.java @@ -30,19 +30,20 @@ import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Objects; public class InternalGeoBounds extends InternalMetricsAggregation implements GeoBounds { - private final double top; - private final double bottom; - private final double posLeft; - private final double posRight; - private final double negLeft; - private final double negRight; - private final boolean wrapLongitude; + final double top; + final double bottom; + final double posLeft; + final double posRight; + final double negLeft; + final double negRight; + final boolean wrapLongitude; InternalGeoBounds(String name, double top, double bottom, double posLeft, double posRight, - double negLeft, double negRight, boolean wrapLongitude, - List pipelineAggregators, Map metaData) { + double negLeft, double negRight, boolean wrapLongitude, + List pipelineAggregators, Map metaData) { super(name, pipelineAggregators, metaData); this.top = top; this.bottom = bottom; @@ -82,7 +83,7 @@ public class InternalGeoBounds extends InternalMetricsAggregation implements Geo public String getWriteableName() { return GeoBoundsAggregationBuilder.NAME; } - + @Override public InternalAggregation doReduce(List aggregations, ReduceContext reduceContext) { double top = Double.NEGATIVE_INFINITY; @@ -187,21 +188,21 @@ public class InternalGeoBounds extends InternalMetricsAggregation implements Geo private static class BoundingBox { private final GeoPoint topLeft; private final GeoPoint bottomRight; - + BoundingBox(GeoPoint topLeft, GeoPoint bottomRight) { this.topLeft = topLeft; this.bottomRight = bottomRight; } - + public GeoPoint topLeft() { return topLeft; } - + public GeoPoint bottomRight() { return bottomRight; } } - + private BoundingBox resolveBoundingBox() { if (Double.isInfinite(top)) { return null; @@ -242,4 +243,19 @@ public class InternalGeoBounds extends InternalMetricsAggregation implements Geo } } + @Override + protected boolean doEquals(Object obj) { + InternalGeoBounds other = (InternalGeoBounds) obj; + return bottom == other.bottom && + posLeft == other.posLeft && + posRight == other.posRight && + negLeft == other.negLeft && + negRight == other.negRight && + wrapLongitude == other.wrapLongitude; + } + + @Override + protected int doHashCode() { + return Objects.hash(bottom, posLeft, posRight, negLeft, negRight, wrapLongitude); + } } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/geobounds/GeoBoundsAggregatorTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/geobounds/GeoBoundsAggregatorTests.java new file mode 100644 index 00000000000..5227c62e6b4 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/geobounds/GeoBoundsAggregatorTests.java @@ -0,0 +1,118 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.aggregations.metrics.geobounds; + +import org.apache.lucene.document.Document; +import org.apache.lucene.document.LatLonDocValuesField; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.store.Directory; +import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.index.mapper.GeoPointFieldMapper; +import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.search.aggregations.AggregatorTestCase; +import org.elasticsearch.test.geo.RandomGeoGenerator; + +import static org.elasticsearch.search.aggregations.metrics.geobounds.InternalGeoBoundsTests.GEOHASH_TOLERANCE; +import static org.hamcrest.Matchers.closeTo; + +public class GeoBoundsAggregatorTests extends AggregatorTestCase { + public void testEmpty() throws Exception { + try (Directory dir = newDirectory(); + RandomIndexWriter w = new RandomIndexWriter(random(), dir)) { + GeoBoundsAggregationBuilder aggBuilder = new GeoBoundsAggregationBuilder("my_agg") + .field("field") + .wrapLongitude(false); + + MappedFieldType fieldType = new GeoPointFieldMapper.GeoPointFieldType(); + fieldType.setHasDocValues(true); + fieldType.setName("field"); + try (IndexReader reader = w.getReader()) { + IndexSearcher searcher = new IndexSearcher(reader); + InternalGeoBounds bounds = search(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType); + assertTrue(Double.isInfinite(bounds.top)); + assertTrue(Double.isInfinite(bounds.bottom)); + assertTrue(Double.isInfinite(bounds.posLeft)); + assertTrue(Double.isInfinite(bounds.posRight)); + assertTrue(Double.isInfinite(bounds.negLeft)); + assertTrue(Double.isInfinite(bounds.negRight)); + } + } + } + + public void testRandom() throws Exception { + double top = Double.NEGATIVE_INFINITY; + double bottom = Double.POSITIVE_INFINITY; + double posLeft = Double.POSITIVE_INFINITY; + double posRight = Double.NEGATIVE_INFINITY; + double negLeft = Double.POSITIVE_INFINITY; + double negRight = Double.NEGATIVE_INFINITY; + int numDocs = randomIntBetween(50, 100); + try (Directory dir = newDirectory(); + RandomIndexWriter w = new RandomIndexWriter(random(), dir)) { + for (int i = 0; i < numDocs; i++) { + Document doc = new Document(); + int numValues = randomIntBetween(1, 5); + for (int j = 0; j < numValues; j++) { + GeoPoint point = RandomGeoGenerator.randomPoint(random()); + if (point.getLat() > top) { + top = point.getLat(); + } + if (point.getLat() < bottom) { + bottom = point.getLat(); + } + if (point.getLon() >= 0 && point.getLon() < posLeft) { + posLeft = point.getLon(); + } + if (point.getLon() >= 0 && point.getLon() > posRight) { + posRight = point.getLon(); + } + if (point.getLon() < 0 && point.getLon() < negLeft) { + negLeft = point.getLon(); + } + if (point.getLon() < 0 && point.getLon() > negRight) { + negRight = point.getLon(); + } + doc.add(new LatLonDocValuesField("field", point.getLat(), point.getLon())); + } + w.addDocument(doc); + } + GeoBoundsAggregationBuilder aggBuilder = new GeoBoundsAggregationBuilder("my_agg") + .field("field") + .wrapLongitude(false); + + MappedFieldType fieldType = new GeoPointFieldMapper.GeoPointFieldType(); + fieldType.setHasDocValues(true); + fieldType.setName("field"); + try (IndexReader reader = w.getReader()) { + IndexSearcher searcher = new IndexSearcher(reader); + InternalGeoBounds bounds = search(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType); + assertThat(bounds.top, closeTo(top, GEOHASH_TOLERANCE)); + assertThat(bounds.bottom, closeTo(bottom, GEOHASH_TOLERANCE)); + assertThat(bounds.posLeft, closeTo(posLeft, GEOHASH_TOLERANCE)); + assertThat(bounds.posRight, closeTo(posRight, GEOHASH_TOLERANCE)); + assertThat(bounds.negRight, closeTo(negRight, GEOHASH_TOLERANCE)); + assertThat(bounds.negLeft, closeTo(negLeft, GEOHASH_TOLERANCE)); + } + } + } +} diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBoundsTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBoundsTests.java new file mode 100644 index 00000000000..cd5d4d43d17 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/geobounds/InternalGeoBoundsTests.java @@ -0,0 +1,85 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.aggregations.metrics.geobounds; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.search.aggregations.InternalAggregationTestCase; +import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.closeTo; + +public class InternalGeoBoundsTests extends InternalAggregationTestCase { + static final double GEOHASH_TOLERANCE = 1E-5D; + + @Override + protected InternalGeoBounds createTestInstance(String name, List pipelineAggregators, + Map metaData) { + InternalGeoBounds geo = new InternalGeoBounds(name, + randomDouble(), randomDouble(), randomDouble(), randomDouble(), + randomDouble(), randomDouble(), randomBoolean(), + pipelineAggregators, Collections.emptyMap()); + return geo; + } + + @Override + protected void assertReduced(InternalGeoBounds reduced, List inputs) { + double top = Double.NEGATIVE_INFINITY; + double bottom = Double.POSITIVE_INFINITY; + double posLeft = Double.POSITIVE_INFINITY; + double posRight = Double.NEGATIVE_INFINITY; + double negLeft = Double.POSITIVE_INFINITY; + double negRight = Double.NEGATIVE_INFINITY; + for (InternalGeoBounds bounds : inputs) { + if (bounds.top > top) { + top = bounds.top; + } + if (bounds.bottom < bottom) { + bottom = bounds.bottom; + } + if (bounds.posLeft < posLeft) { + posLeft = bounds.posLeft; + } + if (bounds.posRight > posRight) { + posRight = bounds.posRight; + } + if (bounds.negLeft < negLeft) { + negLeft = bounds.negLeft; + } + if (bounds.negRight > negRight) { + negRight = bounds.negRight; + } + } + assertThat(reduced.top, closeTo(top, GEOHASH_TOLERANCE)); + assertThat(reduced.bottom, closeTo(bottom, GEOHASH_TOLERANCE)); + assertThat(reduced.posLeft, closeTo(posLeft, GEOHASH_TOLERANCE)); + assertThat(reduced.posRight, closeTo(posRight, GEOHASH_TOLERANCE)); + assertThat(reduced.negLeft, closeTo(negLeft, GEOHASH_TOLERANCE)); + assertThat(reduced.negRight, closeTo(negRight, GEOHASH_TOLERANCE)); + } + + @Override + protected Writeable.Reader instanceReader() { + return InternalGeoBounds::new; + } +}