/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * 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. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.common.geo; import org.apache.lucene.geo.GeoEncodingUtils; import org.opensearch.OpenSearchParseException; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.geo.GeometryTestUtils; import org.opensearch.geometry.Rectangle; import org.opensearch.test.OpenSearchTestCase; import java.io.IOException; import java.util.Arrays; import static org.hamcrest.Matchers.equalTo; /** * Tests for {@link GeoBoundingBox} */ public class GeoBoundingBoxTests extends OpenSearchTestCase { public void testInvalidParseInvalidWKT() throws IOException { XContentBuilder bboxBuilder = XContentFactory.jsonBuilder().startObject().field("wkt", "invalid").endObject(); XContentParser parser = createParser(bboxBuilder); parser.nextToken(); OpenSearchParseException e = expectThrows(OpenSearchParseException.class, () -> GeoBoundingBox.parseBoundingBox(parser)); assertThat(e.getMessage(), equalTo("failed to parse WKT bounding box")); } public void testInvalidParsePoint() throws IOException { XContentBuilder bboxBuilder = XContentFactory.jsonBuilder().startObject().field("wkt", "POINT (100.0 100.0)").endObject(); XContentParser parser = createParser(bboxBuilder); parser.nextToken(); OpenSearchParseException e = expectThrows(OpenSearchParseException.class, () -> GeoBoundingBox.parseBoundingBox(parser)); assertThat(e.getMessage(), equalTo("failed to parse WKT bounding box. [POINT] found. expected [ENVELOPE]")); } public void testWKT() throws IOException { GeoBoundingBox geoBoundingBox = randomBBox(); assertBBox(geoBoundingBox, XContentFactory.jsonBuilder().startObject().field("wkt", geoBoundingBox.toString()).endObject()); } public void testTopBottomLeftRight() throws Exception { GeoBoundingBox geoBoundingBox = randomBBox(); assertBBox( geoBoundingBox, XContentFactory.jsonBuilder() .startObject() .field("top", geoBoundingBox.top()) .field("bottom", geoBoundingBox.bottom()) .field("left", geoBoundingBox.left()) .field("right", geoBoundingBox.right()) .endObject() ); } public void testTopLeftBottomRight() throws Exception { GeoBoundingBox geoBoundingBox = randomBBox(); assertBBox( geoBoundingBox, XContentFactory.jsonBuilder() .startObject() .field("top_left", geoBoundingBox.topLeft()) .field("bottom_right", geoBoundingBox.bottomRight()) .endObject() ); } public void testTopRightBottomLeft() throws Exception { GeoBoundingBox geoBoundingBox = randomBBox(); assertBBox( geoBoundingBox, XContentFactory.jsonBuilder() .startObject() .field("top_right", new GeoPoint(geoBoundingBox.top(), geoBoundingBox.right())) .field("bottom_left", new GeoPoint(geoBoundingBox.bottom(), geoBoundingBox.left())) .endObject() ); } // test that no exception is thrown. BBOX parsing is not validated public void testNullTopBottomLeftRight() throws Exception { GeoBoundingBox geoBoundingBox = randomBBox(); XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); for (String field : randomSubsetOf(Arrays.asList("top", "bottom", "left", "right"))) { switch (field) { case "top": builder.field("top", geoBoundingBox.top()); break; case "bottom": builder.field("bottom", geoBoundingBox.bottom()); break; case "left": builder.field("left", geoBoundingBox.left()); break; case "right": builder.field("right", geoBoundingBox.right()); break; default: throw new IllegalStateException("unexpected branching"); } } builder.endObject(); try (XContentParser parser = createParser(builder)) { parser.nextToken(); GeoBoundingBox.parseBoundingBox(parser); } } public void testPointInBounds() { for (int iter = 0; iter < 1000; iter++) { GeoBoundingBox geoBoundingBox = randomBBox(); GeoBoundingBox bbox = new GeoBoundingBox( new GeoPoint(quantizeLat(geoBoundingBox.top()), quantizeLon(geoBoundingBox.left())), new GeoPoint(quantizeLat(geoBoundingBox.bottom()), quantizeLon(geoBoundingBox.right())) ); if (bbox.left() > bbox.right()) { double lonWithin = randomBoolean() ? randomDoubleBetween(bbox.left(), 180.0, true) : randomDoubleBetween(-180.0, bbox.right(), true); double latWithin = randomDoubleBetween(bbox.bottom(), bbox.top(), true); double lonOutside = randomDoubleBetween(bbox.left(), bbox.right(), true); double latOutside = randomBoolean() ? randomDoubleBetween(Math.max(bbox.top(), bbox.bottom()), 90, false) : randomDoubleBetween(-90, Math.min(bbox.bottom(), bbox.top()), false); assertTrue(bbox.pointInBounds(lonWithin, latWithin)); assertFalse(bbox.pointInBounds(lonOutside, latOutside)); } else { double lonWithin = randomDoubleBetween(bbox.left(), bbox.right(), true); double latWithin = randomDoubleBetween(bbox.bottom(), bbox.top(), true); double lonOutside = GeoUtils.normalizeLon(randomDoubleBetween(bbox.right(), 180, false)); double latOutside = GeoUtils.normalizeLat(randomDoubleBetween(bbox.top(), 90, false)); assertTrue(bbox.pointInBounds(lonWithin, latWithin)); assertFalse(bbox.pointInBounds(lonOutside, latOutside)); } } } private void assertBBox(GeoBoundingBox expected, XContentBuilder builder) throws IOException { try (XContentParser parser = createParser(builder)) { parser.nextToken(); assertThat(GeoBoundingBox.parseBoundingBox(parser), equalTo(expected)); } } public static GeoBoundingBox randomBBox() { Rectangle rectangle = GeometryTestUtils.randomRectangle(); return new GeoBoundingBox( new GeoPoint(rectangle.getMaxLat(), rectangle.getMinLon()), new GeoPoint(rectangle.getMinLat(), rectangle.getMaxLon()) ); } private static double quantizeLat(double lat) { return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(lat)); } private static double quantizeLon(double lon) { return GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lon)); } }