/* * 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.geo; import org.apache.lucene.tests.geo.GeoTestUtil; import org.opensearch.geometry.Circle; import org.opensearch.geometry.Geometry; import org.opensearch.geometry.GeometryCollection; import org.opensearch.geometry.GeometryVisitor; import org.opensearch.geometry.Line; import org.opensearch.geometry.LinearRing; import org.opensearch.geometry.MultiLine; import org.opensearch.geometry.MultiPoint; import org.opensearch.geometry.MultiPolygon; import org.opensearch.geometry.Point; import org.opensearch.geometry.Polygon; import org.opensearch.geometry.Rectangle; import org.opensearch.test.OpenSearchTestCase; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Function; import static org.opensearch.test.OpenSearchTestCase.randomValueOtherThanMany; public class GeometryTestUtils { public static double randomLat() { return GeoTestUtil.nextLatitude(); } public static double randomLon() { return GeoTestUtil.nextLongitude(); } public static double randomAlt() { return OpenSearchTestCase.randomDouble(); } public static Circle randomCircle(boolean hasAlt) { if (hasAlt) { return new Circle( randomLon(), randomLat(), OpenSearchTestCase.randomDouble(), OpenSearchTestCase.randomDoubleBetween(0, 100, false) ); } else { return new Circle(randomLon(), randomLat(), OpenSearchTestCase.randomDoubleBetween(0, 100, false)); } } public static Line randomLine(boolean hasAlts) { // we use nextPolygon because it guarantees no duplicate points org.apache.lucene.geo.Polygon lucenePolygon = GeoTestUtil.nextPolygon(); int size = lucenePolygon.numPoints() - 1; double[] lats = new double[size]; double[] lons = new double[size]; double[] alts = hasAlts ? new double[size] : null; for (int i = 0; i < size; i++) { lats[i] = lucenePolygon.getPolyLat(i); lons[i] = lucenePolygon.getPolyLon(i); if (hasAlts) { alts[i] = randomAlt(); } } if (hasAlts) { return new Line(lons, lats, alts); } return new Line(lons, lats); } public static Point randomPoint() { return randomPoint(OpenSearchTestCase.randomBoolean()); } public static Point randomPoint(boolean hasAlt) { if (hasAlt) { return new Point(randomLon(), randomLat(), randomAlt()); } else { return new Point(randomLon(), randomLat()); } } public static Polygon randomPolygon(boolean hasAlt) { org.apache.lucene.geo.Polygon lucenePolygon = randomValueOtherThanMany(p -> area(p) == 0, GeoTestUtil::nextPolygon); if (lucenePolygon.numHoles() > 0) { org.apache.lucene.geo.Polygon[] luceneHoles = lucenePolygon.getHoles(); List holes = new ArrayList<>(); for (int i = 0; i < lucenePolygon.numHoles(); i++) { org.apache.lucene.geo.Polygon poly = luceneHoles[i]; holes.add(linearRing(poly.getPolyLons(), poly.getPolyLats(), hasAlt)); } return new Polygon(linearRing(lucenePolygon.getPolyLons(), lucenePolygon.getPolyLats(), hasAlt), holes); } return new Polygon(linearRing(lucenePolygon.getPolyLons(), lucenePolygon.getPolyLats(), hasAlt)); } private static double area(org.apache.lucene.geo.Polygon lucenePolygon) { double windingSum = 0; final int numPts = lucenePolygon.numPoints() - 1; for (int i = 0; i < numPts; i++) { // compute signed area windingSum += lucenePolygon.getPolyLon(i) * lucenePolygon.getPolyLat(i + 1) - lucenePolygon.getPolyLat(i) * lucenePolygon .getPolyLon(i + 1); } return Math.abs(windingSum / 2); } private static double[] randomAltRing(int size) { double[] alts = new double[size]; for (int i = 0; i < size - 1; i++) { alts[i] = randomAlt(); } alts[size - 1] = alts[0]; return alts; } public static LinearRing linearRing(double[] lons, double[] lats, boolean generateAlts) { if (generateAlts) { return new LinearRing(lons, lats, randomAltRing(lats.length)); } return new LinearRing(lons, lats); } public static Rectangle randomRectangle() { org.apache.lucene.geo.Rectangle rectangle = GeoTestUtil.nextBox(); return new Rectangle(rectangle.minLon, rectangle.maxLon, rectangle.maxLat, rectangle.minLat); } public static MultiPoint randomMultiPoint(boolean hasAlt) { int size = OpenSearchTestCase.randomIntBetween(3, 10); List points = new ArrayList<>(); for (int i = 0; i < size; i++) { points.add(randomPoint(hasAlt)); } return new MultiPoint(points); } public static MultiLine randomMultiLine(boolean hasAlt) { int size = OpenSearchTestCase.randomIntBetween(3, 10); List lines = new ArrayList<>(); for (int i = 0; i < size; i++) { lines.add(randomLine(hasAlt)); } return new MultiLine(lines); } public static MultiPolygon randomMultiPolygon(boolean hasAlt) { int size = OpenSearchTestCase.randomIntBetween(3, 10); List polygons = new ArrayList<>(); for (int i = 0; i < size; i++) { polygons.add(randomPolygon(hasAlt)); } return new MultiPolygon(polygons); } public static GeometryCollection randomGeometryCollection(boolean hasAlt) { return randomGeometryCollection(0, hasAlt); } private static GeometryCollection randomGeometryCollection(int level, boolean hasAlt) { int size = OpenSearchTestCase.randomIntBetween(1, 10); List shapes = new ArrayList<>(); for (int i = 0; i < size; i++) { shapes.add(randomGeometry(level, hasAlt)); } return new GeometryCollection<>(shapes); } public static Geometry randomGeometry(boolean hasAlt) { return randomGeometry(0, hasAlt); } protected static Geometry randomGeometry(int level, boolean hasAlt) { @SuppressWarnings("unchecked") Function geometry = OpenSearchTestCase.randomFrom( GeometryTestUtils::randomCircle, GeometryTestUtils::randomLine, GeometryTestUtils::randomPoint, GeometryTestUtils::randomPolygon, GeometryTestUtils::randomMultiLine, GeometryTestUtils::randomMultiPoint, GeometryTestUtils::randomMultiPolygon, hasAlt ? GeometryTestUtils::randomPoint : (b) -> randomRectangle(), level < 3 ? (b) -> randomGeometryCollection(level + 1, b) : GeometryTestUtils::randomPoint // don't build too deep ); return geometry.apply(hasAlt); } /** * Extracts all vertices of the supplied geometry */ public static MultiPoint toMultiPoint(Geometry geometry) { return geometry.visit(new GeometryVisitor() { @Override public MultiPoint visit(Circle circle) throws RuntimeException { throw new UnsupportedOperationException("not supporting circles yet"); } @Override public MultiPoint visit(GeometryCollection collection) throws RuntimeException { List points = new ArrayList<>(); collection.forEach(geometry -> toMultiPoint(geometry).forEach(points::add)); return new MultiPoint(points); } @Override public MultiPoint visit(Line line) throws RuntimeException { List points = new ArrayList<>(); for (int i = 0; i < line.length(); i++) { points.add(new Point(line.getX(i), line.getY(i), line.getZ(i))); } return new MultiPoint(points); } @Override public MultiPoint visit(LinearRing ring) throws RuntimeException { return visit((Line) ring); } @Override public MultiPoint visit(MultiLine multiLine) throws RuntimeException { return visit((GeometryCollection) multiLine); } @Override public MultiPoint visit(MultiPoint multiPoint) throws RuntimeException { return multiPoint; } @Override public MultiPoint visit(MultiPolygon multiPolygon) throws RuntimeException { return visit((GeometryCollection) multiPolygon); } @Override public MultiPoint visit(Point point) throws RuntimeException { return new MultiPoint(Collections.singletonList(point)); } @Override public MultiPoint visit(Polygon polygon) throws RuntimeException { List multiPoints = new ArrayList<>(); multiPoints.add(toMultiPoint(polygon.getPolygon())); for (int i = 0; i < polygon.getNumberOfHoles(); i++) { multiPoints.add(toMultiPoint(polygon.getHole(i))); } return toMultiPoint(new GeometryCollection<>(multiPoints)); } @Override public MultiPoint visit(Rectangle rectangle) throws RuntimeException { return new MultiPoint( Arrays.asList( new Point(rectangle.getMinX(), rectangle.getMinY(), rectangle.getMinZ()), new Point(rectangle.getMaxX(), rectangle.getMaxY(), rectangle.getMaxZ()) ) ); } }); } }