From 6f5919aa1910d806d7a8bbf377eef46ce7f780dc Mon Sep 17 00:00:00 2001 From: Ignacio Vera Date: Thu, 12 Dec 2019 07:53:11 +0100 Subject: [PATCH] Build triangle tree from geo_shape indexed fields (#50012) --- .../common/geo/CentroidCalculator.java | 104 +++- .../org/elasticsearch/common/geo/Extent.java | 51 +- .../common/geo/ShapeTreeWriter.java | 34 -- .../common/geo/TriangleTreeReader.java | 149 ++--- .../common/geo/TriangleTreeWriter.java | 566 ++++-------------- .../index/fielddata/MultiGeoValues.java | 92 +-- .../mapper/AbstractGeometryFieldMapper.java | 20 +- .../mapper/BinaryGeoShapeDocValuesField.java | 34 +- .../index/mapper/GeoShapeIndexer.java | 13 +- .../index/mapper/LegacyGeoShapeIndexer.java | 4 +- .../common/geo/CentroidCalculatorTests.java | 33 +- .../elasticsearch/common/geo/ExtentTests.java | 25 +- .../common/geo/GeoTestUtils.java | 32 +- .../common/geo/TriangleTreeTests.java | 141 +++-- .../geogrid/GeoGridAggregatorTestCase.java | 15 +- .../metrics/GeoBoundsAggregatorTests.java | 10 +- .../aggregations/metrics/GeoBoundsIT.java | 6 +- .../metrics/GeoCentroidAggregatorTests.java | 100 +--- .../spatial/index/mapper/ShapeIndexer.java | 9 +- 19 files changed, 563 insertions(+), 875 deletions(-) delete mode 100644 server/src/main/java/org/elasticsearch/common/geo/ShapeTreeWriter.java diff --git a/server/src/main/java/org/elasticsearch/common/geo/CentroidCalculator.java b/server/src/main/java/org/elasticsearch/common/geo/CentroidCalculator.java index 998f2753420ff..51b610c02f431 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/CentroidCalculator.java +++ b/server/src/main/java/org/elasticsearch/common/geo/CentroidCalculator.java @@ -19,6 +19,19 @@ package org.elasticsearch.common.geo; +import org.elasticsearch.geometry.Circle; +import org.elasticsearch.geometry.Geometry; +import org.elasticsearch.geometry.GeometryCollection; +import org.elasticsearch.geometry.GeometryVisitor; +import org.elasticsearch.geometry.Line; +import org.elasticsearch.geometry.LinearRing; +import org.elasticsearch.geometry.MultiLine; +import org.elasticsearch.geometry.MultiPoint; +import org.elasticsearch.geometry.MultiPolygon; +import org.elasticsearch.geometry.Point; +import org.elasticsearch.geometry.Polygon; +import org.elasticsearch.geometry.Rectangle; + /** * This class keeps a running Kahan-sum of coordinates * that are to be averaged in {@link TriangleTreeWriter} for use @@ -32,12 +45,13 @@ public class CentroidCalculator { private double sumY; private int count; - public CentroidCalculator() { + public CentroidCalculator(Geometry geometry) { this.sumX = 0.0; this.compX = 0.0; this.sumY = 0.0; this.compY = 0.0; this.count = 0; + geometry.visit(new CentroidCalculatorVisitor(this)); } /** @@ -47,7 +61,7 @@ public CentroidCalculator() { * @param x the x-coordinate of the point * @param y the y-coordinate of the point */ - public void addCoordinate(double x, double y) { + private void addCoordinate(double x, double y) { double correctedX = x - compX; double newSumX = sumX + correctedX; compX = (newSumX - sumX) - correctedX; @@ -69,7 +83,7 @@ public void addCoordinate(double x, double y) { * * @param otherCalculator the other centroid calculator to add from */ - void addFrom(CentroidCalculator otherCalculator) { + public void addFrom(CentroidCalculator otherCalculator) { addCoordinate(otherCalculator.sumX, otherCalculator.sumY); // adjust count count += otherCalculator.count - 1; @@ -88,4 +102,88 @@ public double getX() { public double getY() { return sumY / count; } + + private static class CentroidCalculatorVisitor implements GeometryVisitor { + + private final CentroidCalculator calculator; + + private CentroidCalculatorVisitor(CentroidCalculator calculator) { + this.calculator = calculator; + } + + @Override + public Void visit(Circle circle) { + calculator.addCoordinate(circle.getX(), circle.getY()); + return null; + } + + @Override + public Void visit(GeometryCollection collection) { + for (Geometry shape : collection) { + shape.visit(this); + } + return null; + } + + @Override + public Void visit(Line line) { + + for (int i = 0; i < line.length(); i++) { + calculator.addCoordinate(line.getX(i), line.getY(i)); + } + return null; + } + + @Override + public Void visit(LinearRing ring) { + for (int i = 0; i < ring.length() - 1; i++) { + calculator.addCoordinate(ring.getX(i), ring.getY(i)); + } + return null; + } + + @Override + public Void visit(MultiLine multiLine) { + for (Line line : multiLine) { + visit(line); + } + return null; + } + + @Override + public Void visit(MultiPoint multiPoint) { + for (Point point : multiPoint) { + visit(point); + } + return null; + } + + @Override + public Void visit(MultiPolygon multiPolygon) { + for (Polygon polygon : multiPolygon) { + visit(polygon); + } + return null; + } + + @Override + public Void visit(Point point) { + calculator.addCoordinate(point.getX(), point.getY()); + return null; + } + + @Override + public Void visit(Polygon polygon) { + return visit(polygon.getPolygon()); + } + + @Override + public Void visit(Rectangle rectangle) { + calculator.addCoordinate(rectangle.getMinX(), rectangle.getMinY()); + calculator.addCoordinate(rectangle.getMinX(), rectangle.getMaxY()); + calculator.addCoordinate(rectangle.getMaxX(), rectangle.getMinY()); + calculator.addCoordinate(rectangle.getMaxX(), rectangle.getMaxY()); + return null; + } + } } diff --git a/server/src/main/java/org/elasticsearch/common/geo/Extent.java b/server/src/main/java/org/elasticsearch/common/geo/Extent.java index 2c3eef24367a5..eec3d7ca408cd 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/Extent.java +++ b/server/src/main/java/org/elasticsearch/common/geo/Extent.java @@ -18,18 +18,12 @@ */ package org.elasticsearch.common.geo; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.io.stream.Writeable; - -import java.io.IOException; import java.util.Objects; /** - * Object representing the extent of a geometry object within a {@link ShapeTreeWriter}. + * Object representing the extent of a geometry object within a {@link TriangleTreeWriter}. */ -public class Extent implements Writeable { - static final int WRITEABLE_SIZE_IN_BYTES = 24; +public class Extent { public int top; public int bottom; @@ -47,7 +41,7 @@ public Extent() { this.posRight = Integer.MIN_VALUE; } - public Extent(int top, int bottom, int negLeft, int negRight, int posLeft, int posRight) { + private Extent(int top, int bottom, int negLeft, int negRight, int posLeft, int posRight) { this.top = top; this.bottom = bottom; this.negLeft = negLeft; @@ -56,10 +50,6 @@ public Extent(int top, int bottom, int negLeft, int negRight, int posLeft, int p this.posRight = posRight; } - Extent(StreamInput input) throws IOException { - this(input.readInt(), input.readInt(), input.readInt(), input.readInt(), input.readInt(), input.readInt()); - } - public void reset(int top, int bottom, int negLeft, int negRight, int posLeft, int posRight) { this.top = top; this.bottom = bottom; @@ -88,9 +78,10 @@ public void addRectangle(int bottomLeftX, int bottomLeftY, int topRightX, int to this.negRight = Math.max(this.negRight, topRightX); } else if (bottomLeftX < 0) { this.negLeft = Math.min(this.negLeft, bottomLeftX); - this.negRight = Math.max(this.negRight, bottomLeftX); - this.posLeft = Math.min(this.posLeft, topRightX); this.posRight = Math.max(this.posRight, topRightX); + // this signal the extent cannot be wrapped around the dateline + this.negRight = 0; + this.posLeft = 0; } else { this.posLeft = Math.min(this.posLeft, bottomLeftX); this.posRight = Math.max(this.posRight, topRightX); @@ -131,8 +122,11 @@ static Extent fromPoints(int bottomLeftX, int bottomLeftY, int topRightX, int to negLeft = bottomLeftX; negRight = topRightX; } else if (bottomLeftX < 0) { - negLeft = negRight = bottomLeftX; - posLeft = posRight = topRightX; + negLeft = bottomLeftX; + posRight = topRightX; + // this signal the extent cannot be wrapped around the dateline + negRight = 0; + posLeft = 0; } else { posLeft = bottomLeftX; posRight = topRightX; @@ -168,17 +162,6 @@ public int maxX() { return Math.max(negRight, posRight); } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeInt(top); - out.writeInt(bottom); - out.writeInt(negLeft); - out.writeInt(negRight); - out.writeInt(posLeft); - out.writeInt(posRight); - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -196,4 +179,16 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(top, bottom, negLeft, negRight, posLeft, posRight); } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("["); + builder.append("top = " + top + ", "); + builder.append("bottom = " + bottom + ", "); + builder.append("negLeft = " + negLeft + ", "); + builder.append("negRight = " + negRight + ", "); + builder.append("posLeft = " + posLeft + ", "); + builder.append("posRight = " + posRight + "]"); + return builder.toString(); + } } diff --git a/server/src/main/java/org/elasticsearch/common/geo/ShapeTreeWriter.java b/server/src/main/java/org/elasticsearch/common/geo/ShapeTreeWriter.java deleted file mode 100644 index 35eaec5fb02f0..0000000000000 --- a/server/src/main/java/org/elasticsearch/common/geo/ShapeTreeWriter.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.common.geo; - -import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.geometry.ShapeType; - -/** - * Shape writer for use in doc-values - */ -public abstract class ShapeTreeWriter implements Writeable { - - public abstract Extent getExtent(); - - public abstract ShapeType getShapeType(); - - public abstract CentroidCalculator getCentroidCalculator(); -} diff --git a/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeReader.java b/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeReader.java index b44bc3cb687ef..4b346a1302cdf 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeReader.java +++ b/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeReader.java @@ -18,11 +18,10 @@ */ package org.elasticsearch.common.geo; +import org.apache.lucene.store.ByteArrayDataInput; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.io.stream.ByteBufferStreamInput; import java.io.IOException; -import java.nio.ByteBuffer; import static org.apache.lucene.geo.GeoUtils.orient; @@ -35,8 +34,8 @@ */ public class TriangleTreeReader { - private final int extentOffset = 8; - private ByteBufferStreamInput input; + private static final int extentOffset = 8; + private final ByteArrayDataInput input; private final CoordinateEncoder coordinateEncoder; private final Rectangle2D rectangle2D; private final Extent extent; @@ -46,19 +45,21 @@ public TriangleTreeReader(CoordinateEncoder coordinateEncoder) { this.coordinateEncoder = coordinateEncoder; this.rectangle2D = new Rectangle2D(); this.extent = new Extent(); + this.input = new ByteArrayDataInput(); } public void reset(BytesRef bytesRef) throws IOException { - this.input = new ByteBufferStreamInput(ByteBuffer.wrap(bytesRef.bytes, bytesRef.offset, bytesRef.length)); + this.input.reset(bytesRef.bytes, bytesRef.offset, bytesRef.length); treeOffset = 0; } /** * returns the bounding box of the geometry in the format [minX, maxX, minY, maxY]. */ - public Extent getExtent() throws IOException { + public Extent getExtent() { if (treeOffset == 0) { - input.position(extentOffset); + // TODO: Compress serialization of extent + input.setPosition(extentOffset); int top = input.readInt(); int bottom = Math.toIntExact(top - input.readVLong()); int posRight = input.readInt(); @@ -66,9 +67,9 @@ public Extent getExtent() throws IOException { int negRight = input.readInt(); int negLeft = input.readInt(); extent.reset(top, bottom, negLeft, negRight, posLeft, posRight); - treeOffset = input.position(); + treeOffset = input.getPosition(); } else { - input.position(treeOffset); + input.setPosition(treeOffset); } return extent; } @@ -76,16 +77,16 @@ public Extent getExtent() throws IOException { /** * returns the X coordinate of the centroid. */ - public double getCentroidX() throws IOException { - input.position(0); + public double getCentroidX() { + input.setPosition(0); return coordinateEncoder.decodeX(input.readInt()); } /** * returns the Y coordinate of the centroid. */ - public double getCentroidY() throws IOException { - input.position(4); + public double getCentroidY() { + input.setPosition(4); return coordinateEncoder.decodeY(input.readInt()); } @@ -93,76 +94,79 @@ public double getCentroidY() throws IOException { * Compute the relation with the provided bounding box. If the result is CELL_INSIDE_QUERY * then the bounding box is within the shape. */ - public GeoRelation relate(int minX, int minY, int maxX, int maxY) throws IOException { + public GeoRelation relate(int minX, int minY, int maxX, int maxY) { Extent extent = getExtent(); int thisMaxX = extent.maxX(); int thisMinX = extent.minX(); int thisMaxY = extent.maxY(); int thisMinY = extent.minY(); + if ((thisMinX > maxX || thisMaxX < minX || thisMinY > maxY || thisMaxY < minY)) { + // shapes are disjoint + return GeoRelation.QUERY_DISJOINT; + } if (minX <= thisMinX && maxX >= thisMaxX && minY <= thisMinY && maxY >= thisMaxY) { // the rectangle fully contains the shape return GeoRelation.QUERY_CROSSES; } + // quick checks failed, need to traverse the tree GeoRelation rel = GeoRelation.QUERY_DISJOINT; - if ((thisMinX > maxX || thisMaxX < minX || thisMinY > maxY || thisMaxY < minY) == false) { - // shapes are NOT disjoint - rectangle2D.setValues(minX, maxX, minY, maxY); - byte metadata = input.readByte(); - if ((metadata & 1 << 2) == 1 << 2) { // component in this node is a point - int x = Math.toIntExact(thisMaxX - input.readVLong()); - int y = Math.toIntExact(thisMaxY - input.readVLong()); - if (rectangle2D.contains(x, y)) { - return GeoRelation.QUERY_CROSSES; - } - thisMinX = x; - } else if ((metadata & 1 << 3) == 1 << 3) { // component in this node is a line - int aX = Math.toIntExact(thisMaxX - input.readVLong()); - int aY = Math.toIntExact(thisMaxY - input.readVLong()); - int bX = Math.toIntExact(thisMaxX - input.readVLong()); - int bY = Math.toIntExact(thisMaxY - input.readVLong()); - if (rectangle2D.intersectsLine(aX, aY, bX, bY)) { - return GeoRelation.QUERY_CROSSES; - } - thisMinX = aX; - } else { // component in this node is a triangle - int aX = Math.toIntExact(thisMaxX - input.readVLong()); - int aY = Math.toIntExact(thisMaxY - input.readVLong()); - int bX = Math.toIntExact(thisMaxX - input.readVLong()); - int bY = Math.toIntExact(thisMaxY - input.readVLong()); - int cX = Math.toIntExact(thisMaxX - input.readVLong()); - int cY = Math.toIntExact(thisMaxY - input.readVLong()); - boolean ab = (metadata & 1 << 4) == 1 << 4; - boolean bc = (metadata & 1 << 5) == 1 << 5; - boolean ca = (metadata & 1 << 6) == 1 << 6; - rel = rectangle2D.relateTriangle(aX, aY, ab, bX, bY, bc, cX, cY, ca); - if (rel == GeoRelation.QUERY_CROSSES) { - return GeoRelation.QUERY_CROSSES; - } - thisMinX = aX; + rectangle2D.setValues(minX, maxX, minY, maxY); + byte metadata = input.readByte(); + if ((metadata & 1 << 2) == 1 << 2) { // component in this node is a point + int x = Math.toIntExact(thisMaxX - input.readVLong()); + int y = Math.toIntExact(thisMaxY - input.readVLong()); + if (rectangle2D.contains(x, y)) { + return GeoRelation.QUERY_CROSSES; } - if ((metadata & 1 << 0) == 1 << 0) { // left != null - GeoRelation left = relate(rectangle2D, false, thisMaxX, thisMaxY); - if (left == GeoRelation.QUERY_CROSSES) { - return GeoRelation.QUERY_CROSSES; - } else if (left == GeoRelation.QUERY_INSIDE) { - rel = left; - } + thisMinX = x; + } else if ((metadata & 1 << 3) == 1 << 3) { // component in this node is a line + int aX = Math.toIntExact(thisMaxX - input.readVLong()); + int aY = Math.toIntExact(thisMaxY - input.readVLong()); + int bX = Math.toIntExact(thisMaxX - input.readVLong()); + int bY = Math.toIntExact(thisMaxY - input.readVLong()); + if (rectangle2D.intersectsLine(aX, aY, bX, bY)) { + return GeoRelation.QUERY_CROSSES; } - if ((metadata & 1 << 1) == 1 << 1) { // right != null - if (rectangle2D.maxX >= thisMinX) { - GeoRelation right = relate(rectangle2D, false, thisMaxX, thisMaxY); - if (right == GeoRelation.QUERY_CROSSES) { - return GeoRelation.QUERY_CROSSES; - } else if (right == GeoRelation.QUERY_INSIDE) { - rel = right; - } + thisMinX = aX; + } else { // component in this node is a triangle + int aX = Math.toIntExact(thisMaxX - input.readVLong()); + int aY = Math.toIntExact(thisMaxY - input.readVLong()); + int bX = Math.toIntExact(thisMaxX - input.readVLong()); + int bY = Math.toIntExact(thisMaxY - input.readVLong()); + int cX = Math.toIntExact(thisMaxX - input.readVLong()); + int cY = Math.toIntExact(thisMaxY - input.readVLong()); + boolean ab = (metadata & 1 << 4) == 1 << 4; + boolean bc = (metadata & 1 << 5) == 1 << 5; + boolean ca = (metadata & 1 << 6) == 1 << 6; + rel = rectangle2D.relateTriangle(aX, aY, ab, bX, bY, bc, cX, cY, ca); + if (rel == GeoRelation.QUERY_CROSSES) { + return GeoRelation.QUERY_CROSSES; + } + thisMinX = aX; + } + if ((metadata & 1 << 0) == 1 << 0) { // left != null + GeoRelation left = relate(rectangle2D, false, thisMaxX, thisMaxY); + if (left == GeoRelation.QUERY_CROSSES) { + return GeoRelation.QUERY_CROSSES; + } else if (left == GeoRelation.QUERY_INSIDE) { + rel = left; + } + } + if ((metadata & 1 << 1) == 1 << 1) { // right != null + if (rectangle2D.maxX >= thisMinX) { + GeoRelation right = relate(rectangle2D, false, thisMaxX, thisMaxY); + if (right == GeoRelation.QUERY_CROSSES) { + return GeoRelation.QUERY_CROSSES; + } else if (right == GeoRelation.QUERY_INSIDE) { + rel = right; } } } + return rel; } - private GeoRelation relate(Rectangle2D rectangle2D, boolean splitX, int parentMaxX, int parentMaxY) throws IOException { + private GeoRelation relate(Rectangle2D rectangle2D, boolean splitX, int parentMaxX, int parentMaxY) { int thisMaxX = Math.toIntExact(parentMaxX - input.readVLong()); int thisMaxY = Math.toIntExact(parentMaxY - input.readVLong()); GeoRelation rel = GeoRelation.QUERY_DISJOINT; @@ -224,11 +228,11 @@ private GeoRelation relate(Rectangle2D rectangle2D, boolean splitX, int parentMa rel = right; } } else { - input.skip(rightSize); + input.skipBytes(rightSize); } } } else { - input.skip(size); + input.skipBytes(size); } return rel; } @@ -243,7 +247,7 @@ private static class Rectangle2D { Rectangle2D() { } - protected void setValues(int minX, int maxX, int minY, int maxY) { + private void setValues(int minX, int maxX, int minY, int maxY) { this.minX = minX; this.maxX = maxX; this.minY = minY; @@ -260,7 +264,7 @@ public boolean contains(int x, int y) { /** * Checks if the rectangle intersects the provided triangle **/ - public boolean intersectsLine(int aX, int aY, int bX, int bY) { + private boolean intersectsLine(int aX, int aY, int bX, int bY) { // 1. query contains any triangle points if (contains(aX, aY) || contains(bX, bY)) { return true; @@ -287,7 +291,7 @@ public boolean intersectsLine(int aX, int aY, int bX, int bY) { /** * Checks if the rectangle intersects the provided triangle **/ - public GeoRelation relateTriangle(int aX, int aY, boolean ab, int bX, int bY, boolean bc, int cX, int cY, boolean ca) { + private GeoRelation relateTriangle(int aX, int aY, boolean ab, int bX, int bY, boolean bc, int cX, int cY, boolean ca) { // 1. query contains any triangle points if (contains(aX, aY) || contains(bX, bY) || contains(cX, cY)) { return GeoRelation.QUERY_CROSSES; @@ -374,8 +378,8 @@ private boolean edgeIntersectsQuery(int ax, int ay, int bx, int by) { /** * Compute whether the given x, y point is in a triangle; uses the winding order method */ - static boolean pointInTriangle(double minX, double maxX, double minY, double maxY, double x, double y, - double aX, double aY, double bX, double bY, double cX, double cY) { + private static boolean pointInTriangle(double minX, double maxX, double minY, double maxY, double x, double y, + double aX, double aY, double bX, double bY, double cX, double cY) { //check the bounding box because if the triangle is degenerated, e.g points and lines, we need to filter out //coplanar points that are not part of the triangle. if (x >= minX && x <= maxX && y >= minY && y <= maxY) { @@ -398,6 +402,5 @@ private static boolean boxesAreDisjoint(final int aMinX, final int aMaxX, final final int bMinX, final int bMaxX, final int bMinY, final int bMaxY) { return (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY); } - } } diff --git a/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeWriter.java b/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeWriter.java index f1cca92c303f3..f4efa0523c7f1 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeWriter.java +++ b/server/src/main/java/org/elasticsearch/common/geo/TriangleTreeWriter.java @@ -18,59 +18,38 @@ */ package org.elasticsearch.common.geo; -import org.apache.lucene.geo.GeoUtils; -import org.apache.lucene.geo.Tessellator; +import org.apache.lucene.document.ShapeField; +import org.apache.lucene.store.ByteBuffersDataOutput; import org.apache.lucene.util.ArrayUtil; -import org.elasticsearch.common.io.stream.BytesStreamOutput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.geometry.Circle; -import org.elasticsearch.geometry.Geometry; -import org.elasticsearch.geometry.GeometryCollection; -import org.elasticsearch.geometry.GeometryVisitor; -import org.elasticsearch.geometry.Line; -import org.elasticsearch.geometry.LinearRing; -import org.elasticsearch.geometry.MultiLine; -import org.elasticsearch.geometry.MultiPoint; -import org.elasticsearch.geometry.MultiPolygon; -import org.elasticsearch.geometry.Point; -import org.elasticsearch.geometry.Polygon; -import org.elasticsearch.geometry.Rectangle; -import org.elasticsearch.geometry.ShapeType; -import org.elasticsearch.index.mapper.GeoShapeIndexer; import java.io.IOException; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; /** - * This is a tree-writer that serializes a {@link Geometry} and tessellate it to write it into a byte array. - * Internally it tessellate the given {@link Geometry} and it builds an interval tree with the - * tessellation. + * This is a tree-writer that serializes a list of {@link ShapeField.DecodedTriangle} as an interval tree + * into a byte array. */ -public class TriangleTreeWriter extends ShapeTreeWriter { +public class TriangleTreeWriter { private final TriangleTreeNode node; private final CoordinateEncoder coordinateEncoder; private final CentroidCalculator centroidCalculator; - private final ShapeType type; private Extent extent; - public TriangleTreeWriter(Geometry geometry, CoordinateEncoder coordinateEncoder) { + public TriangleTreeWriter(List triangles, CoordinateEncoder coordinateEncoder, + CentroidCalculator centroidCalculator) { this.coordinateEncoder = coordinateEncoder; - this.centroidCalculator = new CentroidCalculator(); + this.centroidCalculator = centroidCalculator; this.extent = new Extent(); - this.type = geometry.type(); - TriangleTreeBuilder builder = new TriangleTreeBuilder(coordinateEncoder); - geometry.visit(builder); - this.node = builder.build(); + this.node = build(triangles); } - @Override - public void writeTo(StreamOutput out) throws IOException { + /*** Serialize the interval tree in the provided data output */ + public void writeTo(ByteBuffersDataOutput out) throws IOException { out.writeInt(coordinateEncoder.encodeX(centroidCalculator.getX())); out.writeInt(coordinateEncoder.encodeY(centroidCalculator.getY())); + // TODO: Compress serialization of extent out.writeInt(extent.top); out.writeVLong((long) extent.top - extent.bottom); out.writeInt(extent.posRight); @@ -80,218 +59,105 @@ public void writeTo(StreamOutput out) throws IOException { node.writeTo(out); } - @Override - public Extent getExtent() { - return extent; + private void addToExtent(TriangleTreeNode treeNode) { + extent.addRectangle(treeNode.minX, treeNode.minY, treeNode.maxX, treeNode.maxY); } - @Override - public ShapeType getShapeType() { - return type; - } - - @Override - public CentroidCalculator getCentroidCalculator() { - return centroidCalculator; - } - - /** - * Class that tessellate the geometry and build an interval tree in memory. - */ - class TriangleTreeBuilder implements GeometryVisitor { - - private final List triangles; - private final CoordinateEncoder coordinateEncoder; - - - TriangleTreeBuilder(CoordinateEncoder coordinateEncoder) { - this.coordinateEncoder = coordinateEncoder; - this.triangles = new ArrayList<>(); + private TriangleTreeNode build(List triangles) { + if (triangles.size() == 1) { + TriangleTreeNode triangleTreeNode = new TriangleTreeNode(triangles.get(0)); + addToExtent(triangleTreeNode); + return triangleTreeNode; } - - private void addTriangles(List triangles) { - this.triangles.addAll(triangles); - } - - @Override - public Void visit(GeometryCollection collection) { - for (Geometry geometry : collection) { - geometry.visit(this); - } - return null; - } - - @Override - public Void visit(Line line) { - for (int i = 0; i < line.length(); i++) { - centroidCalculator.addCoordinate(line.getX(i), line.getY(i)); - } - org.apache.lucene.geo.Line luceneLine = GeoShapeIndexer.toLuceneLine(line); - addToExtent(luceneLine.minLon, luceneLine.maxLon, luceneLine.minLat, luceneLine.maxLat); - addTriangles(TriangleTreeLeaf.fromLine(coordinateEncoder, luceneLine)); - return null; - } - - @Override - public Void visit(MultiLine multiLine) { - for (Line line : multiLine) { - visit(line); - } - return null; - } - - @Override - public Void visit(Polygon polygon) { - // TODO: Shall we consider holes for centroid computation? - for (int i =0; i < polygon.getPolygon().length() - 1; i++) { - centroidCalculator.addCoordinate(polygon.getPolygon().getX(i), polygon.getPolygon().getY(i)); - } - org.apache.lucene.geo.Polygon lucenePolygon = GeoShapeIndexer.toLucenePolygon(polygon); - addToExtent(lucenePolygon.minLon, lucenePolygon.maxLon, lucenePolygon.minLat, lucenePolygon.maxLat); - addTriangles(TriangleTreeLeaf.fromPolygon(coordinateEncoder, lucenePolygon)); - return null; - } - - @Override - public Void visit(MultiPolygon multiPolygon) { - for (Polygon polygon : multiPolygon) { - visit(polygon); - } - return null; - } - - @Override - public Void visit(Rectangle r) { - centroidCalculator.addCoordinate(r.getMinX(), r.getMinY()); - centroidCalculator.addCoordinate(r.getMaxX(), r.getMaxY()); - addToExtent(r.getMinLon(), r.getMaxLon(), r.getMinLat(), r.getMaxLat()); - addTriangles(TriangleTreeLeaf.fromRectangle(coordinateEncoder, r)); - return null; + TriangleTreeNode[] nodes = new TriangleTreeNode[triangles.size()]; + for (int i = 0; i < triangles.size(); i++) { + nodes[i] = new TriangleTreeNode(triangles.get(i)); + addToExtent(nodes[i]); } + return createTree(nodes, 0, triangles.size() - 1, true); + } - @Override - public Void visit(Point point) { - centroidCalculator.addCoordinate(point.getX(), point.getY()); - addToExtent(point.getLon(), point.getLon(), point.getLat(), point.getLat()); - addTriangles(TriangleTreeLeaf.fromPoints(coordinateEncoder, point)); + /** Creates tree from sorted components (with range low and high inclusive) */ + private TriangleTreeNode createTree(TriangleTreeNode[] components, int low, int high, boolean splitX) { + if (low > high) { return null; } - - @Override - public Void visit(MultiPoint multiPoint) { - for (Point point : multiPoint) { - visit(point); + final int mid = (low + high) >>> 1; + if (low < high) { + Comparator comparator; + if (splitX) { + comparator = Comparator.comparingInt((TriangleTreeNode left) -> left.minX).thenComparingInt(left -> left.maxX); + } else { + comparator = Comparator.comparingInt((TriangleTreeNode left) -> left.minY).thenComparingInt(left -> left.maxY); } - return null; - } - - @Override - public Void visit(LinearRing ring) { - throw new IllegalArgumentException("invalid shape type found [LinearRing]"); - } - - @Override - public Void visit(Circle circle) { - throw new IllegalArgumentException("invalid shape type found [Circle]"); - } - - private void addToExtent(double minLon, double maxLon, double minLat, double maxLat) { - int minX = coordinateEncoder.encodeX(minLon); - int maxX = coordinateEncoder.encodeX(maxLon); - int minY = coordinateEncoder.encodeY(minLat); - int maxY = coordinateEncoder.encodeY(maxLat); - extent.addRectangle(minX, minY, maxX, maxY); + ArrayUtil.select(components, low, high + 1, mid, comparator); } + TriangleTreeNode newNode = components[mid]; + // find children + newNode.left = createTree(components, low, mid - 1, !splitX); + newNode.right = createTree(components, mid + 1, high, !splitX); - - public TriangleTreeNode build() { - if (triangles.size() == 1) { - return new TriangleTreeNode(triangles.get(0)); - } - TriangleTreeNode[] nodes = new TriangleTreeNode[triangles.size()]; - for (int i = 0; i < triangles.size(); i++) { - nodes[i] = new TriangleTreeNode(triangles.get(i)); - } - TriangleTreeNode root = createTree(nodes, 0, triangles.size() - 1, true); - return root; + // pull up max values to this node + if (newNode.left != null) { + newNode.maxX = Math.max(newNode.maxX, newNode.left.maxX); + newNode.maxY = Math.max(newNode.maxY, newNode.left.maxY); } - - /** Creates tree from sorted components (with range low and high inclusive) */ - private TriangleTreeNode createTree(TriangleTreeNode[] components, int low, int high, boolean splitX) { - if (low > high) { - return null; - } - final int mid = (low + high) >>> 1; - if (low < high) { - Comparator comparator; - if (splitX) { - comparator = (left, right) -> { - int ret = Double.compare(left.minX, right.minX); - if (ret == 0) { - ret = Double.compare(left.maxX, right.maxX); - } - return ret; - }; - } else { - comparator = (left, right) -> { - int ret = Double.compare(left.minY, right.minY); - if (ret == 0) { - ret = Double.compare(left.maxY, right.maxY); - } - return ret; - }; - } - ArrayUtil.select(components, low, high + 1, mid, comparator); - } - TriangleTreeNode newNode = components[mid]; - // find children - newNode.left = createTree(components, low, mid - 1, !splitX); - newNode.right = createTree(components, mid + 1, high, !splitX); - - // pull up max values to this node - if (newNode.left != null) { - newNode.maxX = Math.max(newNode.maxX, newNode.left.maxX); - newNode.maxY = Math.max(newNode.maxY, newNode.left.maxY); - } - if (newNode.right != null) { - newNode.maxX = Math.max(newNode.maxX, newNode.right.maxX); - newNode.maxY = Math.max(newNode.maxY, newNode.right.maxY); - } - return newNode; + if (newNode.right != null) { + newNode.maxX = Math.max(newNode.maxX, newNode.right.maxX); + newNode.maxY = Math.max(newNode.maxY, newNode.right.maxY); } + return newNode; } - /** - * Represents an inner node of the tree. - */ - static class TriangleTreeNode implements Writeable { + /** Represents an inner node of the tree. */ + private static class TriangleTreeNode { + /** type of component */ + public enum TYPE { + POINT, LINE, TRIANGLE + } /** minimum latitude of this geometry's bounding box area */ private int minY; /** maximum latitude of this geometry's bounding box area */ private int maxY; /** minimum longitude of this geometry's bounding box area */ private int minX; - /** maximum longitude of this geometry's bounding box area */ + /** maximum longitude of this geometry's bounding box area */ private int maxX; - // child components, or null. Note internal nodes might mot have - // a consistent bounding box. Internal nodes should not be accessed - // outside if this class. + // child components, or null. private TriangleTreeNode left; private TriangleTreeNode right; /** root node of edge tree */ - private TriangleTreeLeaf component; - - protected TriangleTreeNode(TriangleTreeLeaf component) { - this.minY = component.minY; - this.maxY = component.maxY; - this.minX = component.minX; - this.maxX = component.maxX; + private final ShapeField.DecodedTriangle component; + /** component type */ + private final TYPE type; + + private TriangleTreeNode(ShapeField.DecodedTriangle component) { + this.minY = Math.min(Math.min(component.aY, component.bY), component.cY); + this.maxY = Math.max(Math.max(component.aY, component.bY), component.cY); + this.minX = Math.min(Math.min(component.aX, component.bX), component.cX); + this.maxX = Math.max(Math.max(component.aX, component.bX), component.cX); this.component = component; + this.type = getType(component); + } + + private static TYPE getType(ShapeField.DecodedTriangle triangle) { + // the issue in lucene: https://github.com/apache/lucene-solr/pull/927 + // can help here + if (triangle.aX == triangle.bX && triangle.aY == triangle.bY) { + if (triangle.aX == triangle.cX && triangle.aY == triangle.cY) { + return TYPE.POINT; + } + return TYPE.LINE; + } else if ((triangle.aX == triangle.cX && triangle.aY == triangle.cY) || + (triangle.bX == triangle.cX && triangle.bY == triangle.cY)) { + return TYPE.LINE; + } else { + return TYPE.TRIANGLE; + } } - @Override - public void writeTo(StreamOutput out) throws IOException { - BytesStreamOutput scratchBuffer = new BytesStreamOutput(); + private void writeTo(ByteBuffersDataOutput out) throws IOException { + ByteBuffersDataOutput scratchBuffer = ByteBuffersDataOutput.newResettableInstance(); writeMetadata(out); writeComponent(out); if (left != null) { @@ -302,7 +168,8 @@ public void writeTo(StreamOutput out) throws IOException { } } - private void writeNode(StreamOutput out, int parentMaxX, int parentMaxY, BytesStreamOutput scratchBuffer) throws IOException { + private void writeNode(ByteBuffersDataOutput out, int parentMaxX, int parentMaxY, + ByteBuffersDataOutput scratchBuffer) throws IOException { out.writeVLong((long) parentMaxX - maxX); out.writeVLong((long) parentMaxY - maxY); int size = nodeSize(false, parentMaxX, parentMaxY, scratchBuffer); @@ -313,19 +180,19 @@ private void writeNode(StreamOutput out, int parentMaxX, int parentMaxY, BytesSt left.writeNode(out, maxX, maxY, scratchBuffer); } if (right != null) { - int rightSize = right.nodeSize(true, maxX, maxY,scratchBuffer); + int rightSize = right.nodeSize(true, maxX, maxY, scratchBuffer); out.writeVInt(rightSize); right.writeNode(out, maxX, maxY, scratchBuffer); } } - private void writeMetadata(StreamOutput out) throws IOException { + private void writeMetadata(ByteBuffersDataOutput out) { byte metadata = 0; metadata |= (left != null) ? (1 << 0) : 0; metadata |= (right != null) ? (1 << 1) : 0; - if (component.type == TriangleTreeLeaf.TYPE.POINT) { + if (type == TYPE.POINT) { metadata |= (1 << 2); - } else if (component.type == TriangleTreeLeaf.TYPE.LINE) { + } else if (type == TYPE.LINE) { metadata |= (1 << 3); } else { metadata |= (component.ab) ? (1 << 4) : 0; @@ -335,11 +202,11 @@ private void writeMetadata(StreamOutput out) throws IOException { out.writeByte(metadata); } - private void writeComponent(StreamOutput out) throws IOException { - if (component.type == TriangleTreeLeaf.TYPE.POINT) { + private void writeComponent(ByteBuffersDataOutput out) throws IOException { + if (type == TYPE.POINT) { out.writeVLong((long) maxX - component.aX); out.writeVLong((long) maxY - component.aY); - } else if (component.type == TriangleTreeLeaf.TYPE.LINE) { + } else if (type == TYPE.LINE) { out.writeVLong((long) maxX - component.aX); out.writeVLong((long) maxY - component.aY); out.writeVLong((long) maxX - component.bX); @@ -354,19 +221,19 @@ private void writeComponent(StreamOutput out) throws IOException { } } - public int nodeSize(boolean includeBox, int parentMaxX, int parentMaxY, BytesStreamOutput scratchBuffer) throws IOException { + private int nodeSize(boolean includeBox, int parentMaxX, int parentMaxY, ByteBuffersDataOutput scratchBuffer) throws IOException { int size =0; size++; //metadata size += componentSize(scratchBuffer); if (left != null) { - size += left.nodeSize(true, maxX, maxY, scratchBuffer); + size += left.nodeSize(true, maxX, maxY, scratchBuffer); } if (right != null) { int rightSize = right.nodeSize(true, maxX, maxY, scratchBuffer); scratchBuffer.reset(); scratchBuffer.writeVLong(rightSize); - size += scratchBuffer.size(); // jump size - size += rightSize; + size += scratchBuffer.size(); // jump size + size += rightSize; } if (includeBox) { int jumpSize = size; @@ -374,17 +241,17 @@ public int nodeSize(boolean includeBox, int parentMaxX, int parentMaxY, BytesStr scratchBuffer.writeVLong((long) parentMaxX - maxX); scratchBuffer.writeVLong((long) parentMaxY - maxY); scratchBuffer.writeVLong(jumpSize); - size += scratchBuffer.size();// box + size += scratchBuffer.size(); // box size } return size; } - public int componentSize(BytesStreamOutput scratchBuffer) throws IOException { + private int componentSize(ByteBuffersDataOutput scratchBuffer) throws IOException { scratchBuffer.reset(); - if (component.type == TriangleTreeLeaf.TYPE.POINT) { + if (type == TYPE.POINT) { scratchBuffer.writeVLong((long) maxX - component.aX); scratchBuffer.writeVLong((long) maxY - component.aY); - } else if (component.type == TriangleTreeLeaf.TYPE.LINE) { + } else if (type == TYPE.LINE) { scratchBuffer.writeVLong((long) maxX - component.aX); scratchBuffer.writeVLong((long) maxY - component.aY); scratchBuffer.writeVLong((long) maxX - component.bX); @@ -397,236 +264,7 @@ public int componentSize(BytesStreamOutput scratchBuffer) throws IOException { scratchBuffer.writeVLong((long) maxX - component.cX); scratchBuffer.writeVLong((long) maxY - component.cY); } - return scratchBuffer.size(); - } - - } - - /** - * Represents an leaf of the tree containing one of the triangles. - */ - static class TriangleTreeLeaf { - - public enum TYPE { - POINT, LINE, TRIANGLE - } - - int minX, maxX, minY, maxY; - int aX, aY, bX, bY, cX, cY; - boolean ab, bc, ca; - TYPE type; - - // constructor for points - TriangleTreeLeaf(int aXencoded, int aYencoded) { - encodePoint(aXencoded, aYencoded); - } - - // constructor for points and lines - TriangleTreeLeaf(int aXencoded, int aYencoded, int bXencoded, int bYencoded) { - if (aXencoded == bXencoded && aYencoded == bYencoded) { - encodePoint(aXencoded, aYencoded); - } else { - encodeLine(aXencoded, aYencoded, bXencoded, bYencoded); - } - } - - // generic constructor - TriangleTreeLeaf(int aXencoded, int aYencoded, boolean ab, - int bXencoded, int bYencoded, boolean bc, - int cXencoded, int cYencoded, boolean ca) { - if (aXencoded == bXencoded && aYencoded == bYencoded) { - if (aXencoded == cXencoded && aYencoded == cYencoded) { - encodePoint(aYencoded, aXencoded); - } else { - encodeLine(aYencoded, aXencoded, cYencoded, cXencoded); - return; - } - } else if (aXencoded == cXencoded && aYencoded == cYencoded) { - encodeLine(aYencoded, aXencoded, bYencoded, bXencoded); - } else { - encodeTriangle(aXencoded, aYencoded, ab, bXencoded, bYencoded, bc, cXencoded, cYencoded, ca); - } - } - - private void encodePoint(int aXencoded, int aYencoded) { - this.type = TYPE.POINT; - aX = aXencoded; - aY = aYencoded; - minX = aX; - maxX = aX; - minY = aY; - maxY = aY; - } - - private void encodeLine(int aXencoded, int aYencoded, int bXencoded, int bYencoded) { - this.type = TYPE.LINE; - //rotate edges and place minX at the beginning - if (aXencoded > bXencoded) { - aX = bXencoded; - aY = bYencoded; - bX = aXencoded; - bY = aYencoded; - } else { - aX = aXencoded; - aY = aYencoded; - bX = bXencoded; - bY = bYencoded; - } - this.minX = aX; - this.maxX = bX; - this.minY = Math.min(aY, bY); - this.maxY = Math.max(aY, bY); - } - - private void encodeTriangle(int aXencoded, int aYencoded, boolean abFromShape, - int bXencoded, int bYencoded, boolean bcFromShape, - int cXencoded, int cYencoded, boolean caFromShape) { - - int aX, aY, bX, bY, cX, cY; - boolean ab, bc, ca; - //change orientation if CW - if (GeoUtils.orient(aXencoded, aYencoded, bXencoded, bYencoded, cXencoded, cYencoded) == -1) { - aX = cXencoded; - bX = bXencoded; - cX = aXencoded; - aY = cYencoded; - bY = bYencoded; - cY = aYencoded; - ab = bcFromShape; - bc = abFromShape; - ca = caFromShape; - } else { - aX = aXencoded; - bX = bXencoded; - cX = cXencoded; - aY = aYencoded; - bY = bYencoded; - cY = cYencoded; - ab = abFromShape; - bc = bcFromShape; - ca = caFromShape; - } - //rotate edges and place minX at the beginning - if (bX < aX || cX < aX) { - if (bX < cX) { - int tempX = aX; - int tempY = aY; - boolean tempBool = ab; - aX = bX; - aY = bY; - ab = bc; - bX = cX; - bY = cY; - bc = ca; - cX = tempX; - cY = tempY; - ca = tempBool; - } else if (cX < aX) { - int tempX = aX; - int tempY = aY; - boolean tempBool = ab; - aX = cX; - aY = cY; - ab = ca; - cX = bX; - cY = bY; - ca = bc; - bX = tempX; - bY = tempY; - bc = tempBool; - } - } else if (aX == bX && aX == cX) { - //degenerated case, all points with same longitude - //we need to prevent that aX is in the middle (not part of the MBS) - if (bY < aY || cY < aY) { - if (bY < cY) { - int tempX = aX; - int tempY = aY; - boolean tempBool = ab; - aX = bX; - aY = bY; - ab = bc; - bX = cX; - bY = cY; - bc = ca; - cX = tempX; - cY = tempY; - ca = tempBool; - } else if (cY < aY) { - int tempX = aX; - int tempY = aY; - boolean tempBool = ab; - aX = cX; - aY = cY; - ab = ca; - cX = bX; - cY = bY; - ca = bc; - bX = tempX; - bY = tempY; - bc = tempBool; - } - } - } - this.aX = aX; - this.aY = aY; - this.bX = bX; - this.bY = bY; - this.cX = cX; - this.cY = cY; - this.ab = ab; - this.bc = bc; - this.ca = ca; - this.minX = aX; - this.maxX = Math.max(aX, Math.max(bX, cX)); - this.minY = Math.min(aY, Math.min(bY, cY)); - this.maxY = Math.max(aY, Math.max(bY, cY)); - type = TYPE.TRIANGLE; - } - - private static List fromPoints(CoordinateEncoder encoder, Point... points) { - List triangles = new ArrayList<>(points.length); - for (int i = 0; i < points.length; i++) { - triangles.add(new TriangleTreeLeaf(encoder.encodeX(points[i].getX()), encoder.encodeY(points[i].getY()))); - } - return triangles; - } - - private static List fromRectangle(CoordinateEncoder encoder, Rectangle... rectangles) { - List triangles = new ArrayList<>(2 * rectangles.length); - for (Rectangle r : rectangles) { - triangles.add(new TriangleTreeLeaf( - encoder.encodeX(r.getMinX()), encoder.encodeY(r.getMinY()), true, - encoder.encodeX(r.getMaxX()), encoder.encodeY(r.getMinY()), false, - encoder.encodeX(r.getMinX()), encoder.encodeY(r.getMaxY()), true)); - triangles.add(new TriangleTreeLeaf( - encoder.encodeX(r.getMinX()), encoder.encodeY(r.getMaxY()), false, - encoder.encodeX(r.getMaxX()), encoder.encodeY(r.getMinY()), true, - encoder.encodeX(r.getMaxX()), encoder.encodeY(r.getMaxY()), true)); - } - return triangles; - } - - private static List fromLine(CoordinateEncoder encoder, org.apache.lucene.geo.Line line) { - List triangles = new ArrayList<>(line.numPoints() - 1); - for (int i = 0, j = 1; i < line.numPoints() - 1; i++, j++) { - triangles.add(new TriangleTreeLeaf(encoder.encodeX(line.getLon(i)), encoder.encodeY(line.getLat(i)), - encoder.encodeX(line.getLon(j)), encoder.encodeY(line.getLat(j)))); - } - return triangles; - } - - private static List fromPolygon(CoordinateEncoder encoder, org.apache.lucene.geo.Polygon polygon) { - // TODO: We are going to be tessellating the polygon twice, can we do something? - // TODO: Tessellator seems to have some reference to the encoding but does not need to have. - List tessellation = Tessellator.tessellate(polygon); - List triangles = new ArrayList<>(tessellation.size()); - for (Tessellator.Triangle t : tessellation) { - triangles.add(new TriangleTreeLeaf(encoder.encodeX(t.getX(0)), encoder.encodeY(t.getY(0)), t.isEdgefromPolygon(0), - encoder.encodeX(t.getX(1)), encoder.encodeY(t.getY(1)), t.isEdgefromPolygon(1), - encoder.encodeX(t.getX(2)), encoder.encodeY(t.getY(2)), t.isEdgefromPolygon(2))); - } - return triangles; + return Math.toIntExact(scratchBuffer.size()); } } } diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoValues.java b/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoValues.java index 893d020ebc657..169597f5d9572 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoValues.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoValues.java @@ -18,7 +18,12 @@ */ package org.elasticsearch.index.fielddata; +import org.apache.lucene.document.ShapeField; +import org.apache.lucene.index.IndexableField; import org.apache.lucene.spatial.util.GeoRelationUtils; +import org.apache.lucene.store.ByteBuffersDataOutput; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.geo.CentroidCalculator; import org.elasticsearch.common.geo.CoordinateEncoder; import org.elasticsearch.common.geo.Extent; import org.elasticsearch.common.geo.GeoPoint; @@ -26,15 +31,17 @@ import org.elasticsearch.common.geo.GeoShapeCoordinateEncoder; import org.elasticsearch.common.geo.TriangleTreeReader; import org.elasticsearch.common.geo.TriangleTreeWriter; -import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Rectangle; import org.elasticsearch.geometry.utils.GeographyValidator; import org.elasticsearch.geometry.utils.WellKnownText; +import org.elasticsearch.index.mapper.GeoShapeIndexer; import org.elasticsearch.search.aggregations.support.ValuesSourceType; import java.io.IOException; import java.text.ParseException; +import java.util.Arrays; +import java.util.List; /** * A stateful lightweight per document set of geo values. @@ -84,8 +91,10 @@ protected MultiGeoValues() { public static class GeoPointValue implements GeoValue { private final GeoPoint geoPoint; + private final BoundingBox boundingBox; public GeoPointValue(GeoPoint geoPoint) { + this.boundingBox = new BoundingBox(); this.geoPoint = geoPoint; } @@ -95,13 +104,14 @@ public GeoPoint geoPoint() { @Override public BoundingBox boundingBox() { - return new BoundingBox(geoPoint); + boundingBox.reset(geoPoint); + return boundingBox; } @Override public GeoRelation relate(Rectangle rectangle) { if (GeoRelationUtils.pointInRectPrecise(geoPoint.lat(), geoPoint.lon(), - rectangle.getMinLat(), rectangle.getMaxLat(), rectangle.getMinLon(), rectangle.getMaxLon())) { + rectangle.getMinLat(), rectangle.getMaxLat(), rectangle.getMinLon(), rectangle.getMaxLon())) { return GeoRelation.QUERY_CROSSES; } return GeoRelation.QUERY_DISJOINT; @@ -127,18 +137,17 @@ public static class GeoShapeValue implements GeoValue { private static final WellKnownText MISSING_GEOMETRY_PARSER = new WellKnownText(true, new GeographyValidator(true)); private final TriangleTreeReader reader; + private final BoundingBox boundingBox; public GeoShapeValue(TriangleTreeReader reader) { this.reader = reader; + this.boundingBox = new BoundingBox(); } @Override public BoundingBox boundingBox() { - try { - return new BoundingBox(reader.getExtent(), GeoShapeCoordinateEncoder.INSTANCE); - } catch (IOException e) { - throw new IllegalStateException("unable to read bounding box", e); - } + boundingBox.reset(reader.getExtent(), GeoShapeCoordinateEncoder.INSTANCE); + return boundingBox; } /** @@ -150,20 +159,12 @@ public GeoRelation relate(Rectangle rectangle) { int maxX = GeoShapeCoordinateEncoder.INSTANCE.encodeX(rectangle.getMaxX()); int minY = GeoShapeCoordinateEncoder.INSTANCE.encodeY(rectangle.getMinY()); int maxY = GeoShapeCoordinateEncoder.INSTANCE.encodeY(rectangle.getMaxY()); - try { - return reader.relate(minX, minY, maxX, maxY); - } catch (IOException e) { - throw new IllegalStateException("unable to check intersection", e); - } + return reader.relate(minX, minY, maxX, maxY); } @Override public double lat() { - try { - return reader.getCentroidY(); - } catch (IOException e) { - throw new IllegalStateException("unable to read centroid of shape", e); - } + return reader.getCentroidY(); } /** @@ -171,26 +172,40 @@ public double lat() { */ @Override public double lon() { - try { - return reader.getCentroidX(); - } catch (IOException e) { - throw new IllegalStateException("unable to read centroid of shape", e); - } + return reader.getCentroidX(); } public static GeoShapeValue missing(String missing) { try { Geometry geometry = MISSING_GEOMETRY_PARSER.fromWKT(missing); - TriangleTreeWriter writer = new TriangleTreeWriter(geometry, GeoShapeCoordinateEncoder.INSTANCE); - BytesStreamOutput output = new BytesStreamOutput(); + ShapeField.DecodedTriangle[] triangles = toDecodedTriangles(geometry); + TriangleTreeWriter writer = + new TriangleTreeWriter(Arrays.asList(triangles), GeoShapeCoordinateEncoder.INSTANCE, + new CentroidCalculator(geometry)); + ByteBuffersDataOutput output = new ByteBuffersDataOutput(); writer.writeTo(output); - TriangleTreeReader reader = new TriangleTreeReader(GeoShapeCoordinateEncoder.INSTANCE); - reader.reset(output.bytes().toBytesRef()); + TriangleTreeReader reader = new TriangleTreeReader(GeoShapeCoordinateEncoder.INSTANCE); + reader.reset(new BytesRef(output.toArrayCopy(), 0, Math.toIntExact(output.size()))); return new GeoShapeValue(reader); } catch (IOException | ParseException e) { throw new IllegalArgumentException("Can't apply missing value [" + missing + "]", e); } } + + private static ShapeField.DecodedTriangle[] toDecodedTriangles(Geometry geometry) { + GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test"); + geometry = indexer.prepareForIndexing(geometry); + List fields = indexer.indexShape(null, geometry); + ShapeField.DecodedTriangle[] triangles = new ShapeField.DecodedTriangle[fields.size()]; + final byte[] scratch = new byte[7 * Integer.BYTES]; + for (int i = 0; i < fields.size(); i++) { + BytesRef bytesRef = fields.get(i).binaryValue(); + assert bytesRef.length == 7 * Integer.BYTES; + System.arraycopy(bytesRef.bytes, bytesRef.offset, scratch, 0, 7 * Integer.BYTES); + ShapeField.decodeTriangle(scratch, triangles[i] = new ShapeField.DecodedTriangle()); + } + return triangles; + } } /** @@ -205,14 +220,17 @@ public interface GeoValue { } public static class BoundingBox { - public final double top; - public final double bottom; - public final double negLeft; - public final double negRight; - public final double posLeft; - public final double posRight; - - public BoundingBox(Extent extent, CoordinateEncoder coordinateEncoder) { + 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) { @@ -237,7 +255,7 @@ public BoundingBox(Extent extent, CoordinateEncoder coordinateEncoder) { } } - BoundingBox(GeoPoint point) { + private void reset(GeoPoint point) { this.top = point.lat(); this.bottom = point.lat(); if (point.lon() < 0) { @@ -252,6 +270,7 @@ public BoundingBox(Extent extent, CoordinateEncoder coordinateEncoder) { this.posRight = point.lon(); } } + /** * @return the minimum y-coordinate of the extent */ @@ -280,6 +299,5 @@ public double maxX() { return Math.max(negRight, posRight); } - } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java index e9c6088205b92..bd3a57752115a 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java @@ -18,14 +18,17 @@ */ package org.elasticsearch.index.mapper; +import org.apache.lucene.document.ShapeField; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.Term; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; +import org.apache.lucene.util.BytesRef; import org.elasticsearch.Version; import org.elasticsearch.common.Explicit; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.geo.CentroidCalculator; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.geo.SpatialStrategy; import org.elasticsearch.common.geo.builders.ShapeBuilder; @@ -81,7 +84,8 @@ public interface Indexer { List indexShape(ParseContext context, Processed shape); - void indexDocValueField(ParseContext context, Processed shape); + void indexDocValueField(ParseContext context, ShapeField.DecodedTriangle[] triangles, + CentroidCalculator centroidCalculator); } /** @@ -436,10 +440,18 @@ public void parse(ParseContext context) throws IOException { shape = geometryIndexer.prepareForIndexing(geometry); } - List fields = new ArrayList<>(); - fields.addAll(geometryIndexer.indexShape(context, shape)); + List fields = new ArrayList<>(geometryIndexer.indexShape(context, shape)); + final byte[] scratch = new byte[7 * Integer.BYTES]; if (fieldType().hasDocValues()) { - geometryIndexer.indexDocValueField(context, shape); + // doc values are generated from the indexed fields. + ShapeField.DecodedTriangle[] triangles = new ShapeField.DecodedTriangle[fields.size()]; + for (int i =0; i < fields.size(); i++) { + BytesRef bytesRef = fields.get(i).binaryValue(); + assert bytesRef.length == 7 * Integer.BYTES; + System.arraycopy(bytesRef.bytes, bytesRef.offset, scratch, 0, 7 * Integer.BYTES); + ShapeField.decodeTriangle(scratch, triangles[i] = new ShapeField.DecodedTriangle()); + } + geometryIndexer.indexDocValueField(context, triangles, new CentroidCalculator((Geometry) shape)); } createFieldNamesField(context, fields); for (IndexableField field : fields) { diff --git a/server/src/main/java/org/elasticsearch/index/mapper/BinaryGeoShapeDocValuesField.java b/server/src/main/java/org/elasticsearch/index/mapper/BinaryGeoShapeDocValuesField.java index 9702fcb2403e3..10884ba41ee41 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/BinaryGeoShapeDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/BinaryGeoShapeDocValuesField.java @@ -18,45 +18,43 @@ */ package org.elasticsearch.index.mapper; +import org.apache.lucene.document.ShapeField; +import org.apache.lucene.store.ByteBuffersDataOutput; import org.apache.lucene.util.BytesRef; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.geo.CentroidCalculator; import org.elasticsearch.common.geo.GeoShapeCoordinateEncoder; import org.elasticsearch.common.geo.TriangleTreeWriter; -import org.elasticsearch.common.io.stream.BytesStreamOutput; -import org.elasticsearch.geometry.Geometry; -import org.elasticsearch.geometry.GeometryCollection; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class BinaryGeoShapeDocValuesField extends CustomDocValuesField { - private List geometries; + private final List triangles; + private final CentroidCalculator centroidCalculator; - public BinaryGeoShapeDocValuesField(String name, Geometry geometry) { + public BinaryGeoShapeDocValuesField(String name, ShapeField.DecodedTriangle[] triangles, CentroidCalculator centroidCalculator) { super(name); - this.geometries = new ArrayList<>(1); - add(geometry); + this.triangles = new ArrayList<>(triangles.length); + this.centroidCalculator = centroidCalculator; + this.triangles.addAll(Arrays.asList(triangles)); } - public void add(Geometry geometry) { - geometries.add(geometry); + public void add(ShapeField.DecodedTriangle[] triangles, CentroidCalculator centroidCalculator) { + this.triangles.addAll(Arrays.asList(triangles)); + this.centroidCalculator.addFrom(centroidCalculator); } @Override public BytesRef binaryValue() { try { - final Geometry geometry; - if (geometries.size() > 1) { - geometry = new GeometryCollection(geometries); - } else { - geometry = geometries.get(0); - } - final TriangleTreeWriter writer = new TriangleTreeWriter(geometry, GeoShapeCoordinateEncoder.INSTANCE); - BytesStreamOutput output = new BytesStreamOutput(); + final TriangleTreeWriter writer = new TriangleTreeWriter(triangles, GeoShapeCoordinateEncoder.INSTANCE, centroidCalculator); + ByteBuffersDataOutput output = new ByteBuffersDataOutput(); writer.writeTo(output); - return output.bytes().toBytesRef(); + return new BytesRef(output.toArrayCopy(), 0, Math.toIntExact(output.size())); } catch (IOException e) { throw new ElasticsearchException("failed to encode shape", e); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeIndexer.java b/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeIndexer.java index 4b8db174ca7df..9c8cea248a049 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeIndexer.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeIndexer.java @@ -21,8 +21,10 @@ package org.elasticsearch.index.mapper; import org.apache.lucene.document.LatLonShape; +import org.apache.lucene.document.ShapeField; import org.apache.lucene.index.IndexableField; import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.geo.CentroidCalculator; import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.geometry.Circle; import org.elasticsearch.geometry.Geometry; @@ -197,14 +199,14 @@ public List indexShape(ParseContext context, Geometry shape) { } @Override - public void indexDocValueField(ParseContext context, Geometry shape) { + public void indexDocValueField(ParseContext context, ShapeField.DecodedTriangle[] triangles, CentroidCalculator calculator) { BinaryGeoShapeDocValuesField docValuesField = (BinaryGeoShapeDocValuesField) context.doc().getByKey(name); if (docValuesField == null) { - docValuesField = new BinaryGeoShapeDocValuesField(name, shape); + docValuesField = new BinaryGeoShapeDocValuesField(name, triangles, calculator); context.doc().addWithKey(name, docValuesField); } else { - docValuesField.add(shape); + docValuesField.add(triangles, calculator); } } @@ -1075,7 +1077,6 @@ private void addFields(IndexableField[] fields) { } } - public static org.apache.lucene.geo.Polygon toLucenePolygon(Polygon polygon) { org.apache.lucene.geo.Polygon[] holes = new org.apache.lucene.geo.Polygon[polygon.getNumberOfHoles()]; for(int i = 0; i indexShape(ParseContext context, Shape shape) { } @Override - public void indexDocValueField(ParseContext context, Shape shape) { + public void indexDocValueField(ParseContext context, ShapeField.DecodedTriangle[] triangles, CentroidCalculator centroidCalculator) { throw new UnsupportedOperationException("doc values not supported for legacy shape indexer"); } } diff --git a/server/src/test/java/org/elasticsearch/common/geo/CentroidCalculatorTests.java b/server/src/test/java/org/elasticsearch/common/geo/CentroidCalculatorTests.java index 43c5e4a02134b..c3933b9361310 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/CentroidCalculatorTests.java +++ b/server/src/test/java/org/elasticsearch/common/geo/CentroidCalculatorTests.java @@ -18,6 +18,9 @@ */ package org.elasticsearch.common.geo; +import org.elasticsearch.geometry.Geometry; +import org.elasticsearch.geometry.Line; +import org.elasticsearch.geometry.Point; import org.elasticsearch.test.ESTestCase; import static org.hamcrest.Matchers.equalTo; @@ -25,20 +28,28 @@ public class CentroidCalculatorTests extends ESTestCase { public void test() { - double[] x = new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - double[] y = new double[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; - double[] xRunningAvg = new double[] { 1, 1.5, 2.0, 2.5, 3, 3.5, 4, 4.5, 5, 5.5 }; - double[] yRunningAvg = new double[] { 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 }; - CentroidCalculator calculator = new CentroidCalculator(); - for (int i = 0; i < 10; i++) { - calculator.addCoordinate(x[i], y[i]); + double[] y = new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + double[] x = new double[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; + double[] yRunningAvg = new double[] { 1, 1.5, 2.0, 2.5, 3, 3.5, 4, 4.5, 5, 5.5 }; + double[] xRunningAvg = new double[] { 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 }; + + Point point = new Point(x[0], y[0]); + CentroidCalculator calculator = new CentroidCalculator(point); + assertThat(calculator.getX(), equalTo(xRunningAvg[0])); + assertThat(calculator.getY(), equalTo(yRunningAvg[0])); + for (int i = 1; i < 10; i++) { + double[] subX = new double[i + 1]; + double[] subY = new double[i + 1]; + System.arraycopy(x, 0, subX, 0, i + 1); + System.arraycopy(y, 0, subY, 0, i + 1); + Geometry geometry = new Line(subX, subY); + calculator = new CentroidCalculator(geometry); assertThat(calculator.getX(), equalTo(xRunningAvg[i])); assertThat(calculator.getY(), equalTo(yRunningAvg[i])); } - CentroidCalculator otherCalculator = new CentroidCalculator(); - otherCalculator.addCoordinate(0.0, 0.0); + CentroidCalculator otherCalculator = new CentroidCalculator(new Point(0, 0)); calculator.addFrom(otherCalculator); - assertThat(calculator.getX(), equalTo(5.0)); - assertThat(calculator.getY(), equalTo(50.0)); + assertThat(calculator.getX(), equalTo(50.0)); + assertThat(calculator.getY(), equalTo(5.0)); } } diff --git a/server/src/test/java/org/elasticsearch/common/geo/ExtentTests.java b/server/src/test/java/org/elasticsearch/common/geo/ExtentTests.java index 378fe9e3f054f..db230df45a133 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/ExtentTests.java +++ b/server/src/test/java/org/elasticsearch/common/geo/ExtentTests.java @@ -18,15 +18,11 @@ */ package org.elasticsearch.common.geo; -import org.elasticsearch.Version; -import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.test.AbstractWireSerializingTestCase; - -import java.io.IOException; +import org.elasticsearch.test.ESTestCase; import static org.hamcrest.Matchers.equalTo; -public class ExtentTests extends AbstractWireSerializingTestCase { +public class ExtentTests extends ESTestCase { public void testFromPoint() { int x = randomFrom(-1, 0, 1); @@ -89,21 +85,4 @@ public void testAddRectangle() { assertThat(extent.minY(), equalTo(bottomLeftY2)); assertThat(extent.maxY(), equalTo(topRightY2)); } - - @Override - protected Extent createTestInstance() { - return new Extent(randomIntBetween(-10, 10), randomIntBetween(-10, 10), randomIntBetween(-10, 10), - randomIntBetween(-10, 10), randomIntBetween(-10, 10), randomIntBetween(-10, 10)); - } - - @Override - protected Writeable.Reader instanceReader() { - return Extent::new; - } - - @Override - protected Object copyInstance(Object instance, Version version) throws IOException { - Extent other = (Extent) instance; - return new Extent(other.top, other.bottom, other.negLeft, other.negRight, other.posLeft, other.posRight); - } } diff --git a/server/src/test/java/org/elasticsearch/common/geo/GeoTestUtils.java b/server/src/test/java/org/elasticsearch/common/geo/GeoTestUtils.java index eb54ecba06ae4..296029464ca7d 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/GeoTestUtils.java +++ b/server/src/test/java/org/elasticsearch/common/geo/GeoTestUtils.java @@ -18,16 +18,23 @@ */ package org.elasticsearch.common.geo; +import org.apache.lucene.document.ShapeField; +import org.apache.lucene.index.IndexableField; +import org.apache.lucene.store.ByteBuffersDataOutput; +import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.geometry.Geometry; +import org.elasticsearch.index.mapper.GeoShapeIndexer; + import java.io.IOException; +import java.util.Arrays; +import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -39,13 +46,28 @@ public static void assertRelation(GeoRelation expectedRelation, TriangleTreeRead assertThat(actualRelation, equalTo(expectedRelation)); } + public static ShapeField.DecodedTriangle[] toDecodedTriangles(Geometry geometry) throws IOException { + GeoShapeIndexer indexer = new GeoShapeIndexer(true, "test"); + geometry = indexer.prepareForIndexing(geometry); + List fields = indexer.indexShape(null, geometry); + ShapeField.DecodedTriangle[] triangles = new ShapeField.DecodedTriangle[fields.size()]; + final byte[] scratch = new byte[7 * Integer.BYTES]; + for (int i = 0; i < fields.size(); i++) { + BytesRef bytesRef = fields.get(i).binaryValue(); + assert bytesRef.length == 7 * Integer.BYTES; + System.arraycopy(bytesRef.bytes, bytesRef.offset, scratch, 0, 7 * Integer.BYTES); + ShapeField.decodeTriangle(scratch, triangles[i] = new ShapeField.DecodedTriangle()); + } + return triangles; + } + public static TriangleTreeReader triangleTreeReader(Geometry geometry, CoordinateEncoder encoder) throws IOException { - TriangleTreeWriter writer = new TriangleTreeWriter(geometry, encoder); - BytesStreamOutput output = new BytesStreamOutput(); + ShapeField.DecodedTriangle[] triangles = toDecodedTriangles(geometry); + TriangleTreeWriter writer = new TriangleTreeWriter(Arrays.asList(triangles), encoder, new CentroidCalculator(geometry)); + ByteBuffersDataOutput output = new ByteBuffersDataOutput(); writer.writeTo(output); - output.close(); TriangleTreeReader reader = new TriangleTreeReader(encoder); - reader.reset(output.bytes().toBytesRef()); + reader.reset(new BytesRef(output.toArrayCopy(), 0, Math.toIntExact(output.size()))); return reader; } diff --git a/server/src/test/java/org/elasticsearch/common/geo/TriangleTreeTests.java b/server/src/test/java/org/elasticsearch/common/geo/TriangleTreeTests.java index 99c24fe1b6298..049ae31d06f47 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/TriangleTreeTests.java +++ b/server/src/test/java/org/elasticsearch/common/geo/TriangleTreeTests.java @@ -48,64 +48,69 @@ public class TriangleTreeTests extends ESTestCase { public void testRectangleShape() throws IOException { for (int i = 0; i < 1000; i++) { - int minX = randomIntBetween(-80, 70); - int maxX = randomIntBetween(minX + 10, 80); - int minY = randomIntBetween(-80, 70); - int maxY = randomIntBetween(minY + 10, 80); + int minX = randomIntBetween(-40, -1); + int maxX = randomIntBetween(1, 40); + int minY = randomIntBetween(-40, -1); + int maxY = randomIntBetween(1, 40); double[] x = new double[]{minX, maxX, maxX, minX, minX}; double[] y = new double[]{minY, minY, maxY, maxY, minY}; Geometry rectangle = randomBoolean() ? new Polygon(new LinearRing(x, y), Collections.emptyList()) : new Rectangle(minX, maxX, maxY, minY); - TriangleTreeReader reader = triangleTreeReader(rectangle, TestCoordinateEncoder.INSTANCE); + TriangleTreeReader reader = triangleTreeReader(rectangle, GeoShapeCoordinateEncoder.INSTANCE); - assertThat(Extent.fromPoints(minX, minY, maxX, maxY), equalTo(reader.getExtent())); - // encoder loses precision when casting to integer, so centroid is calculated using integer division here - assertThat(reader.getCentroidX(), equalTo((double) ((minX + maxX) / 2))); - assertThat(reader.getCentroidY(), equalTo((double) ((minY + maxY) / 2))); + Extent expectedExtent = getExtentFromBox(minX, minY, maxX, maxY); + assertThat(expectedExtent, equalTo(reader.getExtent())); + // centroid is calculated using original double values but then loses precision as it is serialized as an integer + int encodedCentroidX = GeoShapeCoordinateEncoder.INSTANCE.encodeX(((double) minX + maxX) / 2); + int encodedCentroidY = GeoShapeCoordinateEncoder.INSTANCE.encodeY(((double) minY + maxY) / 2); + assertThat(reader.getCentroidX(), equalTo(GeoShapeCoordinateEncoder.INSTANCE.decodeX(encodedCentroidX))); + assertThat(reader.getCentroidY(), equalTo(GeoShapeCoordinateEncoder.INSTANCE.decodeY(encodedCentroidY))); // box-query touches bottom-left corner - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(minX - randomIntBetween(1, 180), - minY - randomIntBetween(1, 180), minX, minY)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(minX - randomIntBetween(1, 180 + minX), + minY - randomIntBetween(1, 90 + minY), minX, minY)); // box-query touches bottom-right corner - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(maxX, minY - randomIntBetween(1, 180), - maxX + randomIntBetween(1, 180), minY)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(maxX, minY - randomIntBetween(1, 90 + minY), + maxX + randomIntBetween(1, 180 - maxX), minY)); // box-query touches top-right corner - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(maxX, maxY, maxX + randomIntBetween(1, 180), - maxY + randomIntBetween(1, 180))); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(maxX, maxY, maxX + randomIntBetween(1, 180 - maxX), + maxY + randomIntBetween(1, 90 - maxY))); // box-query touches top-left corner - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(minX - randomIntBetween(1, 180), maxY, minX, - maxY + randomIntBetween(1, 180))); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(minX - randomIntBetween(1, 180 + minX), maxY, minX, + maxY + randomIntBetween(1, 90 - maxY))); // box-query fully-enclosed inside rectangle - assertRelation(GeoRelation.QUERY_INSIDE,reader, Extent.fromPoints((3 * minX + maxX) / 4, (3 * minY + maxY) / 4, - (3 * maxX + minX) / 4, (3 * maxY + minY) / 4)); + assertRelation(GeoRelation.QUERY_INSIDE, reader, getExtentFromBox(3 * (minX + maxX) / 4, 3 * (minY + maxY) / 4, + 3 * (maxX + minX) / 4, 3 * (maxY + minY) / 4)); // box-query fully-contains poly - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(minX - randomIntBetween(1, 180), - minY - randomIntBetween(1, 180), maxX + randomIntBetween(1, 180), maxY + randomIntBetween(1, 180))); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(minX - randomIntBetween(1, 180 + minX), + minY - randomIntBetween(1, 90 + minY), maxX + randomIntBetween(1, 180 - maxX), + maxY + randomIntBetween(1, 90 - maxY))); // box-query half-in-half-out-right - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints((3 * minX + maxX) / 4, (3 * minY + maxY) / 4, - maxX + randomIntBetween(1, 1000), (3 * maxY + minY) / 4)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(3 * (minX + maxX) / 4, 3 * (minY + maxY) / 4, + maxX + randomIntBetween(1, 90 - maxY), 3 * (maxY + minY) / 4)); // box-query half-in-half-out-left - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(minX - randomIntBetween(1, 1000), (3 * minY + maxY) / 4, - (3 * maxX + minX) / 4, (3 * maxY + minY) / 4)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(minX - randomIntBetween(1, 180 + minX), + 3 * (minY + maxY) / 4, 3 * (maxX + minX) / 4, 3 * (maxY + minY) / 4)); // box-query half-in-half-out-top - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints((3 * minX + maxX) / 4, (3 * minY + maxY) / 4, - maxX + randomIntBetween(1, 1000), maxY + randomIntBetween(1, 1000))); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(3 * (minX + maxX) / 4, 3 * (minY + maxY) / 4, + maxX + randomIntBetween(1, 180 - maxX), maxY + randomIntBetween(1, 90 - maxY))); // box-query half-in-half-out-bottom - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints((3 * minX + maxX) / 4, minY - randomIntBetween(1, 1000), - maxX + randomIntBetween(1, 1000), (3 * maxY + minY) / 4)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(3 * (minX + maxX) / 4, + minY - randomIntBetween(1, 90 + minY), maxX + randomIntBetween(1, 180 - maxX), + 3 * (maxY + minY) / 4)); // box-query outside to the right - assertRelation(GeoRelation.QUERY_DISJOINT, reader, Extent.fromPoints(maxX + randomIntBetween(1, 1000), minY, - maxX + randomIntBetween(1001, 2000), maxY)); + assertRelation(GeoRelation.QUERY_DISJOINT, reader, getExtentFromBox(maxX + randomIntBetween(1, 180 - maxX), minY, + maxX + randomIntBetween(1, 180 - maxX), maxY)); // box-query outside to the left - assertRelation(GeoRelation.QUERY_DISJOINT, reader, Extent.fromPoints(maxX - randomIntBetween(1001, 2000), minY, - minX - randomIntBetween(1, 1000), maxY)); + assertRelation(GeoRelation.QUERY_DISJOINT, reader, getExtentFromBox(maxX - randomIntBetween(1, 180 - maxX), minY, + minX - randomIntBetween(1, 180 + minX), maxY)); // box-query outside to the top - assertRelation(GeoRelation.QUERY_DISJOINT, reader, Extent.fromPoints(minX, maxY + randomIntBetween(1, 1000), maxX, - maxY + randomIntBetween(1001, 2000))); + assertRelation(GeoRelation.QUERY_DISJOINT, reader, getExtentFromBox(minX, maxY + randomIntBetween(1, 90 - maxY), maxX, + maxY + randomIntBetween(1, 90 - maxY))); // box-query outside to the bottom - assertRelation(GeoRelation.QUERY_DISJOINT, reader, Extent.fromPoints(minX, minY - randomIntBetween(1001, 2000), maxX, - minY - randomIntBetween(1, 1000))); + assertRelation(GeoRelation.QUERY_DISJOINT, reader, getExtentFromBox(minX, minY - randomIntBetween(1, 90 + minY), maxX, + minY - randomIntBetween(1, 90 + minY))); } } @@ -117,10 +122,10 @@ public void testPacManPolygon() throws Exception { // test cell crossing poly TriangleTreeReader reader = triangleTreeReader(new Polygon(new LinearRing(py, px), Collections.emptyList()), TestCoordinateEncoder.INSTANCE); - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(2, -1, 11, 1)); - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(-12, -12, 12, 12)); - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(-2, -1, 2, 0)); - assertRelation(GeoRelation.QUERY_INSIDE, reader, Extent.fromPoints(-5, -6, 2, -2)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(2, -1, 11, 1)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(-12, -12, 12, 12)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(-2, -1, 2, 0)); + assertRelation(GeoRelation.QUERY_INSIDE, reader, getExtentFromBox(-5, -6, 2, -2)); } // adapted from org.apache.lucene.geo.TestPolygon2D#testMultiPolygon @@ -128,14 +133,14 @@ public void testPolygonWithHole() throws Exception { Polygon polyWithHole = new Polygon(new LinearRing(new double[]{-50, 50, 50, -50, -50}, new double[]{-50, -50, 50, 50, -50}), Collections.singletonList(new LinearRing(new double[]{-10, 10, 10, -10, -10}, new double[]{-10, -10, 10, 10, -10}))); - TriangleTreeReader reader = triangleTreeReader(polyWithHole, TestCoordinateEncoder.INSTANCE); + TriangleTreeReader reader = triangleTreeReader(polyWithHole, GeoShapeCoordinateEncoder.INSTANCE); - assertRelation(GeoRelation.QUERY_DISJOINT, reader, Extent.fromPoints(6, -6, 6, -6)); // in the hole - assertRelation(GeoRelation.QUERY_INSIDE, reader, Extent.fromPoints(25, -25, 25, -25)); // on the mainland - assertRelation(GeoRelation.QUERY_DISJOINT, reader, Extent.fromPoints(51, 51, 52, 52)); // outside of mainland - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(-60, -60, 60, 60)); // enclosing us completely - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(49, 49, 51, 51)); // overlapping the mainland - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(9, 9, 11, 11)); // overlapping the hole + assertRelation(GeoRelation.QUERY_DISJOINT, reader, getExtentFromBox(6, -6, 6, -6)); // in the hole + assertRelation(GeoRelation.QUERY_INSIDE, reader, getExtentFromBox(25, -25, 25, -25)); // on the mainland + assertRelation(GeoRelation.QUERY_DISJOINT, reader, getExtentFromBox(51, 51, 52, 52)); // outside of mainland + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(-60, -60, 60, 60)); // enclosing us completely + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(49, 49, 51, 51)); // overlapping the mainland + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(9, 9, 11, 11)); // overlapping the hole } public void testCombPolygon() throws Exception { @@ -146,11 +151,11 @@ public void testCombPolygon() throws Exception { double[] hy = {1, 20, 20, 1, 1}; Polygon polyWithHole = new Polygon(new LinearRing(px, py), Collections.singletonList(new LinearRing(hx, hy))); - TriangleTreeReader reader = triangleTreeReader(polyWithHole, TestCoordinateEncoder.INSTANCE); + TriangleTreeReader reader = triangleTreeReader(polyWithHole, GeoShapeCoordinateEncoder.INSTANCE); // test cell crossing poly - assertRelation(GeoRelation.QUERY_INSIDE, reader, Extent.fromPoints(5, 10, 5, 10)); - assertRelation(GeoRelation.QUERY_DISJOINT, reader, Extent.fromPoints(15, 10, 15, 10)); - assertRelation(GeoRelation.QUERY_DISJOINT, reader, Extent.fromPoints(25, 10, 25, 10)); + assertRelation(GeoRelation.QUERY_INSIDE, reader, getExtentFromBox(5, 10, 5, 10)); + assertRelation(GeoRelation.QUERY_DISJOINT, reader, getExtentFromBox(15, 10, 15, 10)); + assertRelation(GeoRelation.QUERY_DISJOINT, reader, getExtentFromBox(25, 10, 25, 10)); } public void testPacManClosedLineString() throws Exception { @@ -159,11 +164,11 @@ public void testPacManClosedLineString() throws Exception { double[] py = {0, 5, 9, 10, 9, 0, -9, -10, -9, -5, 0}; // test cell crossing poly - TriangleTreeReader reader = triangleTreeReader(new Line(px, py), TestCoordinateEncoder.INSTANCE); - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(2, -1, 11, 1)); - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(-12, -12, 12, 12)); - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(-2, -1, 2, 0)); - assertRelation(GeoRelation.QUERY_DISJOINT, reader, Extent.fromPoints(-5, -6, 2, -2)); + TriangleTreeReader reader = triangleTreeReader(new Line(px, py), GeoShapeCoordinateEncoder.INSTANCE); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(2, -1, 11, 1)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(-12, -12, 12, 12)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(-2, -1, 2, 0)); + assertRelation(GeoRelation.QUERY_DISJOINT, reader, getExtentFromBox(-5, -6, 2, -2)); } public void testPacManLineString() throws Exception { @@ -172,11 +177,11 @@ public void testPacManLineString() throws Exception { double[] py = {0, 5, 9, 10, 9, 0, -9, -10, -9, -5}; // test cell crossing poly - TriangleTreeReader reader = triangleTreeReader(new Line(px, py), TestCoordinateEncoder.INSTANCE); - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(2, -1, 11, 1)); - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(-12, -12, 12, 12)); - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(-2, -1, 2, 0)); - assertRelation(GeoRelation.QUERY_DISJOINT, reader, Extent.fromPoints(-5, -6, 2, -2)); + TriangleTreeReader reader = triangleTreeReader(new Line(px, py), GeoShapeCoordinateEncoder.INSTANCE); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(2, -1, 11, 1)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(-12, -12, 12, 12)); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(-2, -1, 2, 0)); + assertRelation(GeoRelation.QUERY_DISJOINT, reader, getExtentFromBox(-5, -6, 2, -2)); } public void testPacManPoints() throws Exception { @@ -202,8 +207,8 @@ public void testPacManPoints() throws Exception { int yMax = 9; // test cell crossing poly - TriangleTreeReader reader = triangleTreeReader(new MultiPoint(points), TestCoordinateEncoder.INSTANCE); - assertRelation(GeoRelation.QUERY_CROSSES, reader, Extent.fromPoints(xMin, yMin, xMax, yMax)); + TriangleTreeReader reader = triangleTreeReader(new MultiPoint(points), GeoShapeCoordinateEncoder.INSTANCE); + assertRelation(GeoRelation.QUERY_CROSSES, reader, getExtentFromBox(xMin, yMin, xMax, yMax)); } public void testRandomMultiLineIntersections() throws IOException { @@ -271,6 +276,14 @@ private Extent bufferedExtentFromGeoPoint(double x, double y, double extentSize) return Extent.fromPoints(xMin, yMin, xMax, yMax); } + private static Extent getExtentFromBox(double bottomLeftX, double bottomLeftY, double topRightX, double topRightY) { + return Extent.fromPoints(GeoShapeCoordinateEncoder.INSTANCE.encodeX(bottomLeftX), + GeoShapeCoordinateEncoder.INSTANCE.encodeY(bottomLeftY), + GeoShapeCoordinateEncoder.INSTANCE.encodeX(topRightX), + GeoShapeCoordinateEncoder.INSTANCE.encodeY(topRightY)); + + } + private boolean intersects(Geometry g, Point p, double extentSize) throws IOException { Extent bufferBounds = bufferedExtentFromGeoPoint(p.getX(), p.getY(), extentSize); diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregatorTestCase.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregatorTestCase.java index 13b9ee51d8ae3..41655905e4b04 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregatorTestCase.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregatorTestCase.java @@ -29,6 +29,9 @@ import org.apache.lucene.search.Query; import org.apache.lucene.store.Directory; import org.elasticsearch.common.CheckedConsumer; +import org.elasticsearch.common.geo.CentroidCalculator; +import org.elasticsearch.common.geo.GeoTestUtils; +import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.MultiPoint; import org.elasticsearch.geometry.Point; import org.elasticsearch.index.mapper.BinaryGeoShapeDocValuesField; @@ -90,7 +93,9 @@ public void testFieldMissing() throws IOException { }, new GeoPointFieldMapper.GeoPointFieldType()); testCase(new MatchAllDocsQuery(), "wrong_field", randomPrecision(), iw -> { - iw.addDocument(Collections.singleton(new BinaryGeoShapeDocValuesField(FIELD_NAME, new Point(10D, 10D)))); + iw.addDocument(Collections.singleton( + new BinaryGeoShapeDocValuesField(FIELD_NAME, GeoTestUtils.toDecodedTriangles(new Point(10D, 10D)), + new CentroidCalculator(new Point(10D, 10D))))); }, geoGrid -> { assertEquals(0, geoGrid.getBuckets().size()); }, new GeoShapeFieldMapper.GeoShapeFieldType()); @@ -165,7 +170,9 @@ public void testGeoShapeWithSeveralDocs() throws IOException { } distinctHashesPerDoc.add(hash); if (usually()) { - document.add(new BinaryGeoShapeDocValuesField(FIELD_NAME, new MultiPoint(new ArrayList<>(shapes)))); + Geometry geometry = new MultiPoint(new ArrayList<>(shapes)); + document.add(new BinaryGeoShapeDocValuesField(FIELD_NAME, + GeoTestUtils.toDecodedTriangles(geometry), new CentroidCalculator(geometry))); iw.addDocument(document); shapes.clear(); distinctHashesPerDoc.clear(); @@ -173,7 +180,9 @@ public void testGeoShapeWithSeveralDocs() throws IOException { } } if (shapes.size() != 0) { - document.add(new BinaryGeoShapeDocValuesField(FIELD_NAME, new MultiPoint(new ArrayList<>(shapes)))); + Geometry geometry = new MultiPoint(new ArrayList<>(shapes)); + document.add(new BinaryGeoShapeDocValuesField(FIELD_NAME, + GeoTestUtils.toDecodedTriangles(geometry), new CentroidCalculator(geometry))); iw.addDocument(document); } }, geoHashGrid -> { diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoBoundsAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoBoundsAggregatorTests.java index 163f8c27bdda5..f5356efd50d82 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoBoundsAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoBoundsAggregatorTests.java @@ -27,13 +27,16 @@ import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.store.Directory; import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.geo.CentroidCalculator; import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.geo.GeoTestUtils; import org.elasticsearch.common.geo.GeometryParser; import org.elasticsearch.common.xcontent.DeprecationHandler; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.MultiPoint; import org.elasticsearch.geometry.MultiPolygon; import org.elasticsearch.geometry.Point; @@ -198,7 +201,9 @@ public void testRandomShapes() throws Exception { negRight = point.getLon(); } } - doc.add(new BinaryGeoShapeDocValuesField("field", new MultiPoint(points))); + Geometry geometry = new MultiPoint(points); + doc.add(new BinaryGeoShapeDocValuesField("field", GeoTestUtils.toDecodedTriangles(geometry), + new CentroidCalculator(geometry))); w.addDocument(doc); } GeoBoundsAggregationBuilder aggBuilder = new GeoBoundsAggregationBuilder("my_agg") @@ -337,7 +342,8 @@ public void testFiji() throws Exception { try (Directory dir = newDirectory(); RandomIndexWriter w = new RandomIndexWriter(random(), dir)) { Document doc = new Document(); - doc.add(new BinaryGeoShapeDocValuesField("fiji_shape", geometryForIndexing)); + doc.add(new BinaryGeoShapeDocValuesField("fiji_shape", + GeoTestUtils.toDecodedTriangles(geometryForIndexing), new CentroidCalculator(geometryForIndexing))); for (Polygon poly : fiji) { for (int i = 0; i < poly.getPolygon().length(); i++) { doc.add(new LatLonDocValuesField("fiji_points", poly.getPolygon().getLat(i), poly.getPolygon().getLon(i))); diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoBoundsIT.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoBoundsIT.java index aa98274115356..4c980d185fd23 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoBoundsIT.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoBoundsIT.java @@ -231,10 +231,10 @@ public void testSingleValuedFieldNearDateLineWrapLongitude() throws Exception { assertThat(bottomRight.lon(), closeTo(geoValuesBottomRight.lon(), GEOHASH_TOLERANCE)); } - // test geo_shape + // test geo_shape, should not wrap dateline { - GeoPoint geoValuesTopLeft = new GeoPoint(38, 178); - GeoPoint geoValuesBottomRight = new GeoPoint(-24, -179); + GeoPoint geoValuesTopLeft = new GeoPoint(38, -179); + GeoPoint geoValuesBottomRight = new GeoPoint(-24, 178); GeoBounds geoBounds = response.getAggregations().get(geoShapeAggName); assertThat(geoBounds, notNullValue()); assertThat(geoBounds.getName(), equalTo(geoShapeAggName)); diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidAggregatorTests.java index 83e3887e323f8..5b514f01ec42f 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidAggregatorTests.java @@ -27,19 +27,9 @@ import org.apache.lucene.store.Directory; import org.elasticsearch.common.geo.CentroidCalculator; import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.geo.GeoTestUtils; import org.elasticsearch.geo.GeometryTestUtils; -import org.elasticsearch.geometry.Circle; import org.elasticsearch.geometry.Geometry; -import org.elasticsearch.geometry.GeometryCollection; -import org.elasticsearch.geometry.GeometryVisitor; -import org.elasticsearch.geometry.Line; -import org.elasticsearch.geometry.LinearRing; -import org.elasticsearch.geometry.MultiLine; -import org.elasticsearch.geometry.MultiPoint; -import org.elasticsearch.geometry.MultiPolygon; -import org.elasticsearch.geometry.Point; -import org.elasticsearch.geometry.Polygon; -import org.elasticsearch.geometry.Rectangle; import org.elasticsearch.index.mapper.BinaryGeoShapeDocValuesField; import org.elasticsearch.index.mapper.GeoPointFieldMapper; import org.elasticsearch.index.mapper.GeoShapeFieldMapper; @@ -168,91 +158,19 @@ public void testGeoShapeField() throws Exception { try (Directory dir = newDirectory(); RandomIndexWriter w = new RandomIndexWriter(random(), dir)) { GeoPoint expectedCentroid = new GeoPoint(0, 0); - CentroidCalculator centroidOfCentroidsCalculator = new CentroidCalculator(); + CompensatedSum compensatedSumLon = new CompensatedSum(0, 0); + CompensatedSum compensatedSumLat = new CompensatedSum(0, 0); for (int i = 0; i < numDocs; i++) { - CentroidCalculator calculator = new CentroidCalculator(); + Document document = new Document(); Geometry geometry = geometryGenerator.apply(false); - geometry.visit(new GeometryVisitor() { - @Override - public Void visit(Circle circle) throws Exception { - calculator.addCoordinate(circle.getX(), circle.getY()); - return null; - } - - @Override - public Void visit(GeometryCollection collection) throws Exception { - for (Geometry shape : collection) { - shape.visit(this); - } - return null; - } - - @Override - public Void visit(Line line) throws Exception { - for (int i = 0; i < line.length(); i++) { - calculator.addCoordinate(line.getX(i), line.getY(i)); - } - return null; - } - - @Override - public Void visit(LinearRing ring) throws Exception { - for (int i = 0; i < ring.length() - 1; i++) { - calculator.addCoordinate(ring.getX(i), ring.getY(i)); - } - return null; - } - - @Override - public Void visit(MultiLine multiLine) throws Exception { - for (Line line : multiLine) { - visit(line); - } - return null; - } - - @Override - public Void visit(MultiPoint multiPoint) throws Exception { - for (Point point : multiPoint) { - visit(point); - } - return null; - } - - @Override - public Void visit(MultiPolygon multiPolygon) throws Exception { - for (Polygon polygon : multiPolygon) { - visit(polygon); - } - return null; - } - - @Override - public Void visit(Point point) throws Exception { - calculator.addCoordinate(point.getX(), point.getY()); - return null; - } - - @Override - public Void visit(Polygon polygon) throws Exception { - return visit(polygon.getPolygon()); - } - - @Override - public Void visit(Rectangle rectangle) throws Exception { - calculator.addCoordinate(rectangle.getMinX(), rectangle.getMinY()); - calculator.addCoordinate(rectangle.getMinX(), rectangle.getMaxY()); - calculator.addCoordinate(rectangle.getMaxX(), rectangle.getMinY()); - calculator.addCoordinate(rectangle.getMaxX(), rectangle.getMaxY()); - return null; - } - }); - document.add(new BinaryGeoShapeDocValuesField("field", geometry)); + CentroidCalculator calculator = new CentroidCalculator(geometry); + document.add(new BinaryGeoShapeDocValuesField("field", GeoTestUtils.toDecodedTriangles(geometry), calculator)); w.addDocument(document); - centroidOfCentroidsCalculator.addCoordinate(calculator.getX(), calculator.getY()); + compensatedSumLat.add(calculator.getY()); + compensatedSumLon.add(calculator.getX()); } - expectedCentroid.reset(centroidOfCentroidsCalculator.getY(), centroidOfCentroidsCalculator.getX()); + expectedCentroid.reset(compensatedSumLat.value() / numDocs, compensatedSumLon.value() / numDocs); assertCentroid(w, expectedCentroid, new GeoShapeFieldMapper.GeoShapeFieldType()); } } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeIndexer.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeIndexer.java index 43f52bfd83f52..5e0e47cb7a5d5 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeIndexer.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeIndexer.java @@ -5,10 +5,12 @@ */ package org.elasticsearch.xpack.spatial.index.mapper; +import org.apache.lucene.document.ShapeField; import org.apache.lucene.document.XYShape; import org.apache.lucene.geo.XYLine; import org.apache.lucene.geo.XYPolygon; import org.apache.lucene.index.IndexableField; +import org.elasticsearch.common.geo.CentroidCalculator; import org.elasticsearch.geometry.Circle; import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.GeometryCollection; @@ -54,13 +56,14 @@ public List indexShape(ParseContext context, Geometry shape) { } @Override - public void indexDocValueField(ParseContext context, Geometry shape) { + public void indexDocValueField(ParseContext context, ShapeField.DecodedTriangle[] triangles, + CentroidCalculator centroidCalculator) { BinaryGeoShapeDocValuesField docValuesField = (BinaryGeoShapeDocValuesField) context.doc().getByKey(name); if (docValuesField == null) { - docValuesField = new BinaryGeoShapeDocValuesField(name, shape); + docValuesField = new BinaryGeoShapeDocValuesField(name, triangles, centroidCalculator); context.doc().addWithKey(name, docValuesField); } else { - docValuesField.add(shape); + docValuesField.add(triangles, centroidCalculator); } }