Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESQL: Support ST_EXTENT_AGG (#117451) #118829

Merged
merged 1 commit into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/117451.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 117451
summary: ST_EXTENT aggregation
area: ES|QL
type: feature
issues:
- 104659
2 changes: 2 additions & 0 deletions docs/reference/esql/functions/aggregation-functions.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The <<esql-stats-by>> command supports these aggregate functions:
* <<esql-min>>
* <<esql-percentile>>
* experimental:[] <<esql-st_centroid_agg>>
* experimental:[] <<esql-st_extent_agg>>
* <<esql-std_dev>>
* <<esql-sum>>
* <<esql-top>>
Expand All @@ -33,6 +34,7 @@ include::layout/median_absolute_deviation.asciidoc[]
include::layout/min.asciidoc[]
include::layout/percentile.asciidoc[]
include::layout/st_centroid_agg.asciidoc[]
include::layout/st_extent_agg.asciidoc[]
include::layout/std_dev.asciidoc[]
include::layout/sum.asciidoc[]
include::layout/top.asciidoc[]
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions docs/reference/esql/functions/examples/st_extent_agg.asciidoc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 61 additions & 0 deletions docs/reference/esql/functions/kibana/definition/st_extent_agg.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions docs/reference/esql/functions/kibana/docs/st_extent_agg.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions docs/reference/esql/functions/layout/st_extent_agg.asciidoc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/reference/esql/functions/signature/st_extent_agg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions docs/reference/esql/functions/types/st_extent_agg.asciidoc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,15 @@ public static Optional<Rectangle> visitCartesian(Geometry geometry) {
return Optional.empty();
}

public enum WrapLongitude {
NO_WRAP,
WRAP
}

/**
* Determine the BBOX assuming the CRS is geographic (eg WGS84) and optionally wrapping the longitude around the dateline.
*/
public static Optional<Rectangle> visitGeo(Geometry geometry, boolean wrapLongitude) {
public static Optional<Rectangle> visitGeo(Geometry geometry, WrapLongitude wrapLongitude) {
var visitor = new SpatialEnvelopeVisitor(new GeoPointVisitor(wrapLongitude));
if (geometry.visit(visitor)) {
return Optional.of(visitor.getResult());
Expand Down Expand Up @@ -181,40 +186,16 @@ public Rectangle getResult() {
* </ul>
*/
public static class GeoPointVisitor implements PointVisitor {
private double minY = Double.POSITIVE_INFINITY;
private double maxY = Double.NEGATIVE_INFINITY;
private double minNegX = Double.POSITIVE_INFINITY;
private double maxNegX = Double.NEGATIVE_INFINITY;
private double minPosX = Double.POSITIVE_INFINITY;
private double maxPosX = Double.NEGATIVE_INFINITY;

public double getMinY() {
return minY;
}

public double getMaxY() {
return maxY;
}

public double getMinNegX() {
return minNegX;
}
protected double minY = Double.POSITIVE_INFINITY;
protected double maxY = Double.NEGATIVE_INFINITY;
protected double minNegX = Double.POSITIVE_INFINITY;
protected double maxNegX = Double.NEGATIVE_INFINITY;
protected double minPosX = Double.POSITIVE_INFINITY;
protected double maxPosX = Double.NEGATIVE_INFINITY;

public double getMaxNegX() {
return maxNegX;
}

public double getMinPosX() {
return minPosX;
}
private final WrapLongitude wrapLongitude;

public double getMaxPosX() {
return maxPosX;
}

private final boolean wrapLongitude;

public GeoPointVisitor(boolean wrapLongitude) {
public GeoPointVisitor(WrapLongitude wrapLongitude) {
this.wrapLongitude = wrapLongitude;
}

Expand Down Expand Up @@ -253,32 +234,35 @@ public Rectangle getResult() {
return getResult(minNegX, minPosX, maxNegX, maxPosX, maxY, minY, wrapLongitude);
}

private static Rectangle getResult(
protected static Rectangle getResult(
double minNegX,
double minPosX,
double maxNegX,
double maxPosX,
double maxY,
double minY,
boolean wrapLongitude
WrapLongitude wrapLongitude
) {
assert Double.isFinite(maxY);
if (Double.isInfinite(minPosX)) {
return new Rectangle(minNegX, maxNegX, maxY, minY);
} else if (Double.isInfinite(minNegX)) {
return new Rectangle(minPosX, maxPosX, maxY, minY);
} else if (wrapLongitude) {
double unwrappedWidth = maxPosX - minNegX;
double wrappedWidth = (180 - minPosX) - (-180 - maxNegX);
if (unwrappedWidth <= wrappedWidth) {
return new Rectangle(minNegX, maxPosX, maxY, minY);
} else {
return new Rectangle(minPosX, maxNegX, maxY, minY);
}
} else {
return new Rectangle(minNegX, maxPosX, maxY, minY);
return switch (wrapLongitude) {
case NO_WRAP -> new Rectangle(minNegX, maxPosX, maxY, minY);
case WRAP -> maybeWrap(minNegX, minPosX, maxNegX, maxPosX, maxY, minY);
};
}
}

private static Rectangle maybeWrap(double minNegX, double minPosX, double maxNegX, double maxPosX, double maxY, double minY) {
double unwrappedWidth = maxPosX - minNegX;
double wrappedWidth = 360 + maxNegX - minPosX;
return unwrappedWidth <= wrappedWidth
? new Rectangle(minNegX, maxPosX, maxY, minY)
: new Rectangle(minPosX, maxNegX, maxY, minY);
}
}

private boolean isValid() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.elasticsearch.geo.ShapeTestUtils;
import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor.WrapLongitude;
import org.elasticsearch.test.ESTestCase;

import static org.hamcrest.Matchers.equalTo;
Expand All @@ -36,7 +37,7 @@ public void testVisitCartesianShape() {
public void testVisitGeoShapeNoWrap() {
for (int i = 0; i < 1000; i++) {
var geometry = GeometryTestUtils.randomGeometryWithoutCircle(0, false);
var bbox = SpatialEnvelopeVisitor.visitGeo(geometry, false);
var bbox = SpatialEnvelopeVisitor.visitGeo(geometry, WrapLongitude.NO_WRAP);
assertNotNull(bbox);
assertTrue(i + ": " + geometry, bbox.isPresent());
var result = bbox.get();
Expand All @@ -48,7 +49,8 @@ public void testVisitGeoShapeNoWrap() {
public void testVisitGeoShapeWrap() {
for (int i = 0; i < 1000; i++) {
var geometry = GeometryTestUtils.randomGeometryWithoutCircle(0, true);
var bbox = SpatialEnvelopeVisitor.visitGeo(geometry, false);
// TODO this should be WRAP instead
var bbox = SpatialEnvelopeVisitor.visitGeo(geometry, WrapLongitude.NO_WRAP);
assertNotNull(bbox);
assertTrue(i + ": " + geometry, bbox.isPresent());
var result = bbox.get();
Expand Down Expand Up @@ -81,7 +83,7 @@ public void testVisitCartesianPoints() {
}

public void testVisitGeoPointsNoWrapping() {
var visitor = new SpatialEnvelopeVisitor(new SpatialEnvelopeVisitor.GeoPointVisitor(false));
var visitor = new SpatialEnvelopeVisitor(new SpatialEnvelopeVisitor.GeoPointVisitor(WrapLongitude.NO_WRAP));
double minY = Double.MAX_VALUE;
double maxY = -Double.MAX_VALUE;
double minX = Double.MAX_VALUE;
Expand All @@ -103,7 +105,7 @@ public void testVisitGeoPointsNoWrapping() {
}

public void testVisitGeoPointsWrapping() {
var visitor = new SpatialEnvelopeVisitor(new SpatialEnvelopeVisitor.GeoPointVisitor(true));
var visitor = new SpatialEnvelopeVisitor(new SpatialEnvelopeVisitor.GeoPointVisitor(WrapLongitude.WRAP));
double minY = Double.POSITIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
double minNegX = Double.POSITIVE_INFINITY;
Expand Down Expand Up @@ -145,7 +147,7 @@ public void testVisitGeoPointsWrapping() {
}

public void testWillCrossDateline() {
var visitor = new SpatialEnvelopeVisitor(new SpatialEnvelopeVisitor.GeoPointVisitor(true));
var visitor = new SpatialEnvelopeVisitor(new SpatialEnvelopeVisitor.GeoPointVisitor(WrapLongitude.WRAP));
visitor.visit(new Point(-90.0, 0.0));
visitor.visit(new Point(90.0, 0.0));
assertCrossesDateline(visitor, false);
Expand Down
Loading