diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/QueryDSLDocumentationTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/QueryDSLDocumentationTests.java index da294de39286..98076fe5babc 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/QueryDSLDocumentationTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/QueryDSLDocumentationTests.java @@ -11,9 +11,9 @@ package org.elasticsearch.client.documentation; import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.ShapeRelation; -import org.elasticsearch.common.geo.builders.CoordinatesBuilder; -import org.elasticsearch.common.geo.builders.MultiPointBuilder; import org.elasticsearch.common.unit.DistanceUnit; +import org.elasticsearch.geometry.MultiPoint; +import org.elasticsearch.geometry.Point; import org.elasticsearch.index.query.GeoShapeQueryBuilder; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder; @@ -178,16 +178,16 @@ public class QueryDSLDocumentationTests extends ESTestCase { public void testGeoShape() throws IOException { { // tag::geo_shape + List points = new ArrayList<>(); + points.add(new Point(0, 0)); + points.add(new Point(0, 10)); + points.add(new Point(10, 10)); + points.add(new Point(10, 0)); + points.add(new Point(0, 0)); GeoShapeQueryBuilder qb = geoShapeQuery( - "pin.location", // <1> - new MultiPointBuilder( // <2> - new CoordinatesBuilder() - .coordinate(0, 0) - .coordinate(0, 10) - .coordinate(10, 10) - .coordinate(10, 0) - .coordinate(0, 0) - .build())); + "pin.location", // <1> + new MultiPoint(points) // <2> + ); qb.relation(ShapeRelation.WITHIN); // <3> // end::geo_shape } diff --git a/modules/legacy-geo/build.gradle b/modules/legacy-geo/build.gradle new file mode 100644 index 000000000000..8d45856cabce --- /dev/null +++ b/modules/legacy-geo/build.gradle @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +apply plugin: 'elasticsearch.internal-cluster-test' +apply plugin: 'nebula.optional-base' + +esplugin { + description 'Placeholder plugin for geospatial features in ES' + classname 'org.elasticsearch.legacygeo.LegacyGeoPlugin' +} + +dependencies { + api "org.apache.lucene:lucene-spatial-extras:${versions.lucene}" + api "org.locationtech.spatial4j:spatial4j:${versions.spatial4j}", optional + api "org.locationtech.jts:jts-core:${versions.jts}", optional + testImplementation project(":test:framework") +} + +tasks.named("thirdPartyAudit").configure { + ignoreMissingClasses( + + // from org.locationtech.spatial4j.io.GeoJSONReader (spatial4j) + 'org.noggit.JSONParser', + + // from org.locationtech.spatial4j.io.jackson.ShapeAsGeoJSONSerialize + 'com.fasterxml.jackson.databind.JsonSerializer', + 'com.fasterxml.jackson.databind.JsonDeserializer', + 'com.fasterxml.jackson.databind.node.ArrayNode', + 'com.fasterxml.jackson.databind.DeserializationContext', + 'com.fasterxml.jackson.databind.JsonNode', + 'com.fasterxml.jackson.databind.SerializerProvider', + 'com.fasterxml.jackson.databind.module.SimpleModule', + 'com.fasterxml.jackson.databind.node.ObjectNode', + + // from lucene-spatial + 'com.google.common.geometry.S2Cell', + 'com.google.common.geometry.S2CellId', + 'com.google.common.geometry.S2Projections', + 'com.google.common.geometry.S2Point', + 'com.google.common.geometry.S2$Metric', + 'com.google.common.geometry.S2LatLng' + ) +} + +tasks.named("dependencyLicenses").configure { + mapping from: /lucene-.*/, to: 'lucene' +} + diff --git a/server/licenses/jts-core-1.15.0.jar.sha1 b/modules/legacy-geo/licenses/jts-core-1.15.0.jar.sha1 similarity index 100% rename from server/licenses/jts-core-1.15.0.jar.sha1 rename to modules/legacy-geo/licenses/jts-core-1.15.0.jar.sha1 diff --git a/server/licenses/jts-core-LICENSE.txt b/modules/legacy-geo/licenses/jts-core-LICENSE.txt similarity index 100% rename from server/licenses/jts-core-LICENSE.txt rename to modules/legacy-geo/licenses/jts-core-LICENSE.txt diff --git a/server/licenses/jts-core-NOTICE.txt b/modules/legacy-geo/licenses/jts-core-NOTICE.txt similarity index 100% rename from server/licenses/jts-core-NOTICE.txt rename to modules/legacy-geo/licenses/jts-core-NOTICE.txt diff --git a/modules/legacy-geo/licenses/lucene-LICENSE.txt b/modules/legacy-geo/licenses/lucene-LICENSE.txt new file mode 100644 index 000000000000..28b134f5f8e4 --- /dev/null +++ b/modules/legacy-geo/licenses/lucene-LICENSE.txt @@ -0,0 +1,475 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + + + +Some code in core/src/java/org/apache/lucene/util/UnicodeUtil.java was +derived from unicode conversion examples available at +http://www.unicode.org/Public/PROGRAMS/CVTUTF. Here is the copyright +from those sources: + +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + + +Some code in core/src/java/org/apache/lucene/util/ArrayUtil.java was +derived from Python 2.4.2 sources available at +http://www.python.org. Full license is here: + + http://www.python.org/download/releases/2.4.2/license/ + +Some code in core/src/java/org/apache/lucene/util/UnicodeUtil.java was +derived from Python 3.1.2 sources available at +http://www.python.org. Full license is here: + + http://www.python.org/download/releases/3.1.2/license/ + +Some code in core/src/java/org/apache/lucene/util/automaton was +derived from Brics automaton sources available at +www.brics.dk/automaton/. Here is the copyright from those sources: + +/* + * Copyright (c) 2001-2009 Anders Moeller + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +The levenshtein automata tables in core/src/java/org/apache/lucene/util/automaton +were automatically generated with the moman/finenight FSA package. +Here is the copyright for those sources: + +# Copyright (c) 2010, Jean-Philippe Barrette-LaPierre, +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +Some code in core/src/java/org/apache/lucene/util/UnicodeUtil.java was +derived from ICU (http://www.icu-project.org) +The full license is available here: + http://source.icu-project.org/repos/icu/icu/trunk/license.html + +/* + * Copyright (C) 1999-2010, International Business Machines + * Corporation and others. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * provided that the above copyright notice(s) and this permission notice appear + * in all copies of the Software and that both the above copyright notice(s) and + * this permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE + * LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR + * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall not + * be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization of the + * copyright holder. + */ + +The following license applies to the Snowball stemmers: + +Copyright (c) 2001, Dr Martin Porter +Copyright (c) 2002, Richard Boulton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The following license applies to the KStemmer: + +Copyright © 2003, +Center for Intelligent Information Retrieval, +University of Massachusetts, Amherst. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. The names "Center for Intelligent Information Retrieval" and +"University of Massachusetts" must not be used to endorse or promote products +derived from this software without prior written permission. To obtain +permission, contact info@ciir.cs.umass.edu. + +THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF MASSACHUSETTS AND OTHER CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +The following license applies to the Morfologik project: + +Copyright (c) 2006 Dawid Weiss +Copyright (c) 2007-2011 Dawid Weiss, Marcin Miłkowski +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Morfologik nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +The dictionary comes from Morfologik project. Morfologik uses data from +Polish ispell/myspell dictionary hosted at http://www.sjp.pl/slownik/en/ and +is licenced on the terms of (inter alia) LGPL and Creative Commons +ShareAlike. The part-of-speech tags were added in Morfologik project and +are not found in the data from sjp.pl. The tagset is similar to IPI PAN +tagset. + +--- + +The following license applies to the Morfeusz project, +used by org.apache.lucene.analysis.morfologik. + +BSD-licensed dictionary of Polish (SGJP) +http://sgjp.pl/morfeusz/ + +Copyright © 2011 Zygmunt Saloni, Włodzimierz Gruszczyński, + Marcin Woliński, Robert Wołosz + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS “AS IS” AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/modules/legacy-geo/licenses/lucene-NOTICE.txt b/modules/legacy-geo/licenses/lucene-NOTICE.txt new file mode 100644 index 000000000000..1a1d51572432 --- /dev/null +++ b/modules/legacy-geo/licenses/lucene-NOTICE.txt @@ -0,0 +1,192 @@ +Apache Lucene +Copyright 2014 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +Includes software from other Apache Software Foundation projects, +including, but not limited to: + - Apache Ant + - Apache Jakarta Regexp + - Apache Commons + - Apache Xerces + +ICU4J, (under analysis/icu) is licensed under an MIT styles license +and Copyright (c) 1995-2008 International Business Machines Corporation and others + +Some data files (under analysis/icu/src/data) are derived from Unicode data such +as the Unicode Character Database. See http://unicode.org/copyright.html for more +details. + +Brics Automaton (under core/src/java/org/apache/lucene/util/automaton) is +BSD-licensed, created by Anders Møller. See http://www.brics.dk/automaton/ + +The levenshtein automata tables (under core/src/java/org/apache/lucene/util/automaton) were +automatically generated with the moman/finenight FSA library, created by +Jean-Philippe Barrette-LaPierre. This library is available under an MIT license, +see http://sites.google.com/site/rrettesite/moman and +http://bitbucket.org/jpbarrette/moman/overview/ + +The class org.apache.lucene.util.WeakIdentityMap was derived from +the Apache CXF project and is Apache License 2.0. + +The Google Code Prettify is Apache License 2.0. +See http://code.google.com/p/google-code-prettify/ + +JUnit (junit-4.10) is licensed under the Common Public License v. 1.0 +See http://junit.sourceforge.net/cpl-v10.html + +This product includes code (JaspellTernarySearchTrie) from Java Spelling Checkin +g Package (jaspell): http://jaspell.sourceforge.net/ +License: The BSD License (http://www.opensource.org/licenses/bsd-license.php) + +The snowball stemmers in + analysis/common/src/java/net/sf/snowball +were developed by Martin Porter and Richard Boulton. +The snowball stopword lists in + analysis/common/src/resources/org/apache/lucene/analysis/snowball +were developed by Martin Porter and Richard Boulton. +The full snowball package is available from + http://snowball.tartarus.org/ + +The KStem stemmer in + analysis/common/src/org/apache/lucene/analysis/en +was developed by Bob Krovetz and Sergio Guzman-Lara (CIIR-UMass Amherst) +under the BSD-license. + +The Arabic,Persian,Romanian,Bulgarian, Hindi and Bengali analyzers (common) come with a default +stopword list that is BSD-licensed created by Jacques Savoy. These files reside in: +analysis/common/src/resources/org/apache/lucene/analysis/ar/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/fa/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/ro/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/bg/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/hi/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/bn/stopwords.txt +See http://members.unine.ch/jacques.savoy/clef/index.html. + +The German,Spanish,Finnish,French,Hungarian,Italian,Portuguese,Russian and Swedish light stemmers +(common) are based on BSD-licensed reference implementations created by Jacques Savoy and +Ljiljana Dolamic. These files reside in: +analysis/common/src/java/org/apache/lucene/analysis/de/GermanLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/de/GermanMinimalStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/es/SpanishLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/fi/FinnishLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/fr/FrenchLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/fr/FrenchMinimalStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/hu/HungarianLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/it/ItalianLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/pt/PortugueseLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/ru/RussianLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/sv/SwedishLightStemmer.java + +The Stempel analyzer (stempel) includes BSD-licensed software developed +by the Egothor project http://egothor.sf.net/, created by Leo Galambos, Martin Kvapil, +and Edmond Nolan. + +The Polish analyzer (stempel) comes with a default +stopword list that is BSD-licensed created by the Carrot2 project. The file resides +in stempel/src/resources/org/apache/lucene/analysis/pl/stopwords.txt. +See http://project.carrot2.org/license.html. + +The SmartChineseAnalyzer source code (smartcn) was +provided by Xiaoping Gao and copyright 2009 by www.imdict.net. + +WordBreakTestUnicode_*.java (under modules/analysis/common/src/test/) +is derived from Unicode data such as the Unicode Character Database. +See http://unicode.org/copyright.html for more details. + +The Morfologik analyzer (morfologik) includes BSD-licensed software +developed by Dawid Weiss and Marcin Miłkowski (http://morfologik.blogspot.com/). + +Morfologik uses data from Polish ispell/myspell dictionary +(http://www.sjp.pl/slownik/en/) licenced on the terms of (inter alia) +LGPL and Creative Commons ShareAlike. + +Morfologic includes data from BSD-licensed dictionary of Polish (SGJP) +(http://sgjp.pl/morfeusz/) + +Servlet-api.jar and javax.servlet-*.jar are under the CDDL license, the original +source code for this can be found at http://www.eclipse.org/jetty/downloads.php + +=========================================================================== +Kuromoji Japanese Morphological Analyzer - Apache Lucene Integration +=========================================================================== + +This software includes a binary and/or source version of data from + + mecab-ipadic-2.7.0-20070801 + +which can be obtained from + + http://atilika.com/releases/mecab-ipadic/mecab-ipadic-2.7.0-20070801.tar.gz + +or + + http://jaist.dl.sourceforge.net/project/mecab/mecab-ipadic/2.7.0-20070801/mecab-ipadic-2.7.0-20070801.tar.gz + +=========================================================================== +mecab-ipadic-2.7.0-20070801 Notice +=========================================================================== + +Nara Institute of Science and Technology (NAIST), +the copyright holders, disclaims all warranties with regard to this +software, including all implied warranties of merchantability and +fitness, in no event shall NAIST be liable for +any special, indirect or consequential damages or any damages +whatsoever resulting from loss of use, data or profits, whether in an +action of contract, negligence or other tortuous action, arising out +of or in connection with the use or performance of this software. + +A large portion of the dictionary entries +originate from ICOT Free Software. The following conditions for ICOT +Free Software applies to the current dictionary as well. + +Each User may also freely distribute the Program, whether in its +original form or modified, to any third party or parties, PROVIDED +that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear +on, or be attached to, the Program, which is distributed substantially +in the same form as set out herein and that such intended +distribution, if actually made, will neither violate or otherwise +contravene any of the laws and regulations of the countries having +jurisdiction over the User or the intended distribution itself. + +NO WARRANTY + +The program was produced on an experimental basis in the course of the +research and development conducted during the project and is provided +to users as so produced on an experimental basis. Accordingly, the +program is provided without any warranty whatsoever, whether express, +implied, statutory or otherwise. The term "warranty" used herein +includes, but is not limited to, any warranty of the quality, +performance, merchantability and fitness for a particular purpose of +the program and the nonexistence of any infringement or violation of +any right of any third party. + +Each user of the program will agree and understand, and be deemed to +have agreed and understood, that there is no warranty whatsoever for +the program and, accordingly, the entire risk arising from or +otherwise connected with the program is assumed by the user. + +Therefore, neither ICOT, the copyright holder, or any other +organization that participated in or was otherwise related to the +development of the program and their respective officials, directors, +officers and other employees shall be held liable for any and all +damages, including, without limitation, general, special, incidental +and consequential damages, arising out of or otherwise in connection +with the use or inability to use the program or any product, material +or result produced or otherwise obtained by using the program, +regardless of whether they have been advised of, or otherwise had +knowledge of, the possibility of such damages at any time during the +project or thereafter. Each user will be deemed to have agreed to the +foregoing by his or her commencement of use of the program. The term +"use" as used herein includes, but is not limited to, the use, +modification, copying and distribution of the program and the +production of secondary products from the program. + +In the case where the program, whether in its original form or +modified, was distributed or delivered to or received by a user from +any person, organization or entity other than ICOT, unless it makes or +grants independently of ICOT any specific warranty to the user in +writing, such person, organization or entity, will also be exempted +from and not be held liable to the user for any such damages as noted +above as far as the program is concerned. diff --git a/server/licenses/lucene-spatial-extras-8.10.0.jar.sha1 b/modules/legacy-geo/licenses/lucene-spatial-extras-8.10.0.jar.sha1 similarity index 100% rename from server/licenses/lucene-spatial-extras-8.10.0.jar.sha1 rename to modules/legacy-geo/licenses/lucene-spatial-extras-8.10.0.jar.sha1 diff --git a/server/licenses/spatial4j-0.7.jar.sha1 b/modules/legacy-geo/licenses/spatial4j-0.7.jar.sha1 similarity index 100% rename from server/licenses/spatial4j-0.7.jar.sha1 rename to modules/legacy-geo/licenses/spatial4j-0.7.jar.sha1 diff --git a/server/licenses/spatial4j-ABOUT.txt b/modules/legacy-geo/licenses/spatial4j-ABOUT.txt similarity index 100% rename from server/licenses/spatial4j-ABOUT.txt rename to modules/legacy-geo/licenses/spatial4j-ABOUT.txt diff --git a/server/licenses/spatial4j-LICENSE.txt b/modules/legacy-geo/licenses/spatial4j-LICENSE.txt similarity index 100% rename from server/licenses/spatial4j-LICENSE.txt rename to modules/legacy-geo/licenses/spatial4j-LICENSE.txt diff --git a/server/licenses/spatial4j-NOTICE.txt b/modules/legacy-geo/licenses/spatial4j-NOTICE.txt similarity index 100% rename from server/licenses/spatial4j-NOTICE.txt rename to modules/legacy-geo/licenses/spatial4j-NOTICE.txt diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryLegacyGeoShapeIT.java b/modules/legacy-geo/src/internalClusterTest/java/org/elasticsearch/legacygeo/search/GeoBoundingBoxQueryLegacyGeoShapeIT.java similarity index 65% rename from server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryLegacyGeoShapeIT.java rename to modules/legacy-geo/src/internalClusterTest/java/org/elasticsearch/legacygeo/search/GeoBoundingBoxQueryLegacyGeoShapeIT.java index e633b53ac954..90c9b217af8a 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoBoundingBoxQueryLegacyGeoShapeIT.java +++ b/modules/legacy-geo/src/internalClusterTest/java/org/elasticsearch/legacygeo/search/GeoBoundingBoxQueryLegacyGeoShapeIT.java @@ -6,12 +6,13 @@ * Side Public License, v 1. */ -package org.elasticsearch.search.geo; +package org.elasticsearch.legacygeo.search; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.legacygeo.test.TestLegacyGeoShapeFieldMapperPlugin; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.test.TestLegacyGeoShapeFieldMapperPlugin; +import org.elasticsearch.search.geo.GeoBoundingBoxQueryIntegTestCase; import java.io.IOException; import java.util.Collection; @@ -31,9 +32,16 @@ public class GeoBoundingBoxQueryLegacyGeoShapeIT extends GeoBoundingBoxQueryInte @Override public XContentBuilder getMapping() throws IOException { - return XContentFactory.jsonBuilder().startObject().startObject("type1") - .startObject("properties").startObject("location").field("type", "geo_shape").field("strategy", "recursive") - .endObject().endObject().endObject().endObject(); + return XContentFactory.jsonBuilder() + .startObject() + .startObject("type1") + .startObject("properties") + .startObject("location") + .field("type", "geo_shape") + .field("strategy", "recursive") + .endObject() + .endObject() + .endObject() + .endObject(); } } - diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/geo/LegacyGeoShapeIT.java b/modules/legacy-geo/src/internalClusterTest/java/org/elasticsearch/legacygeo/search/LegacyGeoShapeIT.java similarity index 76% rename from server/src/internalClusterTest/java/org/elasticsearch/search/geo/LegacyGeoShapeIT.java rename to modules/legacy-geo/src/internalClusterTest/java/org/elasticsearch/legacygeo/search/LegacyGeoShapeIT.java index 22ae7ba5615b..041bf72c70b2 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/search/geo/LegacyGeoShapeIT.java +++ b/modules/legacy-geo/src/internalClusterTest/java/org/elasticsearch/legacygeo/search/LegacyGeoShapeIT.java @@ -6,14 +6,15 @@ * Side Public License, v 1. */ -package org.elasticsearch.search.geo; +package org.elasticsearch.legacygeo.search; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.geometry.Circle; +import org.elasticsearch.legacygeo.test.TestLegacyGeoShapeFieldMapperPlugin; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.test.TestLegacyGeoShapeFieldMapperPlugin; +import org.elasticsearch.search.geo.GeoShapeIntegTestCase; import java.io.IOException; import java.util.Collection; @@ -51,21 +52,23 @@ public class LegacyGeoShapeIT extends GeoShapeIntegTestCase { */ public void testLegacyCircle() throws Exception { // create index - assertAcked(prepareCreate("test") - .addMapping("shape", "shape", "type=geo_shape,strategy=recursive,tree=geohash").get()); + assertAcked(prepareCreate("test").addMapping("shape", "shape", "type=geo_shape,strategy=recursive,tree=geohash").get()); ensureGreen(); indexRandom(true, client().prepareIndex("test", "shape").setId("0").setSource("shape", (ToXContent) (builder, params) -> { - builder.startObject().field("type", "circle") - .startArray("coordinates").value(30).value(50).endArray() - .field("radius","77km") + builder.startObject() + .field("type", "circle") + .startArray("coordinates") + .value(30) + .value(50) + .endArray() + .field("radius", "77km") .endObject(); return builder; })); // test self crossing of circles - SearchResponse searchResponse = client().prepareSearch("test").setQuery(geoShapeQuery("shape", - new Circle(30, 50, 77000))).get(); + SearchResponse searchResponse = client().prepareSearch("test").setQuery(geoShapeQuery("shape", new Circle(30, 50, 77000))).get(); assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L)); } } diff --git a/server/src/main/java/org/elasticsearch/common/geo/GeoShapeType.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/GeoShapeType.java similarity index 70% rename from server/src/main/java/org/elasticsearch/common/geo/GeoShapeType.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/GeoShapeType.java index 3781a6679294..fb72ee793d39 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/GeoShapeType.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/GeoShapeType.java @@ -5,23 +5,24 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -package org.elasticsearch.common.geo; +package org.elasticsearch.legacygeo; import org.elasticsearch.ElasticsearchParseException; -import org.elasticsearch.common.geo.builders.CircleBuilder; -import org.elasticsearch.common.geo.builders.CoordinatesBuilder; -import org.elasticsearch.common.geo.builders.EnvelopeBuilder; -import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder; -import org.elasticsearch.common.geo.builders.LineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiLineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiPointBuilder; -import org.elasticsearch.common.geo.builders.MultiPolygonBuilder; -import org.elasticsearch.common.geo.builders.PointBuilder; -import org.elasticsearch.common.geo.builders.PolygonBuilder; -import org.elasticsearch.common.geo.builders.ShapeBuilder; -import org.elasticsearch.common.geo.parsers.CoordinateNode; +import org.elasticsearch.common.geo.Orientation; import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry; import org.elasticsearch.common.unit.DistanceUnit; +import org.elasticsearch.legacygeo.builders.CircleBuilder; +import org.elasticsearch.legacygeo.builders.CoordinatesBuilder; +import org.elasticsearch.legacygeo.builders.EnvelopeBuilder; +import org.elasticsearch.legacygeo.builders.GeometryCollectionBuilder; +import org.elasticsearch.legacygeo.builders.LineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiLineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiPointBuilder; +import org.elasticsearch.legacygeo.builders.MultiPolygonBuilder; +import org.elasticsearch.legacygeo.builders.PointBuilder; +import org.elasticsearch.legacygeo.builders.PolygonBuilder; +import org.elasticsearch.legacygeo.builders.ShapeBuilder; +import org.elasticsearch.legacygeo.parsers.CoordinateNode; import org.locationtech.jts.geom.Coordinate; import java.util.ArrayList; @@ -36,8 +37,7 @@ import java.util.Map; public enum GeoShapeType { POINT("point") { @Override - public PointBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, - Orientation orientation, boolean coerce) { + public PointBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, Orientation orientation, boolean coerce) { return new PointBuilder().coordinate(validate(coordinates, coerce).coordinate); } @@ -45,7 +45,8 @@ public enum GeoShapeType { CoordinateNode validate(CoordinateNode coordinates, boolean coerce) { if (coordinates.isEmpty()) { throw new ElasticsearchParseException( - "invalid number of points (0) provided when expecting a single coordinate ([lat, lng])"); + "invalid number of points (0) provided when expecting a single coordinate ([lat, lng])" + ); } else if (coordinates.children != null) { throw new ElasticsearchParseException("multipoint data provided when single point data expected."); } @@ -54,8 +55,12 @@ public enum GeoShapeType { }, MULTIPOINT("multipoint") { @Override - public MultiPointBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, - Orientation orientation, boolean coerce) { + public MultiPointBuilder getBuilder( + CoordinateNode coordinates, + DistanceUnit.Distance radius, + Orientation orientation, + boolean coerce + ) { validate(coordinates, coerce); CoordinatesBuilder coordinatesBuilder = new CoordinatesBuilder(); for (CoordinateNode node : coordinates.children) { @@ -68,11 +73,14 @@ public enum GeoShapeType { CoordinateNode validate(CoordinateNode coordinates, boolean coerce) { if (coordinates.children == null || coordinates.children.isEmpty()) { if (coordinates.coordinate != null) { - throw new ElasticsearchParseException("single coordinate found when expecting an array of " + - "coordinates. change type to point or change data to an array of >0 coordinates"); + throw new ElasticsearchParseException( + "single coordinate found when expecting an array of " + + "coordinates. change type to point or change data to an array of >0 coordinates" + ); } - throw new ElasticsearchParseException("no data provided for multipoint object when expecting " + - ">0 points (e.g., [[lat, lng]] or [[lat, lng], ...])"); + throw new ElasticsearchParseException( + "no data provided for multipoint object when expecting " + ">0 points (e.g., [[lat, lng]] or [[lat, lng], ...])" + ); } else { for (CoordinateNode point : coordinates.children) { POINT.validate(point, coerce); @@ -84,8 +92,12 @@ public enum GeoShapeType { }, LINESTRING("linestring") { @Override - public LineStringBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, - Orientation orientation, boolean coerce) { + public LineStringBuilder getBuilder( + CoordinateNode coordinates, + DistanceUnit.Distance radius, + Orientation orientation, + boolean coerce + ) { validate(coordinates, coerce); CoordinatesBuilder line = new CoordinatesBuilder(); for (CoordinateNode node : coordinates.children) { @@ -97,16 +109,22 @@ public enum GeoShapeType { @Override CoordinateNode validate(CoordinateNode coordinates, boolean coerce) { if (coordinates.children.size() < 2) { - throw new ElasticsearchParseException("invalid number of points in LineString (found [{}] - must be >= 2)", - coordinates.children.size()); + throw new ElasticsearchParseException( + "invalid number of points in LineString (found [{}] - must be >= 2)", + coordinates.children.size() + ); } return coordinates; } }, MULTILINESTRING("multilinestring") { @Override - public MultiLineStringBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, - Orientation orientation, boolean coerce) { + public MultiLineStringBuilder getBuilder( + CoordinateNode coordinates, + DistanceUnit.Distance radius, + Orientation orientation, + boolean coerce + ) { validate(coordinates, coerce); MultiLineStringBuilder multiline = new MultiLineStringBuilder(); for (CoordinateNode node : coordinates.children) { @@ -118,20 +136,27 @@ public enum GeoShapeType { @Override CoordinateNode validate(CoordinateNode coordinates, boolean coerce) { if (coordinates.children.size() < 1) { - throw new ElasticsearchParseException("invalid number of lines in MultiLineString (found [{}] - must be >= 1)", - coordinates.children.size()); + throw new ElasticsearchParseException( + "invalid number of lines in MultiLineString (found [{}] - must be >= 1)", + coordinates.children.size() + ); } return coordinates; } }, POLYGON("polygon") { @Override - public PolygonBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, - Orientation orientation, boolean coerce) { + public PolygonBuilder getBuilder( + CoordinateNode coordinates, + DistanceUnit.Distance radius, + Orientation orientation, + boolean coerce + ) { validate(coordinates, coerce); // build shell - LineStringBuilder shell = LineStringBuilder.class.cast(LINESTRING.getBuilder(coordinates.children.get(0), - radius, orientation, coerce)); + LineStringBuilder shell = LineStringBuilder.class.cast( + LINESTRING.getBuilder(coordinates.children.get(0), radius, orientation, coerce) + ); // build polygon with shell and holes PolygonBuilder polygon = new PolygonBuilder(shell, orientation); for (int i = 1; i < coordinates.children.size(); ++i) { @@ -145,19 +170,24 @@ public enum GeoShapeType { void validateLinearRing(CoordinateNode coordinates, boolean coerce) { if (coordinates.children == null || coordinates.children.isEmpty()) { String error = "Invalid LinearRing found."; - error += (coordinates.coordinate == null) ? - " No coordinate array provided" : " Found a single coordinate when expecting a coordinate array"; + error += (coordinates.coordinate == null) + ? " No coordinate array provided" + : " Found a single coordinate when expecting a coordinate array"; throw new ElasticsearchParseException(error); } int numValidPts = coerce ? 3 : 4; if (coordinates.children.size() < numValidPts) { - throw new ElasticsearchParseException("invalid number of points in LinearRing (found [{}] - must be >= [{}])", - coordinates.children.size(), numValidPts); + throw new ElasticsearchParseException( + "invalid number of points in LinearRing (found [{}] - must be >= [{}])", + coordinates.children.size(), + numValidPts + ); } // close linear ring iff coerce is set and ring is open, otherwise throw parse exception if (coordinates.children.get(0).coordinate.equals( - coordinates.children.get(coordinates.children.size() - 1).coordinate) == false) { + coordinates.children.get(coordinates.children.size() - 1).coordinate + ) == false) { if (coerce) { coordinates.children.add(coordinates.children.get(0)); } else { @@ -176,7 +206,8 @@ public enum GeoShapeType { */ if (coordinates.children == null || coordinates.children.isEmpty()) { throw new ElasticsearchParseException( - "invalid LinearRing provided for type polygon. Linear ring must be an array of coordinates"); + "invalid LinearRing provided for type polygon. Linear ring must be an array of coordinates" + ); } for (CoordinateNode ring : coordinates.children) { validateLinearRing(ring, coerce); @@ -187,8 +218,12 @@ public enum GeoShapeType { }, MULTIPOLYGON("multipolygon") { @Override - public MultiPolygonBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, - Orientation orientation, boolean coerce) { + public MultiPolygonBuilder getBuilder( + CoordinateNode coordinates, + DistanceUnit.Distance radius, + Orientation orientation, + boolean coerce + ) { validate(coordinates, coerce); MultiPolygonBuilder polygons = new MultiPolygonBuilder(orientation); for (CoordinateNode node : coordinates.children) { @@ -205,8 +240,12 @@ public enum GeoShapeType { }, ENVELOPE("envelope") { @Override - public EnvelopeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, - Orientation orientation, boolean coerce) { + public EnvelopeBuilder getBuilder( + CoordinateNode coordinates, + DistanceUnit.Distance radius, + Orientation orientation, + boolean coerce + ) { validate(coordinates, coerce); // verify coordinate bounds, correct if necessary Coordinate uL = coordinates.children.get(0).coordinate; @@ -220,7 +259,9 @@ public enum GeoShapeType { if (coordinates.children.size() != 2) { throw new ElasticsearchParseException( "invalid number of points [{}] provided for geo_shape [{}] when expecting an array of 2 coordinates", - coordinates.children.size(), GeoShapeType.ENVELOPE.shapename); + coordinates.children.size(), + GeoShapeType.ENVELOPE.shapename + ); } return coordinates; } @@ -232,8 +273,7 @@ public enum GeoShapeType { }, CIRCLE("circle") { @Override - public CircleBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, - Orientation orientation, boolean coerce) { + public CircleBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, Orientation orientation, boolean coerce) { return new CircleBuilder().center(coordinates.coordinate).radius(radius); } @@ -246,8 +286,12 @@ public enum GeoShapeType { }, GEOMETRYCOLLECTION("geometrycollection") { @Override - public ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, - Orientation orientation, boolean coerce) { + public ShapeBuilder getBuilder( + CoordinateNode coordinates, + DistanceUnit.Distance radius, + Orientation orientation, + boolean coerce + ) { // noop, handled in parser return null; } @@ -283,11 +327,16 @@ public enum GeoShapeType { if (shapeTypeMap.containsKey(typename)) { return shapeTypeMap.get(typename); } - throw new IllegalArgumentException("unknown geo_shape ["+geoshapename+"]"); + throw new IllegalArgumentException("unknown geo_shape [" + geoshapename + "]"); } - public abstract ShapeBuilder getBuilder(CoordinateNode coordinates, DistanceUnit.Distance radius, - Orientation orientation, boolean coerce); + public abstract ShapeBuilder getBuilder( + CoordinateNode coordinates, + DistanceUnit.Distance radius, + Orientation orientation, + boolean coerce + ); + abstract CoordinateNode validate(CoordinateNode coordinates, boolean coerce); /** wkt shape name */ diff --git a/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/LegacyGeoPlugin.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/LegacyGeoPlugin.java new file mode 100644 index 000000000000..edd26afe526c --- /dev/null +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/LegacyGeoPlugin.java @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.legacygeo; + +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.plugins.ExtensiblePlugin; +import org.elasticsearch.plugins.Plugin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class LegacyGeoPlugin extends Plugin implements ExtensiblePlugin { + + @Override + public List getNamedWriteables() { + if (ShapesAvailability.JTS_AVAILABLE && ShapesAvailability.SPATIAL4J_AVAILABLE) { + return new ArrayList<>(GeoShapeType.getShapeWriteables()); + + } + return Collections.emptyList(); + } + +} diff --git a/server/src/main/java/org/elasticsearch/common/geo/ShapesAvailability.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/ShapesAvailability.java similarity index 96% rename from server/src/main/java/org/elasticsearch/common/geo/ShapesAvailability.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/ShapesAvailability.java index e0b7cbe62532..41d574fc240d 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/ShapesAvailability.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/ShapesAvailability.java @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo; +package org.elasticsearch.legacygeo; public class ShapesAvailability { diff --git a/server/src/main/java/org/elasticsearch/common/geo/XShapeCollection.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/XShapeCollection.java similarity index 67% rename from server/src/main/java/org/elasticsearch/common/geo/XShapeCollection.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/XShapeCollection.java index 7be44a445acc..891bd4b546a5 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/XShapeCollection.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/XShapeCollection.java @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo; +package org.elasticsearch.legacygeo; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Shape; @@ -19,17 +19,17 @@ import java.util.List; */ public class XShapeCollection extends ShapeCollection { - private boolean pointsOnly = false; + private boolean pointsOnly = false; - public XShapeCollection(List shapes, SpatialContext ctx) { - super(shapes, ctx); - } + public XShapeCollection(List shapes, SpatialContext ctx) { + super(shapes, ctx); + } - public boolean pointsOnly() { - return this.pointsOnly; - } + public boolean pointsOnly() { + return this.pointsOnly; + } - public void setPointsOnly(boolean pointsOnly) { - this.pointsOnly = pointsOnly; - } + public void setPointsOnly(boolean pointsOnly) { + this.pointsOnly = pointsOnly; + } } diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/CircleBuilder.java similarity index 92% rename from server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/CircleBuilder.java index 547c74c2695a..fdff95a0b434 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/CircleBuilder.java @@ -6,19 +6,18 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; - -import org.elasticsearch.common.xcontent.ParseField; -import org.elasticsearch.common.geo.GeoShapeType; -import org.elasticsearch.common.geo.parsers.ShapeParser; -import org.locationtech.spatial4j.shape.Circle; -import org.locationtech.jts.geom.Coordinate; +package org.elasticsearch.legacygeo.builders; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.DistanceUnit.Distance; +import org.elasticsearch.common.xcontent.ParseField; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.legacygeo.GeoShapeType; +import org.elasticsearch.legacygeo.parsers.ShapeParser; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.spatial4j.shape.Circle; import java.io.IOException; import java.util.Objects; @@ -154,7 +153,7 @@ public class CircleBuilder extends ShapeBuilder, GeometryCollectionBuilder> { +public class GeometryCollectionBuilder extends ShapeBuilder, GeometryCollectionBuilder> { public static final GeoShapeType TYPE = GeoShapeType.GEOMETRYCOLLECTION; @@ -38,8 +37,7 @@ public class GeometryCollectionBuilder extends ShapeBuilder getShapeAt(int i) { if (i >= this.shapes.size() || i < 0) { - throw new ElasticsearchException("GeometryCollection contains " + this.shapes.size() + " shapes. + " + - "No shape found at index " + i); + throw new ElasticsearchException( + "GeometryCollection contains " + this.shapes.size() + " shapes. + " + "No shape found at index " + i + ); } return this.shapes.get(i); } @@ -153,8 +152,7 @@ public class GeometryCollectionBuilder extends ShapeBuilder(shapes, SPATIAL_CONTEXT); - //note: ShapeCollection is probably faster than a Multi* geom. + if (shapes.size() == 1) return shapes.get(0); + else return new XShapeCollection<>(shapes, SPATIAL_CONTEXT); + // note: ShapeCollection is probably faster than a Multi* geom. } @Override diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/LineStringBuilder.java similarity index 80% rename from server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/LineStringBuilder.java index a63f6c99b6b1..f758b31756dc 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/LineStringBuilder.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/LineStringBuilder.java @@ -6,13 +6,13 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; +package org.elasticsearch.legacygeo.builders; -import org.elasticsearch.common.geo.GeoShapeType; -import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.geometry.Line; +import org.elasticsearch.legacygeo.GeoShapeType; +import org.elasticsearch.legacygeo.parsers.ShapeParser; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; @@ -37,7 +37,9 @@ public class LineStringBuilder extends ShapeBuilder coordinates) { super(coordinates); if (coordinates.size() < 2) { - throw new IllegalArgumentException("invalid number of points in LineString (found [" + coordinates.size()+ "] - must be >= 2)"); + throw new IllegalArgumentException( + "invalid number of points in LineString (found [" + coordinates.size() + "] - must be >= 2)" + ); } } @@ -69,7 +71,7 @@ public class LineStringBuilder extends ShapeBuilder strings = decomposeS4J(FACTORY, coordinates, new ArrayList()); - if(strings.size() == 1) { + if (strings.size() == 1) { geometry = strings.get(0); } else { LineString[] linestrings = strings.toArray(new LineString[strings.size()]); @@ -111,13 +112,12 @@ public class LineStringBuilder extends ShapeBuilderi.x).toArray(), coordinates.stream().mapToDouble(i->i.y).toArray() - ); + return new Line(coordinates.stream().mapToDouble(i -> i.x).toArray(), coordinates.stream().mapToDouble(i -> i.y).toArray()); } static ArrayList decomposeS4J(GeometryFactory factory, Coordinate[] coordinates, ArrayList strings) { - for(Coordinate[] part : decompose(+DATELINE, coordinates)) { - for(Coordinate[] line : decompose(-DATELINE, part)) { + for (Coordinate[] part : decompose(+DATELINE, coordinates)) { + for (Coordinate[] line : decompose(-DATELINE, part)) { strings.add(factory.createLineString(line)); } } @@ -138,35 +138,35 @@ public class LineStringBuilder extends ShapeBuilder DATELINE ? DATELINE : (coordinates[0].x < -DATELINE ? -DATELINE : 0); for (int i = 1; i < coordinates.length; i++) { - double t = intersection(coordinates[i-1], coordinates[i], dateline); - if(Double.isNaN(t) == false) { + double t = intersection(coordinates[i - 1], coordinates[i], dateline); + if (Double.isNaN(t) == false) { Coordinate[] part; - if(t<1) { - part = Arrays.copyOfRange(coordinates, offset, i+1); - part[part.length-1] = Edge.position(coordinates[i-1], coordinates[i], t); - coordinates[offset+i-1] = Edge.position(coordinates[i-1], coordinates[i], t); + if (t < 1) { + part = Arrays.copyOfRange(coordinates, offset, i + 1); + part[part.length - 1] = Edge.position(coordinates[i - 1], coordinates[i], t); + coordinates[offset + i - 1] = Edge.position(coordinates[i - 1], coordinates[i], t); shift(shift, part); - offset = i-1; + offset = i - 1; shift = coordinates[i].x > DATELINE ? DATELINE : (coordinates[i].x < -DATELINE ? -DATELINE : 0); } else { - part = shift(shift, Arrays.copyOfRange(coordinates, offset, i+1)); + part = shift(shift, Arrays.copyOfRange(coordinates, offset, i + 1)); offset = i; } parts.add(part); } } - if(offset == 0) { + if (offset == 0) { parts.add(shift(shift, coordinates)); - } else if(offset < coordinates.length-1) { + } else if (offset < coordinates.length - 1) { Coordinate[] part = Arrays.copyOfRange(coordinates, offset, coordinates.length); parts.add(shift(shift, part)); } return parts.toArray(new Coordinate[parts.size()][]); } - private static Coordinate[] shift(double shift, Coordinate...coordinates) { - if(shift != 0) { + private static Coordinate[] shift(double shift, Coordinate... coordinates) { + if (shift != 0) { for (int j = 0; j < coordinates.length; j++) { coordinates[j] = new Coordinate(coordinates[j].x - 2 * shift, coordinates[j].y); } diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/MultiLineStringBuilder.java similarity index 90% rename from server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/MultiLineStringBuilder.java index a0afa636f1de..bd3c2b2cc79e 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/MultiLineStringBuilder.java @@ -6,16 +6,16 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; +package org.elasticsearch.legacygeo.builders; -import org.elasticsearch.common.geo.GeoShapeType; -import org.elasticsearch.common.geo.parsers.GeoWKTParser; -import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.geometry.Line; import org.elasticsearch.geometry.MultiLine; +import org.elasticsearch.legacygeo.GeoShapeType; +import org.elasticsearch.legacygeo.parsers.GeoWKTParser; +import org.elasticsearch.legacygeo.parsers.ShapeParser; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.LineString; @@ -94,8 +94,7 @@ public class MultiLineStringBuilder extends ShapeBuilder parts = new ArrayList<>(); for (LineStringBuilder line : lines) { LineStringBuilder.decomposeS4J(FACTORY, line.coordinates(false), parts); } - if(parts.size() == 1) { + if (parts.size() == 1) { geometry = parts.get(0); } else { LineString[] lineStrings = parts.toArray(new LineString[parts.size()]); @@ -147,9 +146,9 @@ public class MultiLineStringBuilder extends ShapeBuilder linestrings = new ArrayList<>(lines.size()); for (int i = 0; i < lines.size(); ++i) { LineStringBuilder lsb = lines.get(i); - linestrings.add(new Line(lsb.coordinates.stream().mapToDouble(c->c.x).toArray(), - lsb.coordinates.stream().mapToDouble(c->c.y).toArray() - )); + linestrings.add( + new Line(lsb.coordinates.stream().mapToDouble(c -> c.x).toArray(), lsb.coordinates.stream().mapToDouble(c -> c.y).toArray()) + ); } return new MultiLine(linestrings); } diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/MultiPointBuilder.java similarity index 80% rename from server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/MultiPointBuilder.java index 578bd6205704..9aaef6749eb7 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/MultiPointBuilder.java @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; +package org.elasticsearch.legacygeo.builders; -import org.elasticsearch.common.geo.GeoShapeType; -import org.elasticsearch.common.geo.XShapeCollection; -import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.geometry.MultiPoint; +import org.elasticsearch.legacygeo.GeoShapeType; +import org.elasticsearch.legacygeo.XShapeCollection; +import org.elasticsearch.legacygeo.parsers.ShapeParser; import org.locationtech.jts.geom.Coordinate; import org.locationtech.spatial4j.shape.Point; @@ -60,8 +60,8 @@ public class MultiPointBuilder extends ShapeBuilder, Mul @Override public XShapeCollection buildS4J() { - //Could wrap JtsGeometry but probably slower due to conversions to/from JTS in relate() - //MultiPoint geometry = FACTORY.createMultiPoint(points.toArray(new Coordinate[points.size()])); + // Could wrap JtsGeometry but probably slower due to conversions to/from JTS in relate() + // MultiPoint geometry = FACTORY.createMultiPoint(points.toArray(new Coordinate[points.size()])); List shapes = new ArrayList<>(coordinates.size()); for (Coordinate coord : coordinates) { shapes.add(SPATIAL_CONTEXT.makePoint(coord.x, coord.y)); @@ -76,8 +76,9 @@ public class MultiPointBuilder extends ShapeBuilder, Mul if (coordinates.isEmpty()) { return MultiPoint.EMPTY; } - return new MultiPoint(coordinates.stream().map(coord -> new org.elasticsearch.geometry.Point(coord.x, coord.y)) - .collect(Collectors.toList())); + return new MultiPoint( + coordinates.stream().map(coord -> new org.elasticsearch.geometry.Point(coord.x, coord.y)).collect(Collectors.toList()) + ); } @Override @@ -88,8 +89,7 @@ public class MultiPointBuilder extends ShapeBuilder, Mul @Override public int numDimensions() { if (coordinates == null || coordinates.isEmpty()) { - throw new IllegalStateException("unable to get number of dimensions, " + - "LineString has not yet been initialized"); + throw new IllegalStateException("unable to get number of dimensions, " + "LineString has not yet been initialized"); } return Double.isNaN(coordinates.get(0).z) ? 2 : 3; } diff --git a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/MultiPolygonBuilder.java similarity index 87% rename from server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/MultiPolygonBuilder.java index a2cb0be779e7..5aa6d9d7dff3 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/builders/MultiPolygonBuilder.java @@ -6,18 +6,18 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; +package org.elasticsearch.legacygeo.builders; -import org.elasticsearch.common.geo.GeoShapeType; -import org.elasticsearch.common.geo.XShapeCollection; import org.elasticsearch.common.geo.Orientation; -import org.elasticsearch.common.geo.parsers.GeoWKTParser; -import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.geometry.MultiPolygon; import org.elasticsearch.geometry.Polygon; +import org.elasticsearch.legacygeo.GeoShapeType; +import org.elasticsearch.legacygeo.XShapeCollection; +import org.elasticsearch.legacygeo.parsers.GeoWKTParser; +import org.elasticsearch.legacygeo.parsers.ShapeParser; import org.locationtech.jts.geom.Coordinate; import org.locationtech.spatial4j.shape.Shape; @@ -130,7 +130,7 @@ public class MultiPolygonBuilder extends ShapeBuilder shapes = new ArrayList<>(this.polygons.size()); - if(wrapdateline) { + if (wrapdateline) { for (PolygonBuilder polygon : this.polygons) { - for(Coordinate[][] part : polygon.coordinates()) { + for (Coordinate[][] part : polygon.coordinates()) { shapes.add(jtsGeometry(PolygonBuilder.polygonS4J(FACTORY, part))); } } @@ -169,14 +168,12 @@ public class MultiPolygonBuilder extends ShapeBuilder(shapes, SPATIAL_CONTEXT); - //note: ShapeCollection is probably faster than a Multi* geom. + if (shapes.size() == 1) return shapes.get(0); + else return new XShapeCollection<>(shapes, SPATIAL_CONTEXT); + // note: ShapeCollection is probably faster than a Multi* geom. } - @SuppressWarnings({"unchecked"}) + @SuppressWarnings({ "unchecked" }) @Override public MultiPolygon buildGeometry() { List shapes = new ArrayList<>(this.polygons.size()); @@ -186,7 +183,7 @@ public class MultiPolygonBuilder extends ShapeBuilder) poly); } else { - shapes.add((Polygon)poly); + shapes.add((Polygon) poly); } } if (shapes.isEmpty()) { @@ -209,7 +206,6 @@ public class MultiPolygonBuilder extends ShapeBuilder(1)); + // super(new ArrayList<>(1)); super(); this.coordinates.add(new Coordinate(lon, lat)); } @@ -64,11 +64,11 @@ public class PointBuilder extends ShapeBuilder points = lineString.coordinates; if (points.size() < 4) { - throw new IllegalArgumentException( - "invalid number of points in LinearRing (found [" + points.size() + "] - must be >= 4)"); + throw new IllegalArgumentException("invalid number of points in LinearRing (found [" + points.size() + "] - must be >= 4)"); } if (points.get(0).equals(points.get(points.size() - 1)) == false) { - throw new IllegalArgumentException("invalid LinearRing found (coordinates are not closed)"); + throw new IllegalArgumentException("invalid LinearRing found (coordinates are not closed)"); } } @@ -203,7 +202,7 @@ public class PolygonBuilder extends ShapeBuilder linearRing.coordinates.get(i).y != - linearRing.coordinates.get(i + 1).y > linearRing.coordinates.get(i).y) { + if (linearRing.coordinates.get(i - 1).x == linearRing.coordinates.get(i + 1).x + && linearRing.coordinates.get(i - 1).y > linearRing.coordinates.get(i).y != linearRing.coordinates.get( + i + 1 + ).y > linearRing.coordinates.get(i).y) { // coplanar continue; } @@ -255,7 +255,7 @@ public class PolygonBuilder extends ShapeBuilder coordinates) { - return new org.elasticsearch.geometry.LinearRing(coordinates.stream().mapToDouble(i -> i.x).toArray(), + return new org.elasticsearch.geometry.LinearRing( + coordinates.stream().mapToDouble(i -> i.x).toArray(), coordinates.stream().mapToDouble(i -> i.y).toArray() ); } @@ -324,8 +323,7 @@ public class PolygonBuilder extends ShapeBuilder 1) { - holes = new LinearRing[polygon.length-1]; + if (polygon.length > 1) { + holes = new LinearRing[polygon.length - 1]; for (int i = 0; i < holes.length; i++) { - holes[i] = factory.createLinearRing(polygon[i+1]); + holes[i] = factory.createLinearRing(polygon[i + 1]); } } else { holes = null; @@ -375,8 +373,8 @@ public class PolygonBuilder extends ShapeBuilder edges, double[] partitionPoint) { // find a coordinate that is not part of the dateline Edge any = edge; - while(any.coordinate.x == +DATELINE || any.coordinate.x == -DATELINE) { - if((any = any.next) == edge) { + while (any.coordinate.x == +DATELINE || any.coordinate.x == -DATELINE) { + if ((any = any.next) == edge) { break; } } @@ -437,9 +435,9 @@ public class PolygonBuilder extends ShapeBuilder= 0) { - double[] partitionPoint = new double[3]; - int length = component(edges[i], -(components.size()+numHoles+1), mainEdges, partitionPoint); + double[] partitionPoint = new double[3]; + int length = component(edges[i], -(components.size() + numHoles + 1), mainEdges, partitionPoint); List component = new ArrayList<>(); - component.add(coordinates(edges[i], new Coordinate[length+1], partitionPoint)); + component.add(coordinates(edges[i], new Coordinate[length + 1], partitionPoint)); components.add(component); } } @@ -583,7 +581,7 @@ public class PolygonBuilder extends ShapeBuilder 0) { - //TODO: Check if we could save the set null step + // TODO: Check if we could save the set null step numHoles--; - holes[e2.component-1] = holes[numHoles]; + holes[e2.component - 1] = holes[numHoles]; holes[numHoles] = null; } // only connect edges if intersections are pairwise // 1. per the comment above, the edge array is sorted by y-value of the intersection - // with the dateline. Two edges have the same y intercept when they cross the + // with the dateline. Two edges have the same y intercept when they cross the // dateline thus they appear sequentially (pairwise) in the edge array. Two edges // do not have the same y intercept when we're forming a multi-poly from a poly // that wraps the dateline (but there are 2 ordered intercepts). @@ -623,12 +621,14 @@ public class PolygonBuilder extends ShapeBuilder DATELINE && rng != 2*DATELINE)) || (translated.get() && component != 0)) { + if ((incorrectOrientation && (rng > DATELINE && rng != 2 * DATELINE)) || (translated.get() && component != 0)) { translate(points); // flip the translation bit if the shell is being translated if (component == 0) { @@ -755,10 +771,17 @@ public class PolygonBuilder extends ShapeBuilder= length+edgeOffset; - assert points.length >= length+pointOffset; + private static Edge[] concat( + int component, + boolean direction, + Coordinate[] points, + final int pointOffset, + Edge[] edges, + final int edgeOffset, + int length + ) { + assert edges.length >= length + edgeOffset; + assert points.length >= length + pointOffset; edges[edgeOffset] = new Edge(points[pointOffset], null); for (int i = 1; i < length; i++) { if (direction) { @@ -789,7 +812,7 @@ public class PolygonBuilder extends ShapeBuilder> implements NamedWriteable, ToXContentObject { +public abstract class ShapeBuilder> + implements + NamedWriteable, + ToXContentObject { protected static final Logger LOGGER = LogManager.getLogger(ShapeBuilder.class); @@ -74,7 +75,7 @@ public abstract class ShapeBuilder(size); - for (int i=0; i < size; i++) { + for (int i = 0; i < size; i++) { coordinates.add(readFromStream(in)); } } @@ -126,7 +127,7 @@ public abstract class ShapeBuilder that = (ShapeBuilder) o; + ShapeBuilder that = (ShapeBuilder) o; return Objects.equals(coordinates, that.coordinates); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/LegacyGeoShapeFieldMapper.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/mapper/LegacyGeoShapeFieldMapper.java similarity index 73% rename from server/src/main/java/org/elasticsearch/index/mapper/LegacyGeoShapeFieldMapper.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/mapper/LegacyGeoShapeFieldMapper.java index a3a4051d0dec..b6fa595ac1b2 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/LegacyGeoShapeFieldMapper.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/mapper/LegacyGeoShapeFieldMapper.java @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -package org.elasticsearch.index.mapper; +package org.elasticsearch.legacygeo.mapper; import org.apache.lucene.search.Query; import org.apache.lucene.spatial.prefix.PrefixTreeStrategy; @@ -20,13 +20,9 @@ import org.elasticsearch.Version; import org.elasticsearch.common.Explicit; import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.geo.GeometryFormatterFactory; -import org.elasticsearch.common.geo.ShapeRelation; -import org.elasticsearch.common.geo.ShapesAvailability; -import org.elasticsearch.common.geo.SpatialStrategy; -import org.elasticsearch.common.geo.XShapeCollection; -import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.geo.Orientation; -import org.elasticsearch.common.geo.parsers.ShapeParser; +import org.elasticsearch.common.geo.ShapeRelation; +import org.elasticsearch.common.geo.SpatialStrategy; import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.unit.DistanceUnit; @@ -34,8 +30,20 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.core.CheckedConsumer; import org.elasticsearch.geometry.Geometry; -import org.elasticsearch.index.query.LegacyGeoShapeQueryProcessor; +import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper; +import org.elasticsearch.index.mapper.DocumentParserContext; +import org.elasticsearch.index.mapper.FieldMapper; +import org.elasticsearch.index.mapper.GeoShapeQueryable; +import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.index.mapper.Mapper; +import org.elasticsearch.index.mapper.MapperBuilderContext; +import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.query.SearchExecutionContext; +import org.elasticsearch.legacygeo.ShapesAvailability; +import org.elasticsearch.legacygeo.XShapeCollection; +import org.elasticsearch.legacygeo.builders.ShapeBuilder; +import org.elasticsearch.legacygeo.parsers.ShapeParser; +import org.elasticsearch.legacygeo.query.LegacyGeoShapeQueryProcessor; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.jts.JtsGeometry; @@ -71,15 +79,16 @@ import java.util.function.Function; *

* "field" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0)) * - * @deprecated use {@link GeoShapeFieldMapper} + * @deprecated use {@link org.elasticsearch.index.mapper.GeoShapeFieldMapper} */ @Deprecated public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper> { public static final String CONTENT_TYPE = "geo_shape"; - public static final Set DEPRECATED_PARAMETERS - = new HashSet<>(Arrays.asList("strategy", "tree", "tree_levels", "precision", "distance_error_pct", "points_only")); + public static final Set DEPRECATED_PARAMETERS = new HashSet<>( + Arrays.asList("strategy", "tree", "tree_levels", "precision", "distance_error_pct", "points_only") + ); public static boolean containsDeprecatedParameter(Set paramKeys) { return DEPRECATED_PARAMETERS.stream().anyMatch(paramKeys::contains); @@ -115,8 +124,19 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< public static final String GEOHASH = "geohash"; } + @Deprecated + public static class DeprecatedParameters { + + private static void checkPrefixTreeSupport(String fieldName) { + if (ShapesAvailability.JTS_AVAILABLE == false || ShapesAvailability.SPATIAL4J_AVAILABLE == false) { + throw new ElasticsearchParseException("Field parameter [{}] is not supported for [{}] field type", fieldName, CONTENT_TYPE); + } + + } + } + private static Builder builder(FieldMapper in) { - return ((LegacyGeoShapeFieldMapper)in).builder; + return ((LegacyGeoShapeFieldMapper) in).builder; } public static class Builder extends FieldMapper.Builder { @@ -128,28 +148,42 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< final Parameter> coerce; Parameter> orientation = orientationParam(m -> builder(m).orientation.get()); - Parameter strategy = new Parameter<>("strategy", false, () -> SpatialStrategy.RECURSIVE, - (n, c, o) -> SpatialStrategy.fromString(o.toString(), DEPRECATION_LOGGER), m -> builder(m).strategy.get()) - .deprecated(); - Parameter tree = Parameter.stringParam("tree", false, m -> builder(m).tree.get(), Defaults.TREE) - .deprecated(); - Parameter treeLevels = new Parameter<>("tree_levels", false, () -> null, - (n, c, o) -> o == null ? null : XContentMapValues.nodeIntegerValue(o), - m -> builder(m).treeLevels.get()) - .deprecated(); - Parameter precision = new Parameter<>("precision", false, () -> null, - (n, c, o) -> o == null ? null : DistanceUnit.Distance.parseDistance(o.toString()), - m -> builder(m).precision.get()) - .deprecated(); - Parameter distanceErrorPct = new Parameter<>("distance_error_pct", true, () -> null, - (n, c, o) -> o == null ? null : XContentMapValues.nodeDoubleValue(o), - m -> builder(m).distanceErrorPct.get()) - .deprecated() - .acceptsNull(); - Parameter pointsOnly = new Parameter<>("points_only", false, + Parameter strategy = new Parameter<>( + "strategy", + false, + () -> SpatialStrategy.RECURSIVE, + (n, c, o) -> SpatialStrategy.fromString(o.toString(), DEPRECATION_LOGGER), + m -> builder(m).strategy.get() + ).deprecated(); + Parameter tree = Parameter.stringParam("tree", false, m -> builder(m).tree.get(), Defaults.TREE).deprecated(); + Parameter treeLevels = new Parameter<>( + "tree_levels", + false, () -> null, - (n, c, o) -> XContentMapValues.nodeBooleanValue(o), m -> builder(m).pointsOnly.get()) - .deprecated().acceptsNull(); + (n, c, o) -> o == null ? null : XContentMapValues.nodeIntegerValue(o), + m -> builder(m).treeLevels.get() + ).deprecated(); + Parameter precision = new Parameter<>( + "precision", + false, + () -> null, + (n, c, o) -> o == null ? null : DistanceUnit.Distance.parseDistance(o.toString()), + m -> builder(m).precision.get() + ).deprecated(); + Parameter distanceErrorPct = new Parameter<>( + "distance_error_pct", + true, + () -> null, + (n, c, o) -> o == null ? null : XContentMapValues.nodeDoubleValue(o), + m -> builder(m).distanceErrorPct.get() + ).deprecated().acceptsNull(); + Parameter pointsOnly = new Parameter<>( + "points_only", + false, + () -> null, + (n, c, o) -> XContentMapValues.nodeBooleanValue(o), + m -> builder(m).pointsOnly.get() + ).deprecated().acceptsNull(); Parameter> meta = Parameter.metaParam(); @@ -209,8 +243,20 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< @Override protected List> getParameters() { - return Arrays.asList(indexed, ignoreMalformed, ignoreZValue, coerce, orientation, - strategy, tree, treeLevels, precision, distanceErrorPct, pointsOnly, meta); + return Arrays.asList( + indexed, + ignoreMalformed, + ignoreZValue, + coerce, + orientation, + strategy, + tree, + treeLevels, + precision, + distanceErrorPct, + pointsOnly, + meta + ); } public Builder coerce(boolean coerce) { @@ -241,14 +287,20 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< private void setupPrefixTrees(GeoShapeFieldType ft) { SpatialPrefixTree prefixTree; if (ft.tree().equals(PrefixTrees.GEOHASH)) { - prefixTree = new GeohashPrefixTree(ShapeBuilder.SPATIAL_CONTEXT, - getLevels(ft.treeLevels(), ft.precisionInMeters(), Defaults.GEOHASH_TREE_LEVELS, true)); + prefixTree = new GeohashPrefixTree( + ShapeBuilder.SPATIAL_CONTEXT, + getLevels(ft.treeLevels(), ft.precisionInMeters(), Defaults.GEOHASH_TREE_LEVELS, true) + ); } else if (ft.tree().equals(PrefixTrees.LEGACY_QUADTREE)) { - prefixTree = new QuadPrefixTree(ShapeBuilder.SPATIAL_CONTEXT, - getLevels(ft.treeLevels(), ft.precisionInMeters(), Defaults.QUADTREE_LEVELS, false)); + prefixTree = new QuadPrefixTree( + ShapeBuilder.SPATIAL_CONTEXT, + getLevels(ft.treeLevels(), ft.precisionInMeters(), Defaults.QUADTREE_LEVELS, false) + ); } else if (ft.tree().equals(PrefixTrees.QUADTREE)) { - prefixTree = new PackedQuadPrefixTree(ShapeBuilder.SPATIAL_CONTEXT, - getLevels(ft.treeLevels(), ft.precisionInMeters(), Defaults.QUADTREE_LEVELS, false)); + prefixTree = new PackedQuadPrefixTree( + ShapeBuilder.SPATIAL_CONTEXT, + getLevels(ft.treeLevels(), ft.precisionInMeters(), Defaults.QUADTREE_LEVELS, false) + ); } else { throw new IllegalArgumentException("Unknown prefix tree type [" + ft.tree() + "]"); } @@ -271,8 +323,13 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< } private GeoShapeFieldType buildFieldType(LegacyGeoShapeParser parser, MapperBuilderContext context) { - GeoShapeFieldType ft = - new GeoShapeFieldType(context.buildFullName(name), indexed.get(), orientation.get().value(), parser, meta.get()); + GeoShapeFieldType ft = new GeoShapeFieldType( + context.buildFullName(name), + indexed.get(), + orientation.get().value(), + parser, + meta.get() + ); setupFieldTypeDeprecatedParameters(ft); setupPrefixTrees(ft); return ft; @@ -280,8 +337,14 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< private static int getLevels(int treeLevels, double precisionInMeters, int defaultLevels, boolean geoHash) { if (treeLevels > 0 || precisionInMeters >= 0) { - return Math.max(treeLevels, precisionInMeters >= 0 ? (geoHash ? GeoUtils.geoHashLevelsForPrecision(precisionInMeters) - : GeoUtils.quadTreeLevelsForPrecision(precisionInMeters)) : 0); + return Math.max( + treeLevels, + precisionInMeters >= 0 + ? (geoHash + ? GeoUtils.geoHashLevelsForPrecision(precisionInMeters) + : GeoUtils.quadTreeLevelsForPrecision(precisionInMeters)) + : 0 + ); } return defaultLevels; } @@ -294,9 +357,7 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< } LegacyGeoShapeParser parser = new LegacyGeoShapeParser(); GeoShapeFieldType ft = buildFieldType(parser, context); - return new LegacyGeoShapeFieldMapper(name, ft, - multiFieldsBuilder.build(this, context), copyTo.build(), - parser, this); + return new LegacyGeoShapeFieldMapper(name, ft, multiFieldsBuilder.build(this, context), copyTo.build(), parser, this); } } @@ -305,18 +366,18 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< boolean ignoreMalformedByDefault = IGNORE_MALFORMED_SETTING.get(parserContext.getSettings()); boolean coerceByDefault = COERCE_SETTING.get(parserContext.getSettings()); FieldMapper.Builder builder = new LegacyGeoShapeFieldMapper.Builder( - name, - parserContext.indexVersionCreated(), - ignoreMalformedByDefault, - coerceByDefault); + name, + parserContext.indexVersionCreated(), + ignoreMalformedByDefault, + coerceByDefault + ); builder.parse(name, parserContext, node); return builder; }; private static class LegacyGeoShapeParser extends Parser> { - private LegacyGeoShapeParser() { - } + private LegacyGeoShapeParser() {} @Override public void parse( @@ -355,8 +416,13 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< private final LegacyGeoShapeQueryProcessor queryProcessor; - private GeoShapeFieldType(String name, boolean indexed, Orientation orientation, - LegacyGeoShapeParser parser, Map meta) { + private GeoShapeFieldType( + String name, + boolean indexed, + Orientation orientation, + LegacyGeoShapeParser parser, + Map meta + ) { super(name, indexed, false, false, parser, orientation, meta); this.queryProcessor = new LegacyGeoShapeQueryProcessor(this); } @@ -371,8 +437,13 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< } @Override - public Query geoShapeQuery(Geometry shape, String fieldName, SpatialStrategy strategy, ShapeRelation relation, - SearchExecutionContext context) { + public Query geoShapeQuery( + Geometry shape, + String fieldName, + SpatialStrategy strategy, + ShapeRelation relation, + SearchExecutionContext context + ) { return queryProcessor.geoShapeQuery(shape, fieldName, strategy, relation, context); } @@ -407,6 +478,7 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< public void setPointsOnly(boolean pointsOnly) { this.pointsOnly = pointsOnly; } + public int treeLevels() { return treeLevels; } @@ -462,13 +534,26 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< private final Version indexCreatedVersion; private final Builder builder; - public LegacyGeoShapeFieldMapper(String simpleName, MappedFieldType mappedFieldType, - MultiFields multiFields, CopyTo copyTo, - LegacyGeoShapeParser parser, - Builder builder) { - super(simpleName, mappedFieldType, Collections.singletonMap(mappedFieldType.name(), Lucene.KEYWORD_ANALYZER), - builder.ignoreMalformed.get(), builder.coerce.get(), builder.ignoreZValue.get(), builder.orientation.get(), - multiFields, copyTo, parser); + public LegacyGeoShapeFieldMapper( + String simpleName, + MappedFieldType mappedFieldType, + MultiFields multiFields, + CopyTo copyTo, + LegacyGeoShapeParser parser, + Builder builder + ) { + super( + simpleName, + mappedFieldType, + Collections.singletonMap(mappedFieldType.name(), Lucene.KEYWORD_ANALYZER), + builder.ignoreMalformed.get(), + builder.coerce.get(), + builder.ignoreZValue.get(), + builder.orientation.get(), + multiFields, + copyTo, + parser + ); this.indexCreatedVersion = builder.indexCreatedVersion; this.builder = builder; } @@ -478,7 +563,7 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< return (GeoShapeFieldType) super.fieldType(); } - String strategy() { + public String strategy() { return fieldType().strategy().getStrategyName(); } @@ -492,15 +577,20 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< // index configured for pointsOnly if (shape instanceof XShapeCollection && ((XShapeCollection) shape).pointsOnly()) { // MULTIPOINT data: index each point separately - @SuppressWarnings("unchecked") List shapes = ((XShapeCollection) shape).getShapes(); + @SuppressWarnings("unchecked") + List shapes = ((XShapeCollection) shape).getShapes(); for (Shape s : shapes) { context.doc().addAll(Arrays.asList(fieldType().defaultPrefixTreeStrategy().createIndexableFields(s))); } return; } else if (shape instanceof Point == false) { - throw new MapperParsingException("[{" + fieldType().name() + "}] is configured for points only but a " - + ((shape instanceof JtsGeometry) ? ((JtsGeometry)shape).getGeom().getGeometryType() : shape.getClass()) - + " was found"); + throw new MapperParsingException( + "[{" + + fieldType().name() + + "}] is configured for points only but a " + + ((shape instanceof JtsGeometry) ? ((JtsGeometry) shape).getGeom().getGeometryType() : shape.getClass()) + + " was found" + ); } } context.doc().addAll(Arrays.asList(fieldType().defaultPrefixTreeStrategy().createIndexableFields(shape))); @@ -514,16 +604,20 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper< @Override public FieldMapper.Builder getMergeBuilder() { - return new Builder(simpleName(), indexCreatedVersion, - builder.ignoreMalformed.getDefaultValue().value(), builder.coerce.getDefaultValue().value()).init(this); + return new Builder( + simpleName(), + indexCreatedVersion, + builder.ignoreMalformed.getDefaultValue().value(), + builder.coerce.getDefaultValue().value() + ).init(this); } @Override protected void checkIncomingMergeType(FieldMapper mergeWith) { - if (mergeWith instanceof AbstractShapeGeometryFieldMapper - && (mergeWith instanceof LegacyGeoShapeFieldMapper) == false) { - throw new IllegalArgumentException("mapper [" + name() - + "] of type [geo_shape] cannot change strategy from [recursive] to [BKD]"); + if (mergeWith instanceof LegacyGeoShapeFieldMapper == false && CONTENT_TYPE.equals(mergeWith.typeName())) { + throw new IllegalArgumentException( + "mapper [" + name() + "] of type [geo_shape] cannot change strategy from [recursive] to [BKD]" + ); } super.checkIncomingMergeType(mergeWith); } diff --git a/server/src/main/java/org/elasticsearch/common/geo/parsers/CoordinateNode.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/CoordinateNode.java similarity index 98% rename from server/src/main/java/org/elasticsearch/common/geo/parsers/CoordinateNode.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/CoordinateNode.java index 58ee7b9b4fc0..efc08c7a7518 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/parsers/CoordinateNode.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/CoordinateNode.java @@ -5,12 +5,12 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -package org.elasticsearch.common.geo.parsers; +package org.elasticsearch.legacygeo.parsers; -import org.locationtech.jts.geom.Coordinate; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.locationtech.jts.geom.Coordinate; import java.io.IOException; import java.util.List; diff --git a/server/src/main/java/org/elasticsearch/common/geo/parsers/GeoJsonParser.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/GeoJsonParser.java similarity index 88% rename from server/src/main/java/org/elasticsearch/common/geo/parsers/GeoJsonParser.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/GeoJsonParser.java index 3608609212e1..9afc9f2ed77c 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/parsers/GeoJsonParser.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/GeoJsonParser.java @@ -5,19 +5,19 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -package org.elasticsearch.common.geo.parsers; +package org.elasticsearch.legacygeo.parsers; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.geo.GeoPoint; -import org.elasticsearch.common.geo.GeoShapeType; import org.elasticsearch.common.geo.Orientation; -import org.elasticsearch.common.geo.builders.CircleBuilder; -import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder; -import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentSubParser; import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper; +import org.elasticsearch.legacygeo.GeoShapeType; +import org.elasticsearch.legacygeo.builders.CircleBuilder; +import org.elasticsearch.legacygeo.builders.GeometryCollectionBuilder; +import org.elasticsearch.legacygeo.builders.ShapeBuilder; import org.locationtech.jts.geom.Coordinate; import java.io.IOException; @@ -37,9 +37,7 @@ abstract class GeoJsonParser { CoordinateNode coordinateNode = null; GeometryCollectionBuilder geometryCollections = null; - Orientation orientation = (shapeMapper == null) - ? Orientation.RIGHT - : shapeMapper.orientation(); + Orientation orientation = (shapeMapper == null) ? Orientation.RIGHT : shapeMapper.orientation(); boolean coerce = shapeMapper != null && shapeMapper.coerce(); boolean ignoreZValue = shapeMapper == null || shapeMapper.ignoreZValue(); @@ -55,8 +53,12 @@ abstract class GeoJsonParser { subParser.nextToken(); final GeoShapeType type = GeoShapeType.forName(subParser.text()); if (shapeType != null && shapeType.equals(type) == false) { - malformedException = ShapeParser.FIELD_TYPE + " already parsed as [" - + shapeType + "] cannot redefine as [" + type + "]"; + malformedException = ShapeParser.FIELD_TYPE + + " already parsed as [" + + shapeType + + "] cannot redefine as [" + + type + + "]"; } else { shapeType = type; } @@ -64,16 +66,14 @@ abstract class GeoJsonParser { subParser.nextToken(); CoordinateNode tempNode = parseCoordinates(subParser, ignoreZValue); if (coordinateNode != null && tempNode.numDimensions() != coordinateNode.numDimensions()) { - throw new ElasticsearchParseException("Exception parsing coordinates: " + - "number of dimensions do not match"); + throw new ElasticsearchParseException("Exception parsing coordinates: " + "number of dimensions do not match"); } coordinateNode = tempNode; } else if (ShapeParser.FIELD_GEOMETRIES.match(fieldName, subParser.getDeprecationHandler())) { if (shapeType == null) { shapeType = GeoShapeType.GEOMETRYCOLLECTION; } else if (shapeType.equals(GeoShapeType.GEOMETRYCOLLECTION) == false) { - malformedException = "cannot have [" + ShapeParser.FIELD_GEOMETRIES + "] with type set to [" - + shapeType + "]"; + malformedException = "cannot have [" + ShapeParser.FIELD_GEOMETRIES + "] with type set to [" + shapeType + "]"; } subParser.nextToken(); geometryCollections = parseGeometries(subParser, shapeMapper); @@ -81,8 +81,7 @@ abstract class GeoJsonParser { if (shapeType == null) { shapeType = GeoShapeType.CIRCLE; } else if (shapeType.equals(GeoShapeType.CIRCLE) == false) { - malformedException = "cannot have [" + CircleBuilder.FIELD_RADIUS + "] with type set to [" - + shapeType + "]"; + malformedException = "cannot have [" + CircleBuilder.FIELD_RADIUS + "] with type set to [" + shapeType + "]"; } subParser.nextToken(); radius = DistanceUnit.Distance.parseDistance(subParser.text()); @@ -110,8 +109,7 @@ abstract class GeoJsonParser { } else if (geometryCollections == null && GeoShapeType.GEOMETRYCOLLECTION == shapeType) { throw new ElasticsearchParseException("geometries not included"); } else if (radius != null && GeoShapeType.CIRCLE != shapeType) { - throw new ElasticsearchParseException("field [{}] is supported for [{}] only", CircleBuilder.FIELD_RADIUS, - CircleBuilder.TYPE); + throw new ElasticsearchParseException("field [{}] is supported for [{}] only", CircleBuilder.FIELD_RADIUS, CircleBuilder.TYPE); } if (shapeType.equals(GeoShapeType.GEOMETRYCOLLECTION)) { @@ -141,9 +139,9 @@ abstract class GeoJsonParser { XContentParser.Token token = parser.nextToken(); // Base cases - if (token != XContentParser.Token.START_ARRAY && - token != XContentParser.Token.END_ARRAY && - token != XContentParser.Token.VALUE_NULL) { + if (token != XContentParser.Token.START_ARRAY + && token != XContentParser.Token.END_ARRAY + && token != XContentParser.Token.VALUE_NULL) { return new CoordinateNode(parseCoordinate(parser, ignoreZValue)); } else if (token == XContentParser.Token.VALUE_NULL) { throw new IllegalArgumentException("coordinates cannot contain NULL values)"); @@ -192,8 +190,7 @@ abstract class GeoJsonParser { * @return Geometry[] geometries of the GeometryCollection * @throws IOException Thrown if an error occurs while reading from the XContentParser */ - static GeometryCollectionBuilder parseGeometries(XContentParser parser, AbstractShapeGeometryFieldMapper mapper) throws - IOException { + static GeometryCollectionBuilder parseGeometries(XContentParser parser, AbstractShapeGeometryFieldMapper mapper) throws IOException { if (parser.currentToken() != XContentParser.Token.START_ARRAY) { throw new ElasticsearchParseException("geometries must be an array of geojson objects"); } diff --git a/server/src/main/java/org/elasticsearch/common/geo/parsers/GeoWKTParser.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/GeoWKTParser.java similarity index 79% rename from server/src/main/java/org/elasticsearch/common/geo/parsers/GeoWKTParser.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/GeoWKTParser.java index cf9e853add47..24479b4bfd99 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/parsers/GeoWKTParser.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/GeoWKTParser.java @@ -5,25 +5,25 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -package org.elasticsearch.common.geo.parsers; +package org.elasticsearch.legacygeo.parsers; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.geo.GeoPoint; -import org.elasticsearch.common.geo.GeoShapeType; -import org.elasticsearch.common.geo.builders.CoordinatesBuilder; -import org.elasticsearch.common.geo.builders.EnvelopeBuilder; -import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder; -import org.elasticsearch.common.geo.builders.LineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiLineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiPointBuilder; -import org.elasticsearch.common.geo.builders.MultiPolygonBuilder; -import org.elasticsearch.common.geo.builders.PointBuilder; -import org.elasticsearch.common.geo.builders.PolygonBuilder; -import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.geo.Orientation; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper; +import org.elasticsearch.legacygeo.GeoShapeType; +import org.elasticsearch.legacygeo.builders.CoordinatesBuilder; +import org.elasticsearch.legacygeo.builders.EnvelopeBuilder; +import org.elasticsearch.legacygeo.builders.GeometryCollectionBuilder; +import org.elasticsearch.legacygeo.builders.LineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiLineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiPointBuilder; +import org.elasticsearch.legacygeo.builders.MultiPolygonBuilder; +import org.elasticsearch.legacygeo.builders.PointBuilder; +import org.elasticsearch.legacygeo.builders.PolygonBuilder; +import org.elasticsearch.legacygeo.builders.ShapeBuilder; import org.locationtech.jts.geom.Coordinate; import java.io.IOException; @@ -53,19 +53,21 @@ public class GeoWKTParser { private GeoWKTParser() {} public static ShapeBuilder parse(XContentParser parser, final AbstractShapeGeometryFieldMapper shapeMapper) - throws IOException, ElasticsearchParseException { + throws IOException, ElasticsearchParseException { return parseExpectedType(parser, null, shapeMapper); } - public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType) - throws IOException, ElasticsearchParseException { + public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType) throws IOException, + ElasticsearchParseException { return parseExpectedType(parser, shapeType, null); } /** throws an exception if the parsed geometry type does not match the expected shape type */ - public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType, - final AbstractShapeGeometryFieldMapper shapeMapper) - throws IOException, ElasticsearchParseException { + public static ShapeBuilder parseExpectedType( + XContentParser parser, + final GeoShapeType shapeType, + final AbstractShapeGeometryFieldMapper shapeMapper + ) throws IOException, ElasticsearchParseException { try (StringReader reader = new StringReader(parser.text())) { boolean coerce = shapeMapper != null && shapeMapper.coerce(); boolean ignoreZValue = shapeMapper == null || shapeMapper.ignoreZValue(); @@ -88,9 +90,12 @@ public class GeoWKTParser { } /** parse geometry from the stream tokenizer */ - private static ShapeBuilder parseGeometry(StreamTokenizer stream, GeoShapeType shapeType, final boolean ignoreZValue, - final boolean coerce) - throws IOException, ElasticsearchParseException { + private static ShapeBuilder parseGeometry( + StreamTokenizer stream, + GeoShapeType shapeType, + final boolean ignoreZValue, + final boolean coerce + ) throws IOException, ElasticsearchParseException { final GeoShapeType type = GeoShapeType.forName(nextWord(stream)); if (shapeType != null && shapeType != GeoShapeType.GEOMETRYCOLLECTION) { if (type.wktName().equals(shapeType.wktName()) == false) { @@ -134,8 +139,8 @@ public class GeoWKTParser { return new EnvelopeBuilder(new Coordinate(minLon, maxLat), new Coordinate(maxLon, minLat)); } - private static PointBuilder parsePoint(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) - throws IOException, ElasticsearchParseException { + private static PointBuilder parsePoint(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) throws IOException, + ElasticsearchParseException { if (nextEmptyOrOpen(stream).equals(EMPTY)) { return null; } @@ -148,7 +153,7 @@ public class GeoWKTParser { } private static List parseCoordinateList(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) - throws IOException, ElasticsearchParseException { + throws IOException, ElasticsearchParseException { CoordinatesBuilder coordinates = new CoordinatesBuilder(); boolean isOpenParen = false; if (isNumberNext(stream) || (isOpenParen = nextWord(stream).equals(LPAREN))) { @@ -171,8 +176,8 @@ public class GeoWKTParser { return coordinates.build(); } - private static Coordinate parseCoordinate(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) - throws IOException, ElasticsearchParseException { + private static Coordinate parseCoordinate(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) throws IOException, + ElasticsearchParseException { final double lon = nextNumber(stream); final double lat = nextNumber(stream); Double z = null; @@ -183,7 +188,7 @@ public class GeoWKTParser { } private static MultiPointBuilder parseMultiPoint(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) - throws IOException, ElasticsearchParseException { + throws IOException, ElasticsearchParseException { String token = nextEmptyOrOpen(stream); if (token.equals(EMPTY)) { return new MultiPointBuilder(); @@ -191,8 +196,8 @@ public class GeoWKTParser { return new MultiPointBuilder(parseCoordinateList(stream, ignoreZValue, coerce)); } - private static LineStringBuilder parseLine(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) - throws IOException, ElasticsearchParseException { + private static LineStringBuilder parseLine(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) throws IOException, + ElasticsearchParseException { String token = nextEmptyOrOpen(stream); if (token.equals(EMPTY)) { return null; @@ -203,7 +208,7 @@ public class GeoWKTParser { // A LinearRing is closed LineString with 4 or more positions. The first and last positions // are equivalent (they represent equivalent points). private static LineStringBuilder parseLinearRing(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) - throws IOException, ElasticsearchParseException { + throws IOException, ElasticsearchParseException { String token = nextEmptyOrOpen(stream); if (token.equals(EMPTY)) { return null; @@ -220,14 +225,13 @@ public class GeoWKTParser { } } if (coordinates.size() < 4) { - throw new ElasticsearchParseException("invalid number of points in LinearRing (found [{}] - must be >= 4)", - coordinates.size()); + throw new ElasticsearchParseException("invalid number of points in LinearRing (found [{}] - must be >= 4)", coordinates.size()); } return new LineStringBuilder(coordinates); } private static MultiLineStringBuilder parseMultiLine(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) - throws IOException, ElasticsearchParseException { + throws IOException, ElasticsearchParseException { String token = nextEmptyOrOpen(stream); if (token.equals(EMPTY)) { return new MultiLineStringBuilder(); @@ -240,13 +244,12 @@ public class GeoWKTParser { return builder; } - private static PolygonBuilder parsePolygon(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) - throws IOException, ElasticsearchParseException { + private static PolygonBuilder parsePolygon(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) throws IOException, + ElasticsearchParseException { if (nextEmptyOrOpen(stream).equals(EMPTY)) { return null; } - PolygonBuilder builder = new PolygonBuilder(parseLinearRing(stream, ignoreZValue, coerce), - Orientation.RIGHT); + PolygonBuilder builder = new PolygonBuilder(parseLinearRing(stream, ignoreZValue, coerce), Orientation.RIGHT); while (nextCloserOrComma(stream).equals(COMMA)) { builder.hole(parseLinearRing(stream, ignoreZValue, coerce)); } @@ -254,7 +257,7 @@ public class GeoWKTParser { } private static MultiPolygonBuilder parseMultiPolygon(StreamTokenizer stream, final boolean ignoreZValue, final boolean coerce) - throws IOException, ElasticsearchParseException { + throws IOException, ElasticsearchParseException { if (nextEmptyOrOpen(stream).equals(EMPTY)) { return null; } @@ -265,14 +268,17 @@ public class GeoWKTParser { return builder; } - private static GeometryCollectionBuilder parseGeometryCollection(StreamTokenizer stream, final boolean ignoreZValue, - final boolean coerce) - throws IOException, ElasticsearchParseException { + private static GeometryCollectionBuilder parseGeometryCollection( + StreamTokenizer stream, + final boolean ignoreZValue, + final boolean coerce + ) throws IOException, ElasticsearchParseException { if (nextEmptyOrOpen(stream).equals(EMPTY)) { return null; } GeometryCollectionBuilder builder = new GeometryCollectionBuilder().shape( - parseGeometry(stream, GeoShapeType.GEOMETRYCOLLECTION, ignoreZValue, coerce)); + parseGeometry(stream, GeoShapeType.GEOMETRYCOLLECTION, ignoreZValue, coerce) + ); while (nextCloserOrComma(stream).equals(COMMA)) { builder.shape(parseGeometry(stream, null, ignoreZValue, coerce)); } @@ -285,9 +291,12 @@ public class GeoWKTParser { case StreamTokenizer.TT_WORD: final String word = stream.sval; return word.equalsIgnoreCase(EMPTY) ? EMPTY : word; - case '(': return LPAREN; - case ')': return RPAREN; - case ',': return COMMA; + case '(': + return LPAREN; + case ')': + return RPAREN; + case ',': + return COMMA; } throw new ElasticsearchParseException("expected word but found: " + tokenString(stream), stream.lineno()); } @@ -309,10 +318,14 @@ public class GeoWKTParser { private static String tokenString(StreamTokenizer stream) { switch (stream.ttype) { - case StreamTokenizer.TT_WORD: return stream.sval; - case StreamTokenizer.TT_EOF: return EOF; - case StreamTokenizer.TT_EOL: return EOL; - case StreamTokenizer.TT_NUMBER: return NUMBER; + case StreamTokenizer.TT_WORD: + return stream.sval; + case StreamTokenizer.TT_EOF: + return EOF; + case StreamTokenizer.TT_EOL: + return EOL; + case StreamTokenizer.TT_NUMBER: + return NUMBER; } return "'" + (char) stream.ttype + "'"; } @@ -328,8 +341,10 @@ public class GeoWKTParser { if (next.equals(EMPTY) || next.equals(LPAREN)) { return next; } - throw new ElasticsearchParseException("expected " + EMPTY + " or " + LPAREN - + " but found: " + tokenString(stream), stream.lineno()); + throw new ElasticsearchParseException( + "expected " + EMPTY + " or " + LPAREN + " but found: " + tokenString(stream), + stream.lineno() + ); } private static String nextCloser(StreamTokenizer stream) throws IOException, ElasticsearchParseException { @@ -351,15 +366,19 @@ public class GeoWKTParser { if (token.equals(COMMA) || token.equals(RPAREN)) { return token; } - throw new ElasticsearchParseException("expected " + COMMA + " or " + RPAREN - + " but found: " + tokenString(stream), stream.lineno()); + throw new ElasticsearchParseException( + "expected " + COMMA + " or " + RPAREN + " but found: " + tokenString(stream), + stream.lineno() + ); } /** next word in the stream */ private static void checkEOF(StreamTokenizer stream) throws ElasticsearchParseException, IOException { if (stream.nextToken() != StreamTokenizer.TT_EOF) { - throw new ElasticsearchParseException("expected end of WKT string but found additional text: " - + tokenString(stream), stream.lineno()); + throw new ElasticsearchParseException( + "expected end of WKT string but found additional text: " + tokenString(stream), + stream.lineno() + ); } } } diff --git a/server/src/main/java/org/elasticsearch/common/geo/parsers/ShapeParser.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/ShapeParser.java similarity index 86% rename from server/src/main/java/org/elasticsearch/common/geo/parsers/ShapeParser.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/ShapeParser.java index 56f2fe3ff520..a8d26a19d473 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/parsers/ShapeParser.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/parsers/ShapeParser.java @@ -5,18 +5,18 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -package org.elasticsearch.common.geo.parsers; +package org.elasticsearch.legacygeo.parsers; import org.elasticsearch.ElasticsearchParseException; -import org.elasticsearch.common.xcontent.ParseField; -import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.ParseField; import org.elasticsearch.common.xcontent.XContent; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.support.MapXContentParser; import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper; import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper; +import org.elasticsearch.legacygeo.builders.ShapeBuilder; import java.io.IOException; import java.util.Collections; @@ -46,11 +46,12 @@ public interface ShapeParser { if (geometryMapper instanceof AbstractShapeGeometryFieldMapper == false) { throw new IllegalArgumentException("geometry must be a shape type"); } - shapeMapper = (AbstractShapeGeometryFieldMapper) geometryMapper; + shapeMapper = (AbstractShapeGeometryFieldMapper) geometryMapper; } if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { return null; - } if (parser.currentToken() == XContentParser.Token.START_OBJECT) { + } + if (parser.currentToken() == XContentParser.Token.START_OBJECT) { return GeoJsonParser.parse(parser, shapeMapper); } else if (parser.currentToken() == XContentParser.Token.VALUE_STRING) { return GeoWKTParser.parse(parser, shapeMapper); @@ -70,8 +71,14 @@ public interface ShapeParser { } static ShapeBuilder parse(Object value) throws IOException { - try (XContentParser parser = new MapXContentParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, - Collections.singletonMap("value", value), null)) { + try ( + XContentParser parser = new MapXContentParser( + NamedXContentRegistry.EMPTY, + LoggingDeprecationHandler.INSTANCE, + Collections.singletonMap("value", value), + null + ) + ) { parser.nextToken(); // start object parser.nextToken(); // field name parser.nextToken(); // field value diff --git a/server/src/main/java/org/elasticsearch/index/query/LegacyGeoShapeQueryProcessor.java b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/query/LegacyGeoShapeQueryProcessor.java similarity index 81% rename from server/src/main/java/org/elasticsearch/index/query/LegacyGeoShapeQueryProcessor.java rename to modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/query/LegacyGeoShapeQueryProcessor.java index eb2520d47ee9..0f72452a9263 100644 --- a/server/src/main/java/org/elasticsearch/index/query/LegacyGeoShapeQueryProcessor.java +++ b/modules/legacy-geo/src/main/java/org/elasticsearch/legacygeo/query/LegacyGeoShapeQueryProcessor.java @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -package org.elasticsearch.index.query; +package org.elasticsearch.legacygeo.query; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; @@ -16,19 +16,9 @@ import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy; import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialOperation; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.geo.Orientation; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.geo.SpatialStrategy; -import org.elasticsearch.common.geo.builders.CircleBuilder; -import org.elasticsearch.common.geo.builders.EnvelopeBuilder; -import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder; -import org.elasticsearch.common.geo.builders.LineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiLineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiPointBuilder; -import org.elasticsearch.common.geo.builders.MultiPolygonBuilder; -import org.elasticsearch.common.geo.builders.PointBuilder; -import org.elasticsearch.common.geo.builders.PolygonBuilder; -import org.elasticsearch.common.geo.builders.ShapeBuilder; -import org.elasticsearch.common.geo.Orientation; import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.geometry.Circle; import org.elasticsearch.geometry.Geometry; @@ -42,7 +32,19 @@ 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.LegacyGeoShapeFieldMapper; +import org.elasticsearch.index.query.ExistsQueryBuilder; +import org.elasticsearch.index.query.SearchExecutionContext; +import org.elasticsearch.legacygeo.builders.CircleBuilder; +import org.elasticsearch.legacygeo.builders.EnvelopeBuilder; +import org.elasticsearch.legacygeo.builders.GeometryCollectionBuilder; +import org.elasticsearch.legacygeo.builders.LineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiLineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiPointBuilder; +import org.elasticsearch.legacygeo.builders.MultiPolygonBuilder; +import org.elasticsearch.legacygeo.builders.PointBuilder; +import org.elasticsearch.legacygeo.builders.PolygonBuilder; +import org.elasticsearch.legacygeo.builders.ShapeBuilder; +import org.elasticsearch.legacygeo.mapper.LegacyGeoShapeFieldMapper; import org.locationtech.jts.geom.Coordinate; import org.locationtech.spatial4j.shape.Shape; @@ -51,7 +53,7 @@ import java.util.List; import static org.elasticsearch.search.SearchService.ALLOW_EXPENSIVE_QUERIES; -public class LegacyGeoShapeQueryProcessor { +public class LegacyGeoShapeQueryProcessor { private final LegacyGeoShapeFieldMapper.GeoShapeFieldType shapeFieldType; @@ -59,11 +61,19 @@ public class LegacyGeoShapeQueryProcessor { this.shapeFieldType = shapeFieldType; } - public Query geoShapeQuery(Geometry shape, String fieldName, SpatialStrategy strategy, - ShapeRelation relation, SearchExecutionContext context) { + public Query geoShapeQuery( + Geometry shape, + String fieldName, + SpatialStrategy strategy, + ShapeRelation relation, + SearchExecutionContext context + ) { if (context.allowExpensiveQueries() == false) { - throw new ElasticsearchException("[geo-shape] queries on [PrefixTree geo shapes] cannot be executed when '" - + ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false."); + throw new ElasticsearchException( + "[geo-shape] queries on [PrefixTree geo shapes] cannot be executed when '" + + ALLOW_EXPENSIVE_QUERIES.getKey() + + "' is set to false." + ); } SpatialStrategy spatialStrategy = shapeFieldType.strategy(); @@ -175,9 +185,11 @@ public class LegacyGeoShapeQueryProcessor { @Override public ShapeBuilder visit(Polygon polygon) { - PolygonBuilder polygonBuilder = - new PolygonBuilder((LineStringBuilder) visit((Line) polygon.getPolygon()), - Orientation.RIGHT, false); + PolygonBuilder polygonBuilder = new PolygonBuilder( + (LineStringBuilder) visit((Line) polygon.getPolygon()), + Orientation.RIGHT, + false + ); for (int i = 0; i < polygon.getNumberOfHoles(); i++) { polygonBuilder.hole((LineStringBuilder) visit((Line) polygon.getHole(i))); } @@ -186,8 +198,10 @@ public class LegacyGeoShapeQueryProcessor { @Override public ShapeBuilder visit(Rectangle rectangle) { - return new EnvelopeBuilder(new Coordinate(rectangle.getMinX(), rectangle.getMaxY()), - new Coordinate(rectangle.getMaxX(), rectangle.getMinY())); + return new EnvelopeBuilder( + new Coordinate(rectangle.getMinX(), rectangle.getMaxY()), + new Coordinate(rectangle.getMaxX(), rectangle.getMinY()) + ); } }); return shapeBuilder; diff --git a/server/src/test/java/org/elasticsearch/common/geo/BaseGeoParsingTestCase.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/BaseGeoParsingTestCase.java similarity index 84% rename from server/src/test/java/org/elasticsearch/common/geo/BaseGeoParsingTestCase.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/BaseGeoParsingTestCase.java index 601ef7459445..dcbb4ca73a30 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/BaseGeoParsingTestCase.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/BaseGeoParsingTestCase.java @@ -5,15 +5,15 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -package org.elasticsearch.common.geo; +package org.elasticsearch.legacygeo; -import org.elasticsearch.common.geo.parsers.ShapeParser; +import org.elasticsearch.common.geo.GeometryParser; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.geometry.utils.GeographyValidator; import org.elasticsearch.index.mapper.GeoShapeIndexer; +import org.elasticsearch.legacygeo.parsers.ShapeParser; +import org.elasticsearch.legacygeo.test.ElasticsearchGeoAssertions; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.spatial4j.shape.Shape; @@ -26,19 +26,26 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static org.elasticsearch.common.geo.builders.ShapeBuilder.SPATIAL_CONTEXT; +import static org.elasticsearch.legacygeo.builders.ShapeBuilder.SPATIAL_CONTEXT; /** Base class for all geo parsing tests */ abstract class BaseGeoParsingTestCase extends ESTestCase { protected static final GeometryFactory GEOMETRY_FACTORY = SPATIAL_CONTEXT.getGeometryFactory(); public abstract void testParsePoint() throws IOException, ParseException; + public abstract void testParseMultiPoint() throws IOException, ParseException; + public abstract void testParseLineString() throws IOException, ParseException; + public abstract void testParseMultiLineString() throws IOException, ParseException; + public abstract void testParsePolygon() throws IOException, ParseException; + public abstract void testParseMultiPolygon() throws IOException, ParseException; + public abstract void testParseEnvelope() throws IOException, ParseException; + public abstract void testParseGeometryCollection() throws IOException, ParseException; protected void assertValidException(XContentBuilder builder, Class expectedException) throws IOException { @@ -62,13 +69,6 @@ abstract class BaseGeoParsingTestCase extends ESTestCase { } } - protected void assertGeometryEquals(org.elasticsearch.geometry.Geometry expected, XContentBuilder geoJson) throws IOException { - try (XContentParser parser = createParser(geoJson)) { - parser.nextToken(); - assertEquals(expected, GeoJson.fromXContent(GeographyValidator.instance(false), false, true, parser)); - } - } - protected ShapeCollection shapeCollection(Shape... shapes) { return new ShapeCollection<>(Arrays.asList(shapes), SPATIAL_CONTEXT); } diff --git a/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/GeoJsonShapeParserTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/GeoJsonShapeParserTests.java new file mode 100644 index 000000000000..784a26911b54 --- /dev/null +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/GeoJsonShapeParserTests.java @@ -0,0 +1,2292 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.legacygeo; + +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.Version; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.common.geo.GeoUtils; +import org.elasticsearch.common.geo.GeometryParser; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.geometry.Geometry; +import org.elasticsearch.geometry.GeometryCollection; +import org.elasticsearch.geometry.Line; +import org.elasticsearch.geometry.MultiLine; +import org.elasticsearch.geometry.MultiPoint; +import org.elasticsearch.index.mapper.GeoShapeIndexer; +import org.elasticsearch.index.mapper.MapperBuilderContext; +import org.elasticsearch.legacygeo.mapper.LegacyGeoShapeFieldMapper; +import org.elasticsearch.legacygeo.parsers.ShapeParser; +import org.elasticsearch.legacygeo.test.ElasticsearchGeoAssertions; +import org.elasticsearch.test.VersionUtils; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.MultiLineString; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.spatial4j.exception.InvalidShapeException; +import org.locationtech.spatial4j.shape.Circle; +import org.locationtech.spatial4j.shape.Rectangle; +import org.locationtech.spatial4j.shape.Shape; +import org.locationtech.spatial4j.shape.ShapeCollection; +import org.locationtech.spatial4j.shape.jts.JtsPoint; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.elasticsearch.legacygeo.builders.ShapeBuilder.SPATIAL_CONTEXT; + +/** + * Tests for {@code GeoJSONShapeParser} + */ +public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase { + + @Override + public void testParsePoint() throws IOException, ParseException { + XContentBuilder pointGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Point") + .startArray("coordinates") + .value(100.0) + .value(0.0) + .endArray() + .endObject(); + Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0)); + assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson, true); + assertGeometryEquals(new org.elasticsearch.geometry.Point(100d, 0d), pointGeoJson, false); + } + + @Override + public void testParseLineString() throws IOException, ParseException { + XContentBuilder lineGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "LineString") + .startArray("coordinates") + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .endArray() + .endObject(); + + List lineCoordinates = new ArrayList<>(); + lineCoordinates.add(new Coordinate(100, 0)); + lineCoordinates.add(new Coordinate(101, 1)); + + try (XContentParser parser = createParser(lineGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertLineString(shape, true); + } + + try (XContentParser parser = createParser(lineGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertLineString(parse(parser), false); + } + } + + @Override + public void testParseMultiLineString() throws IOException, ParseException { + XContentBuilder multilinesGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "MultiLineString") + .startArray("coordinates") + .startArray() + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(102.0) + .value(2.0) + .endArray() + .startArray() + .value(103.0) + .value(3.0) + .endArray() + .endArray() + .endArray() + .endObject(); + + MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString( + new LineString[] { + GEOMETRY_FACTORY.createLineString(new Coordinate[] { new Coordinate(100, 0), new Coordinate(101, 1), }), + GEOMETRY_FACTORY.createLineString(new Coordinate[] { new Coordinate(102, 2), new Coordinate(103, 3), }), } + ); + assertGeometryEquals(jtsGeom(expected), multilinesGeoJson, true); + assertGeometryEquals( + new MultiLine( + Arrays.asList( + new Line(new double[] { 100d, 101d }, new double[] { 0d, 1d }), + new Line(new double[] { 102d, 103d }, new double[] { 2d, 3d }) + ) + ), + multilinesGeoJson, + false + ); + } + + public void testParseCircle() throws IOException, ParseException { + XContentBuilder multilinesGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "circle") + .startArray("coordinates") + .value(100.0) + .value(0.0) + .endArray() + .field("radius", "100m") + .endObject(); + + Circle expected = SPATIAL_CONTEXT.makeCircle(100.0, 0.0, 360 * 100 / GeoUtils.EARTH_EQUATOR); + assertGeometryEquals(expected, multilinesGeoJson, true); + } + + public void testParseMultiDimensionShapes() throws IOException { + // multi dimension point + XContentBuilder pointGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Point") + .startArray("coordinates") + .value(100.0) + .value(0.0) + .value(15.0) + .value(18.0) + .endArray() + .endObject(); + + XContentParser parser = createParser(pointGeoJson); + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + + // multi dimension linestring + XContentBuilder lineGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "LineString") + .startArray("coordinates") + .startArray() + .value(100.0) + .value(0.0) + .value(15.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .value(18.0) + .value(19.0) + .endArray() + .endArray() + .endObject(); + + parser = createParser(lineGeoJson); + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + + @Override + public void testParseEnvelope() throws IOException, ParseException { + // test #1: envelope with expected coordinate order (TopLeft, BottomRight) + XContentBuilder multilinesGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "envelope") + .startArray("coordinates") + .startArray() + .value(-50) + .value(30) + .endArray() + .startArray() + .value(50) + .value(-30) + .endArray() + .endArray() + .endObject(); + Rectangle expected = SPATIAL_CONTEXT.makeRectangle(-50, 50, -30, 30); + assertGeometryEquals(expected, multilinesGeoJson, true); + assertGeometryEquals(new org.elasticsearch.geometry.Rectangle(-50, 50, 30, -30), multilinesGeoJson, false); + + // test #2: envelope that spans dateline + multilinesGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "envelope") + .startArray("coordinates") + .startArray() + .value(50) + .value(30) + .endArray() + .startArray() + .value(-50) + .value(-30) + .endArray() + .endArray() + .endObject(); + + expected = SPATIAL_CONTEXT.makeRectangle(50, -50, -30, 30); + assertGeometryEquals(expected, multilinesGeoJson, true); + assertGeometryEquals(new org.elasticsearch.geometry.Rectangle(50, -50, 30, -30), multilinesGeoJson, false); + + // test #3: "envelope" (actually a triangle) with invalid number of coordinates (TopRight, BottomLeft, BottomRight) + multilinesGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "envelope") + .startArray("coordinates") + .startArray() + .value(50) + .value(30) + .endArray() + .startArray() + .value(-50) + .value(-30) + .endArray() + .startArray() + .value(50) + .value(-39) + .endArray() + .endArray() + .endObject(); + try (XContentParser parser = createParser(multilinesGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + + // test #4: "envelope" with empty coordinates + multilinesGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "envelope") + .startArray("coordinates") + .endArray() + .endObject(); + try (XContentParser parser = createParser(multilinesGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + } + + @Override + public void testParsePolygon() throws IOException, ParseException { + XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(100.0) + .value(1.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .startArray() + .value(101.0) + .value(0.0) + .endArray() + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .startArray() + .value(100.0) + .value(1.0) + .endArray() + .endArray() + .endArray() + .endObject(); + + List shellCoordinates = new ArrayList<>(); + shellCoordinates.add(new Coordinate(100, 0)); + shellCoordinates.add(new Coordinate(101, 0)); + shellCoordinates.add(new Coordinate(101, 1)); + shellCoordinates.add(new Coordinate(100, 1)); + shellCoordinates.add(new Coordinate(100, 0)); + Coordinate[] coordinates = shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]); + LinearRing shell = GEOMETRY_FACTORY.createLinearRing(coordinates); + Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null); + assertGeometryEquals(jtsGeom(expected), polygonGeoJson, true); + + org.elasticsearch.geometry.Polygon p = new org.elasticsearch.geometry.Polygon( + new org.elasticsearch.geometry.LinearRing(new double[] { 100d, 101d, 101d, 100d, 100d }, new double[] { 0d, 0d, 1d, 1d, 0d }) + ); + assertGeometryEquals(p, polygonGeoJson, false); + } + + public void testParse3DPolygon() throws IOException, ParseException { + XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(100.0) + .value(1.0) + .value(10.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .value(10.0) + .endArray() + .startArray() + .value(101.0) + .value(0.0) + .value(10.0) + .endArray() + .startArray() + .value(100.0) + .value(0.0) + .value(10.0) + .endArray() + .startArray() + .value(100.0) + .value(1.0) + .value(10.0) + .endArray() + .endArray() + .endArray() + .endObject(); + + List shellCoordinates = new ArrayList<>(); + shellCoordinates.add(new Coordinate(100, 0, 10)); + shellCoordinates.add(new Coordinate(101, 0, 10)); + shellCoordinates.add(new Coordinate(101, 1, 10)); + shellCoordinates.add(new Coordinate(100, 1, 10)); + shellCoordinates.add(new Coordinate(100, 0, 10)); + Coordinate[] coordinates = shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]); + + Version randomVersion = VersionUtils.randomIndexCompatibleVersion(random()); + Settings indexSettings = Settings.builder() + .put(IndexMetadata.SETTING_VERSION_CREATED, randomVersion) + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0) + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetadata.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()) + .build(); + + LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null); + final LegacyGeoShapeFieldMapper mapperBuilder = new LegacyGeoShapeFieldMapper.Builder("test", Version.CURRENT, false, true).build( + MapperBuilderContext.ROOT + ); + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertEquals(jtsGeom(expected), ShapeParser.parse(parser, mapperBuilder).buildS4J()); + } + + org.elasticsearch.geometry.Polygon p = new org.elasticsearch.geometry.Polygon( + new org.elasticsearch.geometry.LinearRing( + Arrays.stream(coordinates).mapToDouble(i -> i.x).toArray(), + Arrays.stream(coordinates).mapToDouble(i -> i.y).toArray() + ) + ); + assertGeometryEquals(p, polygonGeoJson, false); + } + + public void testInvalidDimensionalPolygon() throws IOException { + XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(100.0) + .value(1.0) + .value(10.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .startArray() + .value(101.0) + .value(0.0) + .value(10.0) + .endArray() + .startArray() + .value(100.0) + .value(0.0) + .value(10.0) + .endArray() + .startArray() + .value(100.0) + .value(1.0) + .value(10.0) + .endArray() + .endArray() + .endArray() + .endObject(); + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + } + + public void testParseInvalidPoint() throws IOException { + // test case 1: create an invalid point object with multipoint data format + XContentBuilder invalidPoint1 = XContentFactory.jsonBuilder() + .startObject() + .field("type", "point") + .startArray("coordinates") + .startArray() + .value(-74.011) + .value(40.753) + .endArray() + .endArray() + .endObject(); + try (XContentParser parser = createParser(invalidPoint1)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + + // test case 2: create an invalid point object with an empty number of coordinates + XContentBuilder invalidPoint2 = XContentFactory.jsonBuilder() + .startObject() + .field("type", "point") + .startArray("coordinates") + .endArray() + .endObject(); + try (XContentParser parser = createParser(invalidPoint2)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + } + + public void testParseInvalidMultipoint() throws IOException { + // test case 1: create an invalid multipoint object with single coordinate + XContentBuilder invalidMultipoint1 = XContentFactory.jsonBuilder() + .startObject() + .field("type", "multipoint") + .startArray("coordinates") + .value(-74.011) + .value(40.753) + .endArray() + .endObject(); + try (XContentParser parser = createParser(invalidMultipoint1)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + + // test case 2: create an invalid multipoint object with null coordinate + XContentBuilder invalidMultipoint2 = XContentFactory.jsonBuilder() + .startObject() + .field("type", "multipoint") + .startArray("coordinates") + .endArray() + .endObject(); + try (XContentParser parser = createParser(invalidMultipoint2)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + + // test case 3: create a valid formatted multipoint object with invalid number (0) of coordinates + XContentBuilder invalidMultipoint3 = XContentFactory.jsonBuilder() + .startObject() + .field("type", "multipoint") + .startArray("coordinates") + .startArray() + .endArray() + .endArray() + .endObject(); + try (XContentParser parser = createParser(invalidMultipoint3)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + } + + public void testParseInvalidMultiPolygon() throws IOException { + // test invalid multipolygon (an "accidental" polygon with inner rings outside outer ring) + String multiPolygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "MultiPolygon") + .startArray("coordinates") + .startArray()// one poly (with two holes) + .startArray() + .startArray() + .value(102.0) + .value(2.0) + .endArray() + .startArray() + .value(103.0) + .value(2.0) + .endArray() + .startArray() + .value(103.0) + .value(3.0) + .endArray() + .startArray() + .value(102.0) + .value(3.0) + .endArray() + .startArray() + .value(102.0) + .value(2.0) + .endArray() + .endArray() + .startArray()// first hole + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .startArray() + .value(101.0) + .value(0.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .startArray() + .value(100.0) + .value(1.0) + .endArray() + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .endArray() + .startArray()// second hole + .startArray() + .value(100.2) + .value(0.8) + .endArray() + .startArray() + .value(100.2) + .value(0.2) + .endArray() + .startArray() + .value(100.8) + .value(0.2) + .endArray() + .startArray() + .value(100.8) + .value(0.8) + .endArray() + .startArray() + .value(100.2) + .value(0.8) + .endArray() + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, multiPolygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class); + assertNull(parser.nextToken()); + } + } + + public void testParseInvalidDimensionalMultiPolygon() throws IOException { + // test invalid multipolygon (an "accidental" polygon with inner rings outside outer ring) + String multiPolygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "MultiPolygon") + .startArray("coordinates") + .startArray()// first poly (without holes) + .startArray() + .startArray() + .value(102.0) + .value(2.0) + .endArray() + .startArray() + .value(103.0) + .value(2.0) + .endArray() + .startArray() + .value(103.0) + .value(3.0) + .endArray() + .startArray() + .value(102.0) + .value(3.0) + .endArray() + .startArray() + .value(102.0) + .value(2.0) + .endArray() + .endArray() + .endArray() + .startArray()// second poly (with hole) + .startArray() + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .startArray() + .value(101.0) + .value(0.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .startArray() + .value(100.0) + .value(1.0) + .endArray() + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .endArray() + .startArray()// hole + .startArray() + .value(100.2) + .value(0.8) + .endArray() + .startArray() + .value(100.2) + .value(0.2) + .value(10.0) + .endArray() + .startArray() + .value(100.8) + .value(0.2) + .endArray() + .startArray() + .value(100.8) + .value(0.8) + .endArray() + .startArray() + .value(100.2) + .value(0.8) + .endArray() + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, multiPolygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + } + + public void testParseOGCPolygonWithoutHoles() throws IOException, ParseException { + // test 1: ccw poly not crossing dateline + String polygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertPolygon(shape, true); + } + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); + } + + // test 2: ccw poly crossing dateline + polygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); + } + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); + } + + // test 3: cw poly not crossing dateline + polygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(180.0) + .value(10.0) + .endArray() + .startArray() + .value(180.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertPolygon(shape, true); + } + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); + } + + // test 4: cw poly crossing dateline + polygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(184.0) + .value(15.0) + .endArray() + .startArray() + .value(184.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(174.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); + } + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); + } + } + + public void testParseOGCPolygonWithHoles() throws IOException, ParseException { + // test 1: ccw poly not crossing dateline + String polygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(-172.0) + .value(8.0) + .endArray() + .startArray() + .value(174.0) + .value(10.0) + .endArray() + .startArray() + .value(-172.0) + .value(-8.0) + .endArray() + .startArray() + .value(-172.0) + .value(8.0) + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertPolygon(shape, true); + } + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); + } + + // test 2: ccw poly crossing dateline + polygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(178.0) + .value(8.0) + .endArray() + .startArray() + .value(-178.0) + .value(8.0) + .endArray() + .startArray() + .value(-180.0) + .value(-8.0) + .endArray() + .startArray() + .value(178.0) + .value(8.0) + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); + } + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); + } + + // test 3: cw poly not crossing dateline + polygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(180.0) + .value(10.0) + .endArray() + .startArray() + .value(179.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(177.0) + .value(8.0) + .endArray() + .startArray() + .value(179.0) + .value(10.0) + .endArray() + .startArray() + .value(179.0) + .value(-8.0) + .endArray() + .startArray() + .value(177.0) + .value(8.0) + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertPolygon(shape, true); + } + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); + } + + // test 4: cw poly crossing dateline + polygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(183.0) + .value(10.0) + .endArray() + .startArray() + .value(183.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(183.0) + .value(10.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(178.0) + .value(8.0) + .endArray() + .startArray() + .value(182.0) + .value(8.0) + .endArray() + .startArray() + .value(180.0) + .value(-8.0) + .endArray() + .startArray() + .value(178.0) + .value(8.0) + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); + } + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); + } + } + + public void testParseInvalidPolygon() throws IOException { + /** + * The following 3 test cases ensure proper error handling of invalid polygons + * per the GeoJSON specification + */ + // test case 1: create an invalid polygon with only 2 points + String invalidPoly = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(-74.011) + .value(40.753) + .endArray() + .startArray() + .value(-75.022) + .value(41.783) + .endArray() + .endArray() + .endArray() + .endObject() + ); + try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + + // test case 2: create an invalid polygon with only 1 point + invalidPoly = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(-74.011) + .value(40.753) + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + + // test case 3: create an invalid polygon with 0 points + invalidPoly = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "polygon") + .startArray("coordinates") + .startArray() + .startArray() + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + + // test case 4: create an invalid polygon with null value points + invalidPoly = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "polygon") + .startArray("coordinates") + .startArray() + .startArray() + .nullValue() + .nullValue() + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, IllegalArgumentException.class); + assertNull(parser.nextToken()); + } + + // test case 5: create an invalid polygon with 1 invalid LinearRing + invalidPoly = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "polygon") + .startArray("coordinates") + .nullValue() + .nullValue() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, IllegalArgumentException.class); + assertNull(parser.nextToken()); + } + + // test case 6: create an invalid polygon with 0 LinearRings + invalidPoly = Strings.toString( + XContentFactory.jsonBuilder().startObject().field("type", "polygon").startArray("coordinates").endArray().endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + + // test case 7: create an invalid polygon with 0 LinearRings + invalidPoly = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "polygon") + .startArray("coordinates") + .startArray() + .value(-74.011) + .value(40.753) + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + } + + public void testParsePolygonWithHole() throws IOException, ParseException { + XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(100.0) + .value(1.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .startArray() + .value(101.0) + .value(0.0) + .endArray() + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .startArray() + .value(100.0) + .value(1.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(100.2) + .value(0.8) + .endArray() + .startArray() + .value(100.2) + .value(0.2) + .endArray() + .startArray() + .value(100.8) + .value(0.2) + .endArray() + .startArray() + .value(100.8) + .value(0.8) + .endArray() + .startArray() + .value(100.2) + .value(0.8) + .endArray() + .endArray() + .endArray() + .endObject(); + + // add 3d point to test ISSUE #10501 + List shellCoordinates = new ArrayList<>(); + shellCoordinates.add(new Coordinate(100, 0, 15.0)); + shellCoordinates.add(new Coordinate(101, 0)); + shellCoordinates.add(new Coordinate(101, 1)); + shellCoordinates.add(new Coordinate(100, 1, 10.0)); + shellCoordinates.add(new Coordinate(100, 0)); + + List holeCoordinates = new ArrayList<>(); + holeCoordinates.add(new Coordinate(100.2, 0.2)); + holeCoordinates.add(new Coordinate(100.8, 0.2)); + holeCoordinates.add(new Coordinate(100.8, 0.8)); + holeCoordinates.add(new Coordinate(100.2, 0.8)); + holeCoordinates.add(new Coordinate(100.2, 0.2)); + + LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + LinearRing[] holes = new LinearRing[1]; + holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); + Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes); + assertGeometryEquals(jtsGeom(expected), polygonGeoJson, true); + + org.elasticsearch.geometry.LinearRing hole = new org.elasticsearch.geometry.LinearRing( + new double[] { 100.8d, 100.8d, 100.2d, 100.2d, 100.8d }, + new double[] { 0.8d, 0.2d, 0.2d, 0.8d, 0.8d } + ); + org.elasticsearch.geometry.Polygon p = new org.elasticsearch.geometry.Polygon( + new org.elasticsearch.geometry.LinearRing(new double[] { 100d, 101d, 101d, 100d, 100d }, new double[] { 0d, 0d, 1d, 1d, 0d }), + Collections.singletonList(hole) + ); + assertGeometryEquals(p, polygonGeoJson, false); + } + + public void testParseSelfCrossingPolygon() throws IOException { + // test self crossing ccw poly not crossing dateline + String polygonGeoJson = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(-177.0) + .value(15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .endArray() + .endObject() + ); + + try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class); + assertNull(parser.nextToken()); + } + } + + @Override + public void testParseMultiPoint() throws IOException, ParseException { + XContentBuilder multiPointGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "MultiPoint") + .startArray("coordinates") + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .endArray() + .endObject(); + ShapeCollection expected = shapeCollection(SPATIAL_CONTEXT.makePoint(100, 0), SPATIAL_CONTEXT.makePoint(101, 1.0)); + assertGeometryEquals(expected, multiPointGeoJson, true); + + assertGeometryEquals( + new MultiPoint(Arrays.asList(new org.elasticsearch.geometry.Point(100, 0), new org.elasticsearch.geometry.Point(101, 1))), + multiPointGeoJson, + false + ); + } + + @Override + public void testParseMultiPolygon() throws IOException, ParseException { + // test #1: two polygons; one without hole, one with hole + XContentBuilder multiPolygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "MultiPolygon") + .startArray("coordinates") + .startArray()// first poly (without holes) + .startArray() + .startArray() + .value(102.0) + .value(2.0) + .endArray() + .startArray() + .value(103.0) + .value(2.0) + .endArray() + .startArray() + .value(103.0) + .value(3.0) + .endArray() + .startArray() + .value(102.0) + .value(3.0) + .endArray() + .startArray() + .value(102.0) + .value(2.0) + .endArray() + .endArray() + .endArray() + .startArray()// second poly (with hole) + .startArray() + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .startArray() + .value(101.0) + .value(0.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .startArray() + .value(100.0) + .value(1.0) + .endArray() + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .endArray() + .startArray()// hole + .startArray() + .value(100.2) + .value(0.8) + .endArray() + .startArray() + .value(100.2) + .value(0.2) + .endArray() + .startArray() + .value(100.8) + .value(0.2) + .endArray() + .startArray() + .value(100.8) + .value(0.8) + .endArray() + .startArray() + .value(100.2) + .value(0.8) + .endArray() + .endArray() + .endArray() + .endArray() + .endObject(); + + List shellCoordinates = new ArrayList<>(); + shellCoordinates.add(new Coordinate(100, 0)); + shellCoordinates.add(new Coordinate(101, 0)); + shellCoordinates.add(new Coordinate(101, 1)); + shellCoordinates.add(new Coordinate(100, 1)); + shellCoordinates.add(new Coordinate(100, 0)); + + List holeCoordinates = new ArrayList<>(); + holeCoordinates.add(new Coordinate(100.2, 0.2)); + holeCoordinates.add(new Coordinate(100.8, 0.2)); + holeCoordinates.add(new Coordinate(100.8, 0.8)); + holeCoordinates.add(new Coordinate(100.2, 0.8)); + holeCoordinates.add(new Coordinate(100.2, 0.2)); + + LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + LinearRing[] holes = new LinearRing[1]; + holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); + Polygon withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes); + + shellCoordinates = new ArrayList<>(); + shellCoordinates.add(new Coordinate(102, 3)); + shellCoordinates.add(new Coordinate(103, 3)); + shellCoordinates.add(new Coordinate(103, 2)); + shellCoordinates.add(new Coordinate(102, 2)); + shellCoordinates.add(new Coordinate(102, 3)); + + shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + Polygon withoutHoles = GEOMETRY_FACTORY.createPolygon(shell, null); + + Shape expected = shapeCollection(withoutHoles, withHoles); + + assertGeometryEquals(expected, multiPolygonGeoJson, true); + + org.elasticsearch.geometry.LinearRing hole = new org.elasticsearch.geometry.LinearRing( + new double[] { 100.8d, 100.8d, 100.2d, 100.2d, 100.8d }, + new double[] { 0.8d, 0.2d, 0.2d, 0.8d, 0.8d } + ); + + org.elasticsearch.geometry.MultiPolygon polygons = new org.elasticsearch.geometry.MultiPolygon( + Arrays.asList( + new org.elasticsearch.geometry.Polygon( + new org.elasticsearch.geometry.LinearRing( + new double[] { 103d, 103d, 102d, 102d, 103d }, + new double[] { 2d, 3d, 3d, 2d, 2d } + ) + ), + new org.elasticsearch.geometry.Polygon( + new org.elasticsearch.geometry.LinearRing( + new double[] { 101d, 101d, 100d, 100d, 101d }, + new double[] { 0d, 1d, 1d, 0d, 0d } + ), + Collections.singletonList(hole) + ) + ) + ); + + assertGeometryEquals(polygons, multiPolygonGeoJson, false); + + // test #2: multipolygon; one polygon with one hole + // this test converting the multipolygon from a ShapeCollection type + // to a simple polygon (jtsGeom) + multiPolygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "MultiPolygon") + .startArray("coordinates") + .startArray() + .startArray() + .startArray() + .value(100.0) + .value(1.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .startArray() + .value(101.0) + .value(0.0) + .endArray() + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .startArray() + .value(100.0) + .value(1.0) + .endArray() + .endArray() + .startArray() // hole + .startArray() + .value(100.2) + .value(0.8) + .endArray() + .startArray() + .value(100.2) + .value(0.2) + .endArray() + .startArray() + .value(100.8) + .value(0.2) + .endArray() + .startArray() + .value(100.8) + .value(0.8) + .endArray() + .startArray() + .value(100.2) + .value(0.8) + .endArray() + .endArray() + .endArray() + .endArray() + .endObject(); + + shellCoordinates = new ArrayList<>(); + shellCoordinates.add(new Coordinate(100, 1)); + shellCoordinates.add(new Coordinate(101, 1)); + shellCoordinates.add(new Coordinate(101, 0)); + shellCoordinates.add(new Coordinate(100, 0)); + shellCoordinates.add(new Coordinate(100, 1)); + + holeCoordinates = new ArrayList<>(); + holeCoordinates.add(new Coordinate(100.2, 0.8)); + holeCoordinates.add(new Coordinate(100.2, 0.2)); + holeCoordinates.add(new Coordinate(100.8, 0.2)); + holeCoordinates.add(new Coordinate(100.8, 0.8)); + holeCoordinates.add(new Coordinate(100.2, 0.8)); + + shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + holes = new LinearRing[1]; + holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); + withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes); + + assertGeometryEquals(jtsGeom(withHoles), multiPolygonGeoJson, true); + + org.elasticsearch.geometry.LinearRing luceneHole = new org.elasticsearch.geometry.LinearRing( + new double[] { 100.8d, 100.8d, 100.2d, 100.2d, 100.8d }, + new double[] { 0.8d, 0.2d, 0.2d, 0.8d, 0.8d } + ); + + org.elasticsearch.geometry.Polygon lucenePolygons = (new org.elasticsearch.geometry.Polygon( + new org.elasticsearch.geometry.LinearRing(new double[] { 100d, 101d, 101d, 100d, 100d }, new double[] { 0d, 0d, 1d, 1d, 0d }), + Collections.singletonList(luceneHole) + )); + assertGeometryEquals(lucenePolygons, multiPolygonGeoJson, false); + } + + @Override + public void testParseGeometryCollection() throws IOException, ParseException { + XContentBuilder geometryCollectionGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "GeometryCollection") + .startArray("geometries") + .startObject() + .field("type", "LineString") + .startArray("coordinates") + .startArray() + .value(100.0) + .value(0.0) + .endArray() + .startArray() + .value(101.0) + .value(1.0) + .endArray() + .endArray() + .endObject() + .startObject() + .field("type", "Point") + .startArray("coordinates") + .value(102.0) + .value(2.0) + .endArray() + .endObject() + .startObject() + .field("type", "Polygon") + .startArray("coordinates") + .startArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .endArray() + .endArray() + .endObject() + .endArray() + .endObject(); + + ArrayList shellCoordinates1 = new ArrayList<>(); + shellCoordinates1.add(new Coordinate(180.0, -12.142857142857142)); + shellCoordinates1.add(new Coordinate(180.0, 12.142857142857142)); + shellCoordinates1.add(new Coordinate(176.0, 15.0)); + shellCoordinates1.add(new Coordinate(172.0, 0.0)); + shellCoordinates1.add(new Coordinate(176.0, -15)); + shellCoordinates1.add(new Coordinate(180.0, -12.142857142857142)); + + ArrayList shellCoordinates2 = new ArrayList<>(); + shellCoordinates2.add(new Coordinate(-180.0, 12.142857142857142)); + shellCoordinates2.add(new Coordinate(-180.0, -12.142857142857142)); + shellCoordinates2.add(new Coordinate(-177.0, -10.0)); + shellCoordinates2.add(new Coordinate(-177.0, 10.0)); + shellCoordinates2.add(new Coordinate(-180.0, 12.142857142857142)); + + Shape[] expected = new Shape[3]; + LineString expectedLineString = GEOMETRY_FACTORY.createLineString( + new Coordinate[] { new Coordinate(100, 0), new Coordinate(101, 1), } + ); + expected[0] = jtsGeom(expectedLineString); + Point expectedPoint = GEOMETRY_FACTORY.createPoint(new Coordinate(102.0, 2.0)); + expected[1] = new JtsPoint(expectedPoint, SPATIAL_CONTEXT); + LinearRing shell1 = GEOMETRY_FACTORY.createLinearRing(shellCoordinates1.toArray(new Coordinate[shellCoordinates1.size()])); + LinearRing shell2 = GEOMETRY_FACTORY.createLinearRing(shellCoordinates2.toArray(new Coordinate[shellCoordinates2.size()])); + MultiPolygon expectedMultiPoly = GEOMETRY_FACTORY.createMultiPolygon( + new Polygon[] { GEOMETRY_FACTORY.createPolygon(shell1), GEOMETRY_FACTORY.createPolygon(shell2) } + ); + expected[2] = jtsGeom(expectedMultiPoly); + + // equals returns true only if geometries are in the same order + assertGeometryEquals(shapeCollection(expected), geometryCollectionGeoJson, true); + + GeometryCollection geometryExpected = new GeometryCollection<>( + Arrays.asList( + new Line(new double[] { 100d, 101d }, new double[] { 0d, 1d }), + new org.elasticsearch.geometry.Point(102d, 2d), + new org.elasticsearch.geometry.MultiPolygon( + Arrays.asList( + new org.elasticsearch.geometry.Polygon( + new org.elasticsearch.geometry.LinearRing( + new double[] { 180d, 180d, 176d, 172d, 176d, 180d }, + new double[] { -12.142857142857142d, 12.142857142857142d, 15d, 0d, -15d, -12.142857142857142d } + ) + ), + new org.elasticsearch.geometry.Polygon( + new org.elasticsearch.geometry.LinearRing( + new double[] { -180d, -180d, -177d, -177d, -180d }, + new double[] { 12.142857142857142d, -12.142857142857142d, -10d, 10d, 12.142857142857142d } + ) + ) + ) + ) + ) + ); + assertGeometryEquals(geometryExpected, geometryCollectionGeoJson, false); + } + + public void testThatParserExtractsCorrectTypeAndCoordinatesFromArbitraryJson() throws IOException, ParseException { + XContentBuilder pointGeoJson = XContentFactory.jsonBuilder() + .startObject() + .startObject("crs") + .field("type", "name") + .startObject("properties") + .field("name", "urn:ogc:def:crs:OGC:1.3:CRS84") + .endObject() + .endObject() + .field("bbox", "foobar") + .field("type", "point") + .field("bubu", "foobar") + .startArray("coordinates") + .value(100.0) + .value(0.0) + .endArray() + .startObject("nested") + .startArray("coordinates") + .value(200.0) + .value(0.0) + .endArray() + .endObject() + .startObject("lala") + .field("type", "NotAPoint") + .endObject() + .endObject(); + Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0)); + assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson, true); + + org.elasticsearch.geometry.Point expectedPt = new org.elasticsearch.geometry.Point(100, 0); + assertGeometryEquals(expectedPt, pointGeoJson, false); + } + + public void testParseOrientationOption() throws IOException, ParseException { + // test 1: valid ccw (right handed system) poly not crossing dateline (with 'right' field) + XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .field("orientation", "right") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(-172.0) + .value(8.0) + .endArray() + .startArray() + .value(174.0) + .value(10.0) + .endArray() + .startArray() + .value(-172.0) + .value(-8.0) + .endArray() + .startArray() + .value(-172.0) + .value(8.0) + .endArray() + .endArray() + .endArray() + .endObject(); + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertPolygon(shape, true); + } + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); + } + + // test 2: valid ccw (right handed system) poly not crossing dateline (with 'ccw' field) + polygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .field("orientation", "ccw") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(-172.0) + .value(8.0) + .endArray() + .startArray() + .value(174.0) + .value(10.0) + .endArray() + .startArray() + .value(-172.0) + .value(-8.0) + .endArray() + .startArray() + .value(-172.0) + .value(8.0) + .endArray() + .endArray() + .endArray() + .endObject(); + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertPolygon(shape, true); + } + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); + } + + // test 3: valid ccw (right handed system) poly not crossing dateline (with 'counterclockwise' field) + polygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .field("orientation", "counterclockwise") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(-172.0) + .value(8.0) + .endArray() + .startArray() + .value(174.0) + .value(10.0) + .endArray() + .startArray() + .value(-172.0) + .value(-8.0) + .endArray() + .startArray() + .value(-172.0) + .value(8.0) + .endArray() + .endArray() + .endArray() + .endObject(); + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertPolygon(shape, true); + } + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); + } + + // test 4: valid cw (left handed system) poly crossing dateline (with 'left' field) + polygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .field("orientation", "left") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(-178.0) + .value(8.0) + .endArray() + .startArray() + .value(178.0) + .value(8.0) + .endArray() + .startArray() + .value(180.0) + .value(-8.0) + .endArray() + .startArray() + .value(-178.0) + .value(8.0) + .endArray() + .endArray() + .endArray() + .endObject(); + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); + } + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); + } + + // test 5: valid cw multipoly (left handed system) poly crossing dateline (with 'cw' field) + polygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .field("orientation", "cw") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(-178.0) + .value(8.0) + .endArray() + .startArray() + .value(178.0) + .value(8.0) + .endArray() + .startArray() + .value(180.0) + .value(-8.0) + .endArray() + .startArray() + .value(-178.0) + .value(8.0) + .endArray() + .endArray() + .endArray() + .endObject(); + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); + } + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); + } + + // test 6: valid cw multipoly (left handed system) poly crossing dateline (with 'clockwise' field) + polygonGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Polygon") + .field("orientation", "clockwise") + .startArray("coordinates") + .startArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .startArray() + .value(-177.0) + .value(10.0) + .endArray() + .startArray() + .value(-177.0) + .value(-10.0) + .endArray() + .startArray() + .value(176.0) + .value(-15.0) + .endArray() + .startArray() + .value(172.0) + .value(0.0) + .endArray() + .startArray() + .value(176.0) + .value(15.0) + .endArray() + .endArray() + .startArray() + .startArray() + .value(-178.0) + .value(8.0) + .endArray() + .startArray() + .value(178.0) + .value(8.0) + .endArray() + .startArray() + .value(180.0) + .value(-8.0) + .endArray() + .startArray() + .value(-178.0) + .value(8.0) + .endArray() + .endArray() + .endArray() + .endObject(); + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + Shape shape = ShapeParser.parse(parser).buildS4J(); + ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); + } + + try (XContentParser parser = createParser(polygonGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); + } + } + + public void testParseInvalidShapes() throws IOException { + // single dimensions point + XContentBuilder tooLittlePointGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Point") + .startArray("coordinates") + .value(10.0) + .endArray() + .endObject(); + + try (XContentParser parser = createParser(tooLittlePointGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + + // zero dimensions point + XContentBuilder emptyPointGeoJson = XContentFactory.jsonBuilder() + .startObject() + .field("type", "Point") + .startObject("coordinates") + .field("foo", "bar") + .endObject() + .endObject(); + + try (XContentParser parser = createParser(emptyPointGeoJson)) { + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertNull(parser.nextToken()); + } + } + + public void testParseInvalidGeometryCollectionShapes() throws IOException { + // single dimensions point + XContentBuilder invalidPoints = XContentFactory.jsonBuilder() + .startObject() + .startObject("foo") + .field("type", "geometrycollection") + .startArray("geometries") + .startObject() + .field("type", "polygon") + .startArray("coordinates") + .startArray() + .value("46.6022226498514") + .value("24.7237442867977") + .endArray() + .startArray() + .value("46.6031857243798") + .value("24.722968774929") + .endArray() + .endArray() // coordinates + .endObject() + .endArray() // geometries + .endObject() + .endObject(); + try (XContentParser parser = createParser(invalidPoints)) { + parser.nextToken(); // foo + parser.nextToken(); // start object + parser.nextToken(); // start object + ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); + assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); // end of the document + assertNull(parser.nextToken()); // no more elements afterwards + } + } + + public Geometry parse(XContentParser parser) throws IOException, ParseException { + GeometryParser geometryParser = new GeometryParser(true, true, true); + GeoShapeIndexer indexer = new GeoShapeIndexer(true, "name"); + return indexer.prepareForIndexing(geometryParser.parse(parser)); + } +} diff --git a/server/src/test/java/org/elasticsearch/common/geo/GeoWKTShapeParserTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/GeoWKTShapeParserTests.java similarity index 83% rename from server/src/test/java/org/elasticsearch/common/geo/GeoWKTShapeParserTests.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/GeoWKTShapeParserTests.java index ecd2a9f3a8e8..7154abb0eef4 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/GeoWKTShapeParserTests.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/GeoWKTShapeParserTests.java @@ -5,24 +5,13 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -package org.elasticsearch.common.geo; +package org.elasticsearch.legacygeo; import org.apache.lucene.geo.GeoTestUtil; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.Version; -import org.elasticsearch.common.geo.builders.CoordinatesBuilder; -import org.elasticsearch.common.geo.builders.EnvelopeBuilder; -import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder; -import org.elasticsearch.common.geo.builders.LineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiLineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiPointBuilder; -import org.elasticsearch.common.geo.builders.MultiPolygonBuilder; -import org.elasticsearch.common.geo.builders.PointBuilder; -import org.elasticsearch.common.geo.builders.PolygonBuilder; -import org.elasticsearch.common.geo.builders.ShapeBuilder; -import org.elasticsearch.common.geo.parsers.GeoWKTParser; -import org.elasticsearch.common.geo.parsers.ShapeParser; +import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; @@ -33,9 +22,21 @@ import org.elasticsearch.geometry.MultiLine; import org.elasticsearch.geometry.MultiPoint; import org.elasticsearch.index.mapper.GeoShapeFieldMapper; import org.elasticsearch.index.mapper.GeoShapeIndexer; -import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper; import org.elasticsearch.index.mapper.MapperBuilderContext; -import org.elasticsearch.test.geo.RandomShapeGenerator; +import org.elasticsearch.legacygeo.builders.CoordinatesBuilder; +import org.elasticsearch.legacygeo.builders.EnvelopeBuilder; +import org.elasticsearch.legacygeo.builders.GeometryCollectionBuilder; +import org.elasticsearch.legacygeo.builders.LineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiLineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiPointBuilder; +import org.elasticsearch.legacygeo.builders.MultiPolygonBuilder; +import org.elasticsearch.legacygeo.builders.PointBuilder; +import org.elasticsearch.legacygeo.builders.PolygonBuilder; +import org.elasticsearch.legacygeo.builders.ShapeBuilder; +import org.elasticsearch.legacygeo.mapper.LegacyGeoShapeFieldMapper; +import org.elasticsearch.legacygeo.parsers.GeoWKTParser; +import org.elasticsearch.legacygeo.parsers.ShapeParser; +import org.elasticsearch.legacygeo.test.RandomShapeGenerator; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.LinearRing; @@ -55,7 +56,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import static org.elasticsearch.common.geo.builders.ShapeBuilder.SPATIAL_CONTEXT; +import static org.elasticsearch.legacygeo.builders.ShapeBuilder.SPATIAL_CONTEXT; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasToString; @@ -64,8 +65,7 @@ import static org.hamcrest.Matchers.hasToString; */ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { - private static XContentBuilder toWKTContent(ShapeBuilder builder, boolean generateMalformed) - throws IOException { + private static XContentBuilder toWKTContent(ShapeBuilder builder, boolean generateMalformed) throws IOException { String wkt = builder.toWKT(); if (generateMalformed) { // malformed - extra paren @@ -172,7 +172,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { MultiLineStringBuilder builder = new MultiLineStringBuilder(); for (int j = 0; j < numLineStrings; ++j) { List lsc = randomLineStringCoords(); - Coordinate [] coords = lsc.toArray(new Coordinate[lsc.size()]); + Coordinate[] coords = lsc.toArray(new Coordinate[lsc.size()]); lineStrings.add(GEOMETRY_FACTORY.createLineString(coords)); builder.linestring(new LineStringBuilder(lsc)); } @@ -180,8 +180,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { List lines = new ArrayList<>(lineStrings.size()); for (int j = 0; j < lineStrings.size(); ++j) { Coordinate[] c = lineStrings.get(j).getCoordinates(); - lines.add(new Line(Arrays.stream(c).mapToDouble(i->i.x).toArray(), Arrays.stream(c).mapToDouble(i->i.y).toArray() - )); + lines.add(new Line(Arrays.stream(c).mapToDouble(i -> i.x).toArray(), Arrays.stream(c).mapToDouble(i -> i.y).toArray())); } Geometry expectedGeom; if (lines.isEmpty()) { @@ -194,8 +193,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { assertExpected(expectedGeom, builder, false); assertMalformed(builder); - MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString( - lineStrings.toArray(new LineString[lineStrings.size()])); + MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString(lineStrings.toArray(new LineString[lineStrings.size()])); assumeTrue("JTS test path cannot handle empty multilinestrings", numLineStrings > 1); assertExpected(jtsGeom(expected), builder, true); } @@ -203,7 +201,8 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { @Override public void testParsePolygon() throws IOException, ParseException { PolygonBuilder builder = PolygonBuilder.class.cast( - RandomShapeGenerator.createShape(random(), RandomShapeGenerator.ShapeType.POLYGON)); + RandomShapeGenerator.createShape(random(), RandomShapeGenerator.ShapeType.POLYGON) + ); Coordinate[] coords = builder.coordinates()[0][0]; LinearRing shell = GEOMETRY_FACTORY.createLinearRing(coords); @@ -252,20 +251,20 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { PolygonBuilder polygonWithHole = new PolygonBuilder(new CoordinatesBuilder().coordinates(shellCoordinates)); polygonWithHole.hole(new LineStringBuilder(holeCoordinates)); - LinearRing shell = GEOMETRY_FACTORY.createLinearRing( - shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); LinearRing[] holes = new LinearRing[1]; - holes[0] = GEOMETRY_FACTORY.createLinearRing( - holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); + holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes); assertExpected(jtsGeom(expected), polygonWithHole, true); - org.elasticsearch.geometry.LinearRing hole = - new org.elasticsearch.geometry.LinearRing( - new double[] {100.2d, 100.8d, 100.8d, 100.2d, 100.2d}, new double[] {0.8d, 0.8d, 0.2d, 0.2d, 0.8d}); - org.elasticsearch.geometry.Polygon p = - new org.elasticsearch.geometry.Polygon(new org.elasticsearch.geometry.LinearRing( - new double[] {101d, 101d, 100d, 100d, 101d}, new double[] {0d, 1d, 1d, 0d, 0d}), Collections.singletonList(hole)); + org.elasticsearch.geometry.LinearRing hole = new org.elasticsearch.geometry.LinearRing( + new double[] { 100.2d, 100.8d, 100.8d, 100.2d, 100.2d }, + new double[] { 0.8d, 0.8d, 0.2d, 0.2d, 0.8d } + ); + org.elasticsearch.geometry.Polygon p = new org.elasticsearch.geometry.Polygon( + new org.elasticsearch.geometry.LinearRing(new double[] { 101d, 101d, 100d, 100d, 101d }, new double[] { 0d, 1d, 1d, 0d, 0d }), + Collections.singletonList(hole) + ); assertExpected(p, polygonWithHole, false); assertMalformed(polygonWithHole); } @@ -293,13 +292,11 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { XContentParser parser = createParser(xContentBuilder); parser.nextToken(); - final GeoShapeFieldMapper mapperBuilder = new GeoShapeFieldMapper.Builder("test", false, true) - .ignoreZValue(false) - .build(MapperBuilderContext.ROOT); + final GeoShapeFieldMapper mapperBuilder = new GeoShapeFieldMapper.Builder("test", false, true).ignoreZValue(false) + .build(MapperBuilderContext.ROOT); // test store z disabled - ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, - () -> ShapeParser.parse(parser, mapperBuilder)); + ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, () -> ShapeParser.parse(parser, mapperBuilder)); assertThat(e, hasToString(containsString("but [ignore_z_value] parameter is [false]"))); } @@ -326,13 +323,12 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { XContentParser parser = createParser(xContentBuilder); parser.nextToken(); - final LegacyGeoShapeFieldMapper mapperBuilder = - new LegacyGeoShapeFieldMapper.Builder("test", Version.V_6_3_0, false, true) - .build(MapperBuilderContext.ROOT); + final LegacyGeoShapeFieldMapper mapperBuilder = new LegacyGeoShapeFieldMapper.Builder("test", Version.V_6_3_0, false, true).build( + MapperBuilderContext.ROOT + ); // test store z disabled - ElasticsearchException e = expectThrows(ElasticsearchException.class, - () -> ShapeParser.parse(parser, mapperBuilder)); + ElasticsearchException e = expectThrows(ElasticsearchException.class, () -> ShapeParser.parse(parser, mapperBuilder)); assertThat(e, hasToString(containsString("unable to add coordinate to CoordinateBuilder: coordinate dimensions do not match"))); } @@ -350,9 +346,9 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { XContentParser parser = createParser(xContentBuilder); parser.nextToken(); - final LegacyGeoShapeFieldMapper mapperBuilder = - new LegacyGeoShapeFieldMapper.Builder("test", Version.V_6_3_0, false, true) - .build(MapperBuilderContext.ROOT); + final LegacyGeoShapeFieldMapper mapperBuilder = new LegacyGeoShapeFieldMapper.Builder("test", Version.V_6_3_0, false, true).build( + MapperBuilderContext.ROOT + ); ShapeBuilder shapeBuilder = ShapeParser.parse(parser, mapperBuilder); assertEquals(shapeBuilder.numDimensions(), 3); @@ -365,16 +361,18 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { XContentParser parser = createParser(xContentBuilder); parser.nextToken(); - final LegacyGeoShapeFieldMapper defaultMapperBuilder = - new LegacyGeoShapeFieldMapper.Builder("test", Version.V_6_3_0, false, true) - .coerce(false).build(MapperBuilderContext.ROOT); - ElasticsearchParseException exception = expectThrows(ElasticsearchParseException.class, - () -> ShapeParser.parse(parser, defaultMapperBuilder)); + final LegacyGeoShapeFieldMapper defaultMapperBuilder = new LegacyGeoShapeFieldMapper.Builder("test", Version.V_6_3_0, false, true) + .coerce(false) + .build(MapperBuilderContext.ROOT); + ElasticsearchParseException exception = expectThrows( + ElasticsearchParseException.class, + () -> ShapeParser.parse(parser, defaultMapperBuilder) + ); assertEquals("invalid LinearRing found (coordinates are not closed)", exception.getMessage()); - final LegacyGeoShapeFieldMapper coercingMapperBuilder = - new LegacyGeoShapeFieldMapper.Builder("test", Version.CURRENT, false, true) - .coerce(true).build(MapperBuilderContext.ROOT); + final LegacyGeoShapeFieldMapper coercingMapperBuilder = new LegacyGeoShapeFieldMapper.Builder("test", Version.CURRENT, false, true) + .coerce(true) + .build(MapperBuilderContext.ROOT); ShapeBuilder shapeBuilder = ShapeParser.parse(parser, coercingMapperBuilder); assertNotNull(shapeBuilder); assertEquals("polygon ((100.0 5.0, 100.0 10.0, 90.0 10.0, 90.0 5.0, 100.0 5.0))", shapeBuilder.toWKT()); @@ -441,8 +439,10 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase { XContentBuilder builder = toWKTContent(new PointBuilder(-1, 2), false); XContentParser parser = createParser(builder); parser.nextToken(); - ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, - () -> GeoWKTParser.parseExpectedType(parser, GeoShapeType.POLYGON)); + ElasticsearchParseException e = expectThrows( + ElasticsearchParseException.class, + () -> GeoWKTParser.parseExpectedType(parser, GeoShapeType.POLYGON) + ); assertThat(e, hasToString(containsString("Expected geometry type [polygon] but found [point]"))); } } diff --git a/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/GeometryIOTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/GeometryIOTests.java new file mode 100644 index 000000000000..2d484c73526a --- /dev/null +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/GeometryIOTests.java @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.legacygeo; + +import org.elasticsearch.common.geo.GeometryIO; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.geometry.Geometry; +import org.elasticsearch.geometry.GeometryCollection; +import org.elasticsearch.geometry.ShapeType; +import org.elasticsearch.legacygeo.builders.ShapeBuilder; +import org.elasticsearch.test.ESTestCase; + +import static org.elasticsearch.geo.GeometryTestUtils.randomGeometry; +import static org.elasticsearch.legacygeo.query.LegacyGeoShapeQueryProcessor.geometryToShapeBuilder; + +public class GeometryIOTests extends ESTestCase { + + public void testRandomSerialization() throws Exception { + for (int i = 0; i < randomIntBetween(1, 20); i++) { + boolean hasAlt = randomBoolean(); + Geometry geometry = randomGeometry(hasAlt); + if (shapeSupported(geometry) && randomBoolean()) { + // Shape builder conversion doesn't support altitude + ShapeBuilder shapeBuilder = geometryToShapeBuilder(geometry); + if (randomBoolean()) { + Geometry actual = shapeBuilder.buildGeometry(); + assertEquals(geometry, actual); + } + if (randomBoolean()) { + // Test ShapeBuilder -> Geometry Serialization + try (BytesStreamOutput out = new BytesStreamOutput()) { + out.writeNamedWriteable(shapeBuilder); + try (StreamInput in = out.bytes().streamInput()) { + Geometry actual = GeometryIO.readGeometry(in); + assertEquals(geometry, actual); + assertEquals(0, in.available()); + } + } + } else { + // Test Geometry -> ShapeBuilder Serialization + try (BytesStreamOutput out = new BytesStreamOutput()) { + GeometryIO.writeGeometry(out, geometry); + try (StreamInput in = out.bytes().streamInput()) { + try (StreamInput nin = new NamedWriteableAwareStreamInput(in, this.writableRegistry())) { + ShapeBuilder actual = nin.readNamedWriteable(ShapeBuilder.class); + assertEquals(shapeBuilder, actual); + assertEquals(0, in.available()); + } + } + } + } + // Test Geometry -> Geometry + try (BytesStreamOutput out = new BytesStreamOutput()) { + GeometryIO.writeGeometry(out, geometry); + ; + try (StreamInput in = out.bytes().streamInput()) { + Geometry actual = GeometryIO.readGeometry(in); + assertEquals(geometry, actual); + assertEquals(0, in.available()); + } + } + + } + } + } + + private boolean shapeSupported(Geometry geometry) { + if (geometry.hasZ()) { + return false; + } + + if (geometry.type() == ShapeType.GEOMETRYCOLLECTION) { + GeometryCollection collection = (GeometryCollection) geometry; + for (Geometry g : collection) { + if (shapeSupported(g) == false) { + return false; + } + } + } + return true; + } + + @Override + protected NamedWriteableRegistry writableRegistry() { + return new NamedWriteableRegistry(GeoShapeType.getShapeWriteables()); + } +} diff --git a/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/ShapeBuilderTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/ShapeBuilderTests.java new file mode 100644 index 000000000000..c2dec52d636a --- /dev/null +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/ShapeBuilderTests.java @@ -0,0 +1,805 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.legacygeo; + +import org.elasticsearch.geometry.LinearRing; +import org.elasticsearch.index.mapper.GeoShapeIndexer; +import org.elasticsearch.legacygeo.builders.CircleBuilder; +import org.elasticsearch.legacygeo.builders.CoordinatesBuilder; +import org.elasticsearch.legacygeo.builders.EnvelopeBuilder; +import org.elasticsearch.legacygeo.builders.LineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiLineStringBuilder; +import org.elasticsearch.legacygeo.builders.PointBuilder; +import org.elasticsearch.legacygeo.builders.PolygonBuilder; +import org.elasticsearch.legacygeo.builders.ShapeBuilder; +import org.elasticsearch.legacygeo.test.ElasticsearchGeoAssertions; +import org.elasticsearch.test.ESTestCase; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.spatial4j.exception.InvalidShapeException; +import org.locationtech.spatial4j.shape.Circle; +import org.locationtech.spatial4j.shape.Point; +import org.locationtech.spatial4j.shape.Rectangle; +import org.locationtech.spatial4j.shape.impl.PointImpl; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + +/** + * Tests for {@link ShapeBuilder} + */ +public class ShapeBuilderTests extends ESTestCase { + + public void testNewPoint() { + PointBuilder pb = new PointBuilder().coordinate(-100, 45); + Point point = pb.buildS4J(); + assertEquals(-100D, point.getX(), 0.0d); + assertEquals(45D, point.getY(), 0.0d); + org.elasticsearch.geometry.Point geoPoint = pb.buildGeometry(); + assertEquals(-100D, geoPoint.getX(), 0.0d); + assertEquals(45D, geoPoint.getY(), 0.0d); + } + + public void testNewRectangle() { + EnvelopeBuilder eb = new EnvelopeBuilder(new Coordinate(-45, 30), new Coordinate(45, -30)); + Rectangle rectangle = eb.buildS4J(); + assertEquals(-45D, rectangle.getMinX(), 0.0d); + assertEquals(-30D, rectangle.getMinY(), 0.0d); + assertEquals(45D, rectangle.getMaxX(), 0.0d); + assertEquals(30D, rectangle.getMaxY(), 0.0d); + + org.elasticsearch.geometry.Rectangle luceneRectangle = eb.buildGeometry(); + assertEquals(-45D, luceneRectangle.getMinX(), 0.0d); + assertEquals(-30D, luceneRectangle.getMinY(), 0.0d); + assertEquals(45D, luceneRectangle.getMaxX(), 0.0d); + assertEquals(30D, luceneRectangle.getMaxY(), 0.0d); + } + + public void testNewPolygon() { + PolygonBuilder pb = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-45, 30).coordinate(45, 30).coordinate(45, -30).coordinate(-45, -30).coordinate(-45, 30) + ); + + Polygon poly = pb.toPolygonS4J(); + LineString exterior = poly.getExteriorRing(); + assertEquals(exterior.getCoordinateN(0), new Coordinate(-45, 30)); + assertEquals(exterior.getCoordinateN(1), new Coordinate(45, 30)); + assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30)); + assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30)); + + LinearRing polygon = pb.toPolygonGeometry().getPolygon(); + assertEquals(polygon.getY(0), 30, 0d); + assertEquals(polygon.getX(0), -45, 0d); + assertEquals(polygon.getY(1), 30, 0d); + assertEquals(polygon.getX(1), 45, 0d); + assertEquals(polygon.getY(2), -30, 0d); + assertEquals(polygon.getX(2), 45, 0d); + assertEquals(polygon.getY(3), -30, 0d); + assertEquals(polygon.getX(3), -45, 0d); + } + + public void testNewPolygon_coordinate() { + PolygonBuilder pb = new PolygonBuilder( + new CoordinatesBuilder().coordinate(new Coordinate(-45, 30)) + .coordinate(new Coordinate(45, 30)) + .coordinate(new Coordinate(45, -30)) + .coordinate(new Coordinate(-45, -30)) + .coordinate(new Coordinate(-45, 30)) + ); + + Polygon poly = pb.toPolygonS4J(); + LineString exterior = poly.getExteriorRing(); + assertEquals(exterior.getCoordinateN(0), new Coordinate(-45, 30)); + assertEquals(exterior.getCoordinateN(1), new Coordinate(45, 30)); + assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30)); + assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30)); + + LinearRing polygon = pb.toPolygonGeometry().getPolygon(); + assertEquals(polygon.getY(0), 30, 0d); + assertEquals(polygon.getX(0), -45, 0d); + assertEquals(polygon.getY(1), 30, 0d); + assertEquals(polygon.getX(1), 45, 0d); + assertEquals(polygon.getY(2), -30, 0d); + assertEquals(polygon.getX(2), 45, 0d); + assertEquals(polygon.getY(3), -30, 0d); + assertEquals(polygon.getX(3), -45, 0d); + } + + public void testNewPolygon_coordinates() { + PolygonBuilder pb = new PolygonBuilder( + new CoordinatesBuilder().coordinates( + new Coordinate(-45, 30), + new Coordinate(45, 30), + new Coordinate(45, -30), + new Coordinate(-45, -30), + new Coordinate(-45, 30) + ) + ); + + Polygon poly = pb.toPolygonS4J(); + LineString exterior = poly.getExteriorRing(); + assertEquals(exterior.getCoordinateN(0), new Coordinate(-45, 30)); + assertEquals(exterior.getCoordinateN(1), new Coordinate(45, 30)); + assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30)); + assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30)); + + LinearRing polygon = pb.toPolygonGeometry().getPolygon(); + assertEquals(polygon.getY(0), 30, 0d); + assertEquals(polygon.getX(0), -45, 0d); + assertEquals(polygon.getY(1), 30, 0d); + assertEquals(polygon.getX(1), 45, 0d); + assertEquals(polygon.getY(2), -30, 0d); + assertEquals(polygon.getX(2), 45, 0d); + assertEquals(polygon.getY(3), -30, 0d); + assertEquals(polygon.getX(3), -45, 0d); + } + + public void testLineStringBuilder() { + // Building a simple LineString + LineStringBuilder lsb = new LineStringBuilder( + new CoordinatesBuilder().coordinate(-130.0, 55.0) + .coordinate(-130.0, -40.0) + .coordinate(-15.0, -40.0) + .coordinate(-20.0, 50.0) + .coordinate(-45.0, 50.0) + .coordinate(-45.0, -15.0) + .coordinate(-110.0, -15.0) + .coordinate(-110.0, 55.0) + ); + + lsb.buildS4J(); + buildGeometry(lsb); + + // Building a linestring that needs to be wrapped + lsb = new LineStringBuilder( + new CoordinatesBuilder().coordinate(100.0, 50.0) + .coordinate(110.0, -40.0) + .coordinate(240.0, -40.0) + .coordinate(230.0, 60.0) + .coordinate(200.0, 60.0) + .coordinate(200.0, -30.0) + .coordinate(130.0, -30.0) + .coordinate(130.0, 60.0) + ); + + lsb.buildS4J(); + buildGeometry(lsb); + + // Building a lineString on the dateline + lsb = new LineStringBuilder( + new CoordinatesBuilder().coordinate(-180.0, 80.0).coordinate(-180.0, 40.0).coordinate(-180.0, -40.0).coordinate(-180.0, -80.0) + ); + + lsb.buildS4J(); + buildGeometry(lsb); + + // Building a lineString on the dateline + lsb = new LineStringBuilder( + new CoordinatesBuilder().coordinate(180.0, 80.0).coordinate(180.0, 40.0).coordinate(180.0, -40.0).coordinate(180.0, -80.0) + ); + + lsb.buildS4J(); + buildGeometry(lsb); + } + + public void testMultiLineString() { + MultiLineStringBuilder mlsb = new MultiLineStringBuilder().linestring( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(-100.0, 50.0).coordinate(50.0, 50.0).coordinate(50.0, 20.0).coordinate(-100.0, 20.0) + ) + ) + .linestring( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(-100.0, 20.0).coordinate(50.0, 20.0).coordinate(50.0, 0.0).coordinate(-100.0, 0.0) + ) + ); + mlsb.buildS4J(); + buildGeometry(mlsb); + + // LineString that needs to be wrapped + new MultiLineStringBuilder().linestring( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(150.0, 60.0).coordinate(200.0, 60.0).coordinate(200.0, 40.0).coordinate(150.0, 40.0) + ) + ) + .linestring( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(150.0, 20.0).coordinate(200.0, 20.0).coordinate(200.0, 0.0).coordinate(150.0, 0.0) + ) + ); + + mlsb.buildS4J(); + buildGeometry(mlsb); + } + + public void testPolygonSelfIntersection() { + PolygonBuilder newPolygon = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-40.0, 50.0).coordinate(40.0, 50.0).coordinate(-40.0, -50.0).coordinate(40.0, -50.0).close() + ); + Exception e = expectThrows(InvalidShapeException.class, () -> newPolygon.buildS4J()); + assertThat(e.getMessage(), containsString("Cannot determine orientation: signed area equal to 0")); + } + + /** note: only supported by S4J at the moment */ + public void testGeoCircle() { + double earthCircumference = 40075016.69; + Circle circle = new CircleBuilder().center(0, 0).radius("100m").buildS4J(); + assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001); + assertEquals(new PointImpl(0, 0, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); + circle = new CircleBuilder().center(+180, 0).radius("100m").buildS4J(); + assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001); + assertEquals(new PointImpl(180, 0, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); + circle = new CircleBuilder().center(-180, 0).radius("100m").buildS4J(); + assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001); + assertEquals(new PointImpl(-180, 0, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); + circle = new CircleBuilder().center(0, 90).radius("100m").buildS4J(); + assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001); + assertEquals(new PointImpl(0, 90, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); + circle = new CircleBuilder().center(0, -90).radius("100m").buildS4J(); + assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001); + assertEquals(new PointImpl(0, -90, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); + double randomLat = (randomDouble() * 180) - 90; + double randomLon = (randomDouble() * 360) - 180; + double randomRadius = randomIntBetween(1, (int) earthCircumference / 4); + circle = new CircleBuilder().center(randomLon, randomLat).radius(randomRadius + "m").buildS4J(); + assertEquals((360 * randomRadius) / earthCircumference, circle.getRadius(), 0.00000001); + assertEquals(new PointImpl(randomLon, randomLat, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); + } + + public void testPolygonWrapping() { + PolygonBuilder pb = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-150.0, 65.0) + .coordinate(-250.0, 65.0) + .coordinate(-250.0, -65.0) + .coordinate(-150.0, -65.0) + .close() + ); + + ElasticsearchGeoAssertions.assertMultiPolygon(pb.buildS4J(), true); + ElasticsearchGeoAssertions.assertMultiPolygon(buildGeometry(pb), false); + } + + public void testLineStringWrapping() { + LineStringBuilder lsb = new LineStringBuilder( + new CoordinatesBuilder().coordinate(-150.0, 65.0) + .coordinate(-250.0, 65.0) + .coordinate(-250.0, -65.0) + .coordinate(-150.0, -65.0) + .close() + ); + + ElasticsearchGeoAssertions.assertMultiLineString(lsb.buildS4J(), true); + ElasticsearchGeoAssertions.assertMultiLineString(buildGeometry(lsb), false); + } + + public void testDatelineOGC() { + // tests that the following shape (defined in counterclockwise OGC order) + // https://gist.github.com/anonymous/7f1bb6d7e9cd72f5977c crosses the dateline + // expected results: 3 polygons, 1 with a hole + + // a giant c shape + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(174, 0) + .coordinate(-176, 0) + .coordinate(-176, 3) + .coordinate(177, 3) + .coordinate(177, 5) + .coordinate(-176, 5) + .coordinate(-176, 8) + .coordinate(174, 8) + .coordinate(174, 0) + ); + + // 3/4 of an embedded 'c', crossing dateline once + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(175, 1) + .coordinate(175, 7) + .coordinate(-178, 7) + .coordinate(-178, 6) + .coordinate(176, 6) + .coordinate(176, 2) + .coordinate(179, 2) + .coordinate(179, 1) + .coordinate(175, 1) + ) + ); + + // embedded hole right of the dateline + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(-179, 1).coordinate(-179, 2).coordinate(-177, 2).coordinate(-177, 1).coordinate(-179, 1) + ) + ); + + ElasticsearchGeoAssertions.assertMultiPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertMultiPolygon(buildGeometry(builder.close()), false); + } + + public void testDateline() { + // tests that the following shape (defined in clockwise non-OGC order) + // https://gist.github.com/anonymous/7f1bb6d7e9cd72f5977c crosses the dateline + // expected results: 3 polygons, 1 with a hole + + // a giant c shape + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-186, 0) + .coordinate(-176, 0) + .coordinate(-176, 3) + .coordinate(-183, 3) + .coordinate(-183, 5) + .coordinate(-176, 5) + .coordinate(-176, 8) + .coordinate(-186, 8) + .coordinate(-186, 0) + ); + + // 3/4 of an embedded 'c', crossing dateline once + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(-185, 1) + .coordinate(-181, 1) + .coordinate(-181, 2) + .coordinate(-184, 2) + .coordinate(-184, 6) + .coordinate(-178, 6) + .coordinate(-178, 7) + .coordinate(-185, 7) + .coordinate(-185, 1) + ) + ); + + // embedded hole right of the dateline + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(-179, 1).coordinate(-177, 1).coordinate(-177, 2).coordinate(-179, 2).coordinate(-179, 1) + ) + ); + + ElasticsearchGeoAssertions.assertMultiPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertMultiPolygon(buildGeometry(builder.close()), false); + } + + public void testComplexShapeWithHole() { + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-85.0018514, 37.1311314) + .coordinate(-85.0016645, 37.1315293) + .coordinate(-85.0016246, 37.1317069) + .coordinate(-85.0016526, 37.1318183) + .coordinate(-85.0017119, 37.1319196) + .coordinate(-85.0019371, 37.1321182) + .coordinate(-85.0019972, 37.1322115) + .coordinate(-85.0019942, 37.1323234) + .coordinate(-85.0019543, 37.1324336) + .coordinate(-85.001906, 37.1324985) + .coordinate(-85.001834, 37.1325497) + .coordinate(-85.0016965, 37.1325907) + .coordinate(-85.0016011, 37.1325873) + .coordinate(-85.0014816, 37.1325353) + .coordinate(-85.0011755, 37.1323509) + .coordinate(-85.000955, 37.1322802) + .coordinate(-85.0006241, 37.1322529) + .coordinate(-85.0000002, 37.1322307) + .coordinate(-84.9994, 37.1323001) + .coordinate(-84.999109, 37.1322864) + .coordinate(-84.998934, 37.1322415) + .coordinate(-84.9988639, 37.1321888) + .coordinate(-84.9987841, 37.1320944) + .coordinate(-84.9987208, 37.131954) + .coordinate(-84.998736, 37.1316611) + .coordinate(-84.9988091, 37.131334) + .coordinate(-84.9989283, 37.1311337) + .coordinate(-84.9991943, 37.1309198) + .coordinate(-84.9993573, 37.1308459) + .coordinate(-84.9995888, 37.1307924) + .coordinate(-84.9998746, 37.130806) + .coordinate(-85.0000002, 37.1308358) + .coordinate(-85.0004984, 37.1310658) + .coordinate(-85.0008008, 37.1311625) + .coordinate(-85.0009461, 37.1311684) + .coordinate(-85.0011373, 37.1311515) + .coordinate(-85.0016455, 37.1310491) + .coordinate(-85.0018514, 37.1311314) + ); + + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(-85.0000002, 37.1317672) + .coordinate(-85.0001983, 37.1317538) + .coordinate(-85.0003378, 37.1317582) + .coordinate(-85.0004697, 37.131792) + .coordinate(-85.0008048, 37.1319439) + .coordinate(-85.0009342, 37.1319838) + .coordinate(-85.0010184, 37.1319463) + .coordinate(-85.0010618, 37.13184) + .coordinate(-85.0010057, 37.1315102) + .coordinate(-85.000977, 37.1314403) + .coordinate(-85.0009182, 37.1313793) + .coordinate(-85.0005366, 37.1312209) + .coordinate(-85.000224, 37.1311466) + .coordinate(-85.000087, 37.1311356) + .coordinate(-85.0000002, 37.1311433) + .coordinate(-84.9995021, 37.1312336) + .coordinate(-84.9993308, 37.1312859) + .coordinate(-84.9992567, 37.1313252) + .coordinate(-84.9991868, 37.1314277) + .coordinate(-84.9991593, 37.1315381) + .coordinate(-84.9991841, 37.1316527) + .coordinate(-84.9992329, 37.1317117) + .coordinate(-84.9993527, 37.1317788) + .coordinate(-84.9994931, 37.1318061) + .coordinate(-84.9996815, 37.1317979) + .coordinate(-85.0000002, 37.1317672) + ) + ); + ElasticsearchGeoAssertions.assertPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertPolygon(buildGeometry(builder.close()), false); + } + + public void testShapeWithHoleAtEdgeEndPoints() { + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-4, 2) + .coordinate(4, 2) + .coordinate(6, 0) + .coordinate(4, -2) + .coordinate(-4, -2) + .coordinate(-6, 0) + .coordinate(-4, 2) + ); + + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(4, 1).coordinate(4, -1).coordinate(-4, -1).coordinate(-4, 1).coordinate(4, 1) + ) + ); + ElasticsearchGeoAssertions.assertPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertPolygon(buildGeometry(builder.close()), false); + } + + public void testShapeWithPointOnDateline() { + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(180, 0).coordinate(176, 4).coordinate(176, -4).coordinate(180, 0) + ); + ElasticsearchGeoAssertions.assertPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertPolygon(buildGeometry(builder.close()), false); + } + + public void testShapeWithEdgeAlongDateline() { + // test case 1: test the positive side of the dateline + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(180, 0).coordinate(176, 4).coordinate(180, -4).coordinate(180, 0) + ); + + ElasticsearchGeoAssertions.assertPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertPolygon(buildGeometry(builder.close()), false); + + // test case 2: test the negative side of the dateline + builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-176, 4).coordinate(-180, 0).coordinate(-180, -4).coordinate(-176, 4) + ); + + ElasticsearchGeoAssertions.assertPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertPolygon(buildGeometry(builder.close()), false); + } + + public void testShapeWithBoundaryHoles() { + // test case 1: test the positive side of the dateline + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-177, 10) + .coordinate(176, 15) + .coordinate(172, 0) + .coordinate(176, -15) + .coordinate(-177, -10) + .coordinate(-177, 10) + ); + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(176, 10).coordinate(180, 5).coordinate(180, -5).coordinate(176, -10).coordinate(176, 10) + ) + ); + + ElasticsearchGeoAssertions.assertMultiPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertMultiPolygon(buildGeometry(builder.close()), false); + + // test case 2: test the negative side of the dateline + builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-176, 15) + .coordinate(179, 10) + .coordinate(179, -10) + .coordinate(-176, -15) + .coordinate(-172, 0) + .close() + ); + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(-176, 10) + .coordinate(-176, -10) + .coordinate(-180, -5) + .coordinate(-180, 5) + .coordinate(-176, 10) + .close() + ) + ); + + ElasticsearchGeoAssertions.assertMultiPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertMultiPolygon(buildGeometry(builder.close()), false); + } + + public void testShapeWithHoleTouchingAtDateline() throws Exception { + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-180, 90) + .coordinate(-180, -90) + .coordinate(180, -90) + .coordinate(180, 90) + .coordinate(-180, 90) + ); + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(180.0, -16.14) + .coordinate(178.53, -16.64) + .coordinate(178.49, -16.82) + .coordinate(178.73, -17.02) + .coordinate(178.86, -16.86) + .coordinate(180.0, -16.14) + ) + ); + + ElasticsearchGeoAssertions.assertPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertPolygon(buildGeometry(builder.close()), false); + } + + public void testShapeWithTangentialHole() { + // test a shape with one tangential (shared) vertex (should pass) + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(179, 10) + .coordinate(168, 15) + .coordinate(164, 0) + .coordinate(166, -15) + .coordinate(179, -10) + .coordinate(179, 10) + ); + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(-177, 10) + .coordinate(-178, -10) + .coordinate(-180, -5) + .coordinate(-180, 5) + .coordinate(-177, 10) + ) + ); + + ElasticsearchGeoAssertions.assertMultiPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertMultiPolygon(buildGeometry(builder.close()), false); + } + + public void testShapeWithInvalidTangentialHole() { + // test a shape with one invalid tangential (shared) vertex (should throw exception) + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(179, 10) + .coordinate(168, 15) + .coordinate(164, 0) + .coordinate(166, -15) + .coordinate(179, -10) + .coordinate(179, 10) + ); + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(164, 0).coordinate(175, 10).coordinate(175, 5).coordinate(179, -10).coordinate(164, 0) + ) + ); + Exception e; + + e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J()); + assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); + e = expectThrows(IllegalArgumentException.class, () -> buildGeometry(builder.close())); + assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); + } + + public void testBoundaryShapeWithTangentialHole() { + // test a shape with one tangential (shared) vertex for each hole (should pass) + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-177, 10) + .coordinate(176, 15) + .coordinate(172, 0) + .coordinate(176, -15) + .coordinate(-177, -10) + .coordinate(-177, 10) + ); + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(-177, 10) + .coordinate(-178, -10) + .coordinate(-180, -5) + .coordinate(-180, 5) + .coordinate(-177, 10) + ) + ); + builder.hole( + new LineStringBuilder(new CoordinatesBuilder().coordinate(172, 0).coordinate(176, 10).coordinate(176, -5).coordinate(172, 0)) + ); + ElasticsearchGeoAssertions.assertMultiPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertMultiPolygon(buildGeometry(builder.close()), false); + } + + public void testBoundaryShapeWithInvalidTangentialHole() { + // test shape with two tangential (shared) vertices (should throw exception) + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-177, 10) + .coordinate(176, 15) + .coordinate(172, 0) + .coordinate(176, -15) + .coordinate(-177, -10) + .coordinate(-177, 10) + ); + builder.hole( + new LineStringBuilder( + new CoordinatesBuilder().coordinate(-177, 10) + .coordinate(172, 0) + .coordinate(180, -5) + .coordinate(176, -10) + .coordinate(-177, 10) + ) + ); + Exception e; + e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J()); + assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); + e = expectThrows(IllegalArgumentException.class, () -> buildGeometry(builder.close())); + assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); + } + + /** + * Test an enveloping polygon around the max mercator bounds + */ + public void testBoundaryShape() { + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(-180, 90).coordinate(180, 90).coordinate(180, -90).coordinate(-180, 90) + ); + + ElasticsearchGeoAssertions.assertPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertPolygon(buildGeometry(builder.close()), false); + } + + public void testShapeWithAlternateOrientation() { + // cw: should produce a multi polygon spanning hemispheres + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(180, 0).coordinate(176, 4).coordinate(-176, 4).coordinate(180, 0) + ); + + ElasticsearchGeoAssertions.assertPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertPolygon(buildGeometry(builder.close()), false); + + // cw: geo core will convert to ccw across the dateline + builder = new PolygonBuilder(new CoordinatesBuilder().coordinate(180, 0).coordinate(-176, 4).coordinate(176, 4).coordinate(180, 0)); + + ElasticsearchGeoAssertions.assertMultiPolygon(builder.close().buildS4J(), true); + ElasticsearchGeoAssertions.assertMultiPolygon(buildGeometry(builder.close()), false); + } + + public void testShapeWithConsecutiveDuplicatePoints() { + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(180, 0).coordinate(176, 4).coordinate(176, 4).coordinate(-176, 4).coordinate(180, 0) + ); + + // duplicated points are removed + PolygonBuilder expected = new PolygonBuilder( + new CoordinatesBuilder().coordinate(180, 0).coordinate(176, 4).coordinate(-176, 4).coordinate(180, 0) + ); + + assertEquals(buildGeometry(expected.close()), buildGeometry(builder.close())); + assertEquals(expected.close().buildS4J(), builder.close().buildS4J()); + } + + public void testShapeWithCoplanarVerticalPoints() throws Exception { + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(180, -36) + .coordinate(180, 90) + .coordinate(-180, 90) + .coordinate(-180, 79) + .coordinate(16, 58) + .coordinate(8, 13) + .coordinate(-180, 74) + .coordinate(-180, -85) + .coordinate(-180, -90) + .coordinate(180, -90) + .coordinate(180, -85) + .coordinate(26, 6) + .coordinate(33, 62) + .coordinate(180, -36) + ); + + // coplanar points on vertical edge are removed. + PolygonBuilder expected = new PolygonBuilder( + new CoordinatesBuilder().coordinate(180, -36) + .coordinate(180, 90) + .coordinate(-180, 90) + .coordinate(-180, 79) + .coordinate(16, 58) + .coordinate(8, 13) + .coordinate(-180, 74) + .coordinate(-180, -90) + .coordinate(180, -90) + .coordinate(180, -85) + .coordinate(26, 6) + .coordinate(33, 62) + .coordinate(180, -36) + ); + + assertEquals(buildGeometry(expected.close()), buildGeometry(builder.close())); + assertEquals(expected.close().buildS4J(), builder.close().buildS4J()); + + } + + public void testPolygon3D() { + String expected = "{\n" + + " \"type\" : \"polygon\",\n" + + " \"orientation\" : \"right\",\n" + + " \"coordinates\" : [\n" + + " [\n" + + " [\n" + + " -45.0,\n" + + " 30.0,\n" + + " 100.0\n" + + " ],\n" + + " [\n" + + " 45.0,\n" + + " 30.0,\n" + + " 75.0\n" + + " ],\n" + + " [\n" + + " 45.0,\n" + + " -30.0,\n" + + " 77.0\n" + + " ],\n" + + " [\n" + + " -45.0,\n" + + " -30.0,\n" + + " 101.0\n" + + " ],\n" + + " [\n" + + " -45.0,\n" + + " 30.0,\n" + + " 110.0\n" + + " ]\n" + + " ]\n" + + " ]\n" + + "}"; + + PolygonBuilder pb = new PolygonBuilder( + new CoordinatesBuilder().coordinate(new Coordinate(-45, 30, 100)) + .coordinate(new Coordinate(45, 30, 75)) + .coordinate(new Coordinate(45, -30, 77)) + .coordinate(new Coordinate(-45, -30, 101)) + .coordinate(new Coordinate(-45, 30, 110)) + ); + + assertEquals(expected, pb.toString()); + } + + public void testInvalidSelfCrossingPolygon() { + PolygonBuilder builder = new PolygonBuilder( + new CoordinatesBuilder().coordinate(0, 0) + .coordinate(0, 2) + .coordinate(1, 1.9) + .coordinate(0.5, 1.8) + .coordinate(1.5, 1.8) + .coordinate(1, 1.9) + .coordinate(2, 2) + .coordinate(2, 0) + .coordinate(0, 0) + ); + Exception e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J()); + assertThat(e.getMessage(), containsString("Self-intersection at or near point [")); + assertThat(e.getMessage(), not(containsString("NaN"))); + } + + public Object buildGeometry(ShapeBuilder builder) { + return new GeoShapeIndexer(true, "name").prepareForIndexing(builder.buildGeometry()); + } +} diff --git a/server/src/test/java/org/elasticsearch/common/geo/builders/AbstractShapeBuilderTestCase.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/AbstractShapeBuilderTestCase.java similarity index 95% rename from server/src/test/java/org/elasticsearch/common/geo/builders/AbstractShapeBuilderTestCase.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/AbstractShapeBuilderTestCase.java index 1ff958faf947..c55e2120e639 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/builders/AbstractShapeBuilderTestCase.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/AbstractShapeBuilderTestCase.java @@ -6,10 +6,8 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; +package org.elasticsearch.legacygeo.builders; -import org.elasticsearch.common.geo.GeoShapeType; -import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.io.stream.NamedWriteable; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.Writeable.Reader; @@ -18,6 +16,8 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.legacygeo.GeoShapeType; +import org.elasticsearch.legacygeo.parsers.ShapeParser; import org.elasticsearch.test.ESTestCase; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -26,7 +26,7 @@ import java.io.IOException; import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode; -public abstract class AbstractShapeBuilderTestCase> extends ESTestCase { +public abstract class AbstractShapeBuilderTestCase> extends ESTestCase { private static final int NUMBER_OF_TESTBUILDERS = 20; private static NamedWriteableRegistry namedWriteableRegistry; diff --git a/server/src/test/java/org/elasticsearch/common/geo/builders/CircleBuilderTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/CircleBuilderTests.java similarity index 95% rename from server/src/test/java/org/elasticsearch/common/geo/builders/CircleBuilderTests.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/CircleBuilderTests.java index c2323f4a1be1..72ab85016aac 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/builders/CircleBuilderTests.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/CircleBuilderTests.java @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; +package org.elasticsearch.legacygeo.builders; import org.elasticsearch.common.unit.DistanceUnit; import org.locationtech.jts.geom.Coordinate; @@ -32,14 +32,14 @@ public class CircleBuilderTests extends AbstractShapeBuilderTestCase 0.0 || original.center().y > 0.0) { - mutation.center(new Coordinate(original.center().x/2, original.center().y/2)); + mutation.center(new Coordinate(original.center().x / 2, original.center().y / 2)); } else { // original center was 0.0, 0.0 mutation.center(randomDouble() + 0.1, randomDouble() + 0.1); } } else if (randomBoolean()) { if (radius > 0) { - radius = radius/2; + radius = radius / 2; } else { radius = randomDouble() + 0.1; } diff --git a/server/src/test/java/org/elasticsearch/common/geo/builders/EnvelopeBuilderTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/EnvelopeBuilderTests.java similarity index 64% rename from server/src/test/java/org/elasticsearch/common/geo/builders/EnvelopeBuilderTests.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/EnvelopeBuilderTests.java index 8944d90ec82c..65b4dcefca26 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/builders/EnvelopeBuilderTests.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/EnvelopeBuilderTests.java @@ -6,11 +6,10 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; +package org.elasticsearch.legacygeo.builders; +import org.elasticsearch.legacygeo.test.RandomShapeGenerator; import org.locationtech.jts.geom.Coordinate; - -import org.elasticsearch.test.geo.RandomShapeGenerator; import org.locationtech.spatial4j.shape.Rectangle; import java.io.IOException; @@ -39,31 +38,40 @@ public class EnvelopeBuilderTests extends AbstractShapeBuilderTestCase { + + @Override + protected GeometryCollectionBuilder createTestShapeBuilder() { + GeometryCollectionBuilder geometryCollection = new GeometryCollectionBuilder(); + int shapes = randomIntBetween(0, 8); + for (int i = 0; i < shapes; i++) { + switch (randomIntBetween(0, 7)) { + case 0: + geometryCollection.shape(PointBuilderTests.createRandomShape()); + break; + case 1: + geometryCollection.shape(CircleBuilderTests.createRandomShape()); + break; + case 2: + geometryCollection.shape(EnvelopeBuilderTests.createRandomShape()); + break; + case 3: + geometryCollection.shape(LineStringBuilderTests.createRandomShape()); + break; + case 4: + geometryCollection.shape(MultiLineStringBuilderTests.createRandomShape()); + break; + case 5: + geometryCollection.shape(MultiPolygonBuilderTests.createRandomShape()); + break; + case 6: + geometryCollection.shape(MultiPointBuilderTests.createRandomShape()); + break; + case 7: + geometryCollection.shape(PolygonBuilderTests.createRandomShape()); + break; + } + } + return geometryCollection; + } + + @Override + protected GeometryCollectionBuilder createMutation(GeometryCollectionBuilder original) throws IOException { + return mutate(original); + } + + static GeometryCollectionBuilder mutate(GeometryCollectionBuilder original) throws IOException { + GeometryCollectionBuilder mutation = copyShape(original); + if (mutation.shapes.size() > 0) { + int shapePosition = randomIntBetween(0, mutation.shapes.size() - 1); + ShapeBuilder shapeToChange = mutation.shapes.get(shapePosition); + switch (shapeToChange.type()) { + case POINT: + shapeToChange = PointBuilderTests.mutate((PointBuilder) shapeToChange); + break; + case CIRCLE: + shapeToChange = CircleBuilderTests.mutate((CircleBuilder) shapeToChange); + break; + case ENVELOPE: + shapeToChange = EnvelopeBuilderTests.mutate((EnvelopeBuilder) shapeToChange); + break; + case LINESTRING: + shapeToChange = LineStringBuilderTests.mutate((LineStringBuilder) shapeToChange); + break; + case MULTILINESTRING: + shapeToChange = MultiLineStringBuilderTests.mutate((MultiLineStringBuilder) shapeToChange); + break; + case MULTIPOLYGON: + shapeToChange = MultiPolygonBuilderTests.mutate((MultiPolygonBuilder) shapeToChange); + break; + case MULTIPOINT: + shapeToChange = MultiPointBuilderTests.mutate((MultiPointBuilder) shapeToChange); + break; + case POLYGON: + shapeToChange = PolygonBuilderTests.mutate((PolygonBuilder) shapeToChange); + break; + case GEOMETRYCOLLECTION: + throw new UnsupportedOperationException("GeometryCollection should not be nested inside each other"); + } + mutation.shapes.set(shapePosition, shapeToChange); + } else { + mutation.shape(RandomShapeGenerator.createShape(random())); + } + return mutation; + } +} diff --git a/server/src/test/java/org/elasticsearch/common/geo/builders/LineStringBuilderTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/LineStringBuilderTests.java similarity index 93% rename from server/src/test/java/org/elasticsearch/common/geo/builders/LineStringBuilderTests.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/LineStringBuilderTests.java index f2b9d153337f..86af9ece0cf1 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/builders/LineStringBuilderTests.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/LineStringBuilderTests.java @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; +package org.elasticsearch.legacygeo.builders; -import org.elasticsearch.test.geo.RandomShapeGenerator; -import org.elasticsearch.test.geo.RandomShapeGenerator.ShapeType; +import org.elasticsearch.legacygeo.test.RandomShapeGenerator; +import org.elasticsearch.legacygeo.test.RandomShapeGenerator.ShapeType; import org.locationtech.jts.geom.Coordinate; import java.io.IOException; diff --git a/server/src/test/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilderTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/MultiLineStringBuilderTests.java similarity index 92% rename from server/src/test/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilderTests.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/MultiLineStringBuilderTests.java index 97e3c766e8ee..a222cfe9e6b4 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilderTests.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/MultiLineStringBuilderTests.java @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; +package org.elasticsearch.legacygeo.builders; +import org.elasticsearch.legacygeo.test.RandomShapeGenerator; +import org.elasticsearch.legacygeo.test.RandomShapeGenerator.ShapeType; import org.locationtech.jts.geom.Coordinate; -import org.elasticsearch.test.geo.RandomShapeGenerator; -import org.elasticsearch.test.geo.RandomShapeGenerator.ShapeType; import java.io.IOException; diff --git a/server/src/test/java/org/elasticsearch/common/geo/builders/MultiPointBuilderTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/MultiPointBuilderTests.java similarity index 90% rename from server/src/test/java/org/elasticsearch/common/geo/builders/MultiPointBuilderTests.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/MultiPointBuilderTests.java index e67c3fc916a3..d6484c42c625 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/builders/MultiPointBuilderTests.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/builders/MultiPointBuilderTests.java @@ -6,13 +6,12 @@ * Side Public License, v 1. */ -package org.elasticsearch.common.geo.builders; +package org.elasticsearch.legacygeo.builders; +import org.elasticsearch.legacygeo.test.RandomShapeGenerator; +import org.elasticsearch.legacygeo.test.RandomShapeGenerator.ShapeType; import org.locationtech.jts.geom.Coordinate; -import org.elasticsearch.test.geo.RandomShapeGenerator; -import org.elasticsearch.test.geo.RandomShapeGenerator.ShapeType; - import java.io.IOException; import java.util.List; @@ -57,7 +56,7 @@ public class MultiPointBuilderTests extends AbstractShapeBuilderTestCase= 4)", e.getMessage()); } - PolygonBuilder pb = new PolygonBuilder(new LineStringBuilder(new CoordinatesBuilder().coordinate(0.0, 0.0) - .coordinate(1.0, 0.0).coordinate(1.0, 1.0).build()), Orientation.RIGHT, true); + PolygonBuilder pb = new PolygonBuilder( + new LineStringBuilder(new CoordinatesBuilder().coordinate(0.0, 0.0).coordinate(1.0, 0.0).coordinate(1.0, 1.0).build()), + Orientation.RIGHT, + true + ); assertThat("Shell should have been closed via coerce", pb.shell().coordinates(false).length, equalTo(4)); } public void testCoerceHole() { - PolygonBuilder pb = new PolygonBuilder(new CoordinatesBuilder().coordinate(0.0, 0.0) - .coordinate(2.0, 0.0).coordinate(2.0, 2.0).coordinate(0.0, 0.0)); - try{ - pb.hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(0.0,0.0).coordinate(1.0,0.0).coordinate(1.0,1.0).build())); + PolygonBuilder pb = new PolygonBuilder( + new CoordinatesBuilder().coordinate(0.0, 0.0).coordinate(2.0, 0.0).coordinate(2.0, 2.0).coordinate(0.0, 0.0) + ); + try { + pb.hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(0.0, 0.0).coordinate(1.0, 0.0).coordinate(1.0, 1.0).build())); fail("should raise validation exception"); } catch (IllegalArgumentException e) { assertEquals("invalid number of points in LinearRing (found [3] - must be >= 4)", e.getMessage()); } - pb.hole(new LineStringBuilder(new CoordinatesBuilder().coordinate(0.0,0.0).coordinate(1.0,0.0).coordinate(1.0,1.0).build()), true); + pb.hole( + new LineStringBuilder(new CoordinatesBuilder().coordinate(0.0, 0.0).coordinate(1.0, 0.0).coordinate(1.0, 1.0).build()), + true + ); assertThat("hole should have been closed via coerce", pb.holes().get(0).coordinates(false).length, equalTo(4)); } @@ -140,26 +151,43 @@ public class PolygonBuilderTests extends AbstractShapeBuilderTestCase { - b.field("type", "geo_shape"); - b.field("strategy", "term"); - })); + checker.registerConflictCheck("strategy", fieldMapping(this::minimalMapping), fieldMapping(b -> { + b.field("type", "geo_shape"); + b.field("strategy", "term"); + })); checker.registerConflictCheck("tree", b -> b.field("tree", "geohash")); checker.registerConflictCheck("tree_levels", b -> b.field("tree_levels", 5)); @@ -87,7 +92,7 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { }); checker.registerUpdateCheck(b -> b.field("coerce", true), m -> { LegacyGeoShapeFieldMapper gpfm = (LegacyGeoShapeFieldMapper) m; - assertTrue(gpfm.coerce.value()); + assertTrue(gpfm.coerce()); }); // TODO - distance_error_pct ends up being subsumed into a calculated value, how to test checker.registerUpdateCheck(b -> b.field("distance_error_pct", 0.8), m -> {}); @@ -133,15 +138,11 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { assertEquals(Strings.toString(mapping), mapper.mappingSource().toString()); LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper; - assertThat(geoShapeFieldMapper.fieldType().tree(), - equalTo(LegacyGeoShapeFieldMapper.Defaults.TREE)); + assertThat(geoShapeFieldMapper.fieldType().tree(), equalTo(LegacyGeoShapeFieldMapper.Defaults.TREE)); assertThat(geoShapeFieldMapper.fieldType().treeLevels(), equalTo(0)); - assertThat(geoShapeFieldMapper.fieldType().pointsOnly(), - equalTo(LegacyGeoShapeFieldMapper.Defaults.POINTS_ONLY)); - assertThat(geoShapeFieldMapper.fieldType().distanceErrorPct(), - equalTo(LegacyGeoShapeFieldMapper.Defaults.DISTANCE_ERROR_PCT)); - assertThat(geoShapeFieldMapper.fieldType().orientation(), - equalTo(Orientation.RIGHT)); + assertThat(geoShapeFieldMapper.fieldType().pointsOnly(), equalTo(LegacyGeoShapeFieldMapper.Defaults.POINTS_ONLY)); + assertThat(geoShapeFieldMapper.fieldType().distanceErrorPct(), equalTo(LegacyGeoShapeFieldMapper.Defaults.DISTANCE_ERROR_PCT)); + assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(Orientation.RIGHT)); assertFieldWarnings("strategy"); } @@ -154,7 +155,7 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { ); Mapper fieldMapper = mapper.mappers().getMapper("field"); assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class)); - Orientation orientation = ((LegacyGeoShapeFieldMapper)fieldMapper).fieldType().orientation(); + Orientation orientation = ((LegacyGeoShapeFieldMapper) fieldMapper).fieldType().orientation(); assertThat(orientation, equalTo(Orientation.CLOCKWISE)); assertThat(orientation, equalTo(Orientation.LEFT)); assertThat(orientation, equalTo(Orientation.CW)); @@ -165,7 +166,7 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { ); fieldMapper = mapper.mappers().getMapper("field"); assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class)); - orientation = ((LegacyGeoShapeFieldMapper)fieldMapper).fieldType().orientation(); + orientation = ((LegacyGeoShapeFieldMapper) fieldMapper).fieldType().orientation(); assertThat(orientation, equalTo(Orientation.COUNTER_CLOCKWISE)); assertThat(orientation, equalTo(Orientation.RIGHT)); assertThat(orientation, equalTo(Orientation.CCW)); @@ -181,16 +182,14 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { ); Mapper fieldMapper = mapper.mappers().getMapper("field"); assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class)); - boolean coerce = ((LegacyGeoShapeFieldMapper)fieldMapper).coerce(); + boolean coerce = ((LegacyGeoShapeFieldMapper) fieldMapper).coerce(); assertThat(coerce, equalTo(true)); // explicit false coerce test - mapper = createDocumentMapper( - fieldMapping(b -> b.field("type", "geo_shape").field("tree", "quadtree").field("coerce", false)) - ); + mapper = createDocumentMapper(fieldMapping(b -> b.field("type", "geo_shape").field("tree", "quadtree").field("coerce", false))); fieldMapper = mapper.mappers().getMapper("field"); assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class)); - coerce = ((LegacyGeoShapeFieldMapper)fieldMapper).coerce(); + coerce = ((LegacyGeoShapeFieldMapper) fieldMapper).coerce(); assertThat(coerce, equalTo(false)); assertFieldWarnings("tree", "strategy"); } @@ -204,7 +203,7 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { ); Mapper fieldMapper = mapper.mappers().getMapper("field"); assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class)); - boolean ignoreZValue = ((LegacyGeoShapeFieldMapper)fieldMapper).ignoreZValue(); + boolean ignoreZValue = ((LegacyGeoShapeFieldMapper) fieldMapper).ignoreZValue(); assertThat(ignoreZValue, equalTo(true)); // explicit false accept_z_value test @@ -213,7 +212,7 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { ); fieldMapper = mapper.mappers().getMapper("field"); assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class)); - ignoreZValue = ((LegacyGeoShapeFieldMapper)fieldMapper).ignoreZValue(); + ignoreZValue = ((LegacyGeoShapeFieldMapper) fieldMapper).ignoreZValue(); assertThat(ignoreZValue, equalTo(false)); assertFieldWarnings("strategy", "tree"); } @@ -227,7 +226,7 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { ); Mapper fieldMapper = mapper.mappers().getMapper("field"); assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class)); - boolean ignoreMalformed = ((LegacyGeoShapeFieldMapper)fieldMapper).ignoreMalformed(); + boolean ignoreMalformed = ((LegacyGeoShapeFieldMapper) fieldMapper).ignoreMalformed(); assertThat(ignoreMalformed, equalTo(true)); // explicit false ignore_malformed test @@ -236,7 +235,7 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { ); fieldMapper = mapper.mappers().getMapper("field"); assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class)); - ignoreMalformed = ((LegacyGeoShapeFieldMapper)fieldMapper).ignoreMalformed(); + ignoreMalformed = ((LegacyGeoShapeFieldMapper) fieldMapper).ignoreMalformed(); assertThat(ignoreMalformed, equalTo(false)); assertFieldWarnings("tree", "strategy"); } @@ -465,11 +464,21 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper; assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(Orientation.CCW)); - Exception e = expectThrows(IllegalArgumentException.class, () -> merge(mapperService, fieldMapping(b -> b.field("type", "geo_shape") - .field("tree", "quadtree") - .field("strategy", "term").field("precision", "1km") - .field("tree_levels", 26).field("distance_error_pct", 26) - .field("orientation", "cw")))); + Exception e = expectThrows( + IllegalArgumentException.class, + () -> merge( + mapperService, + fieldMapping( + b -> b.field("type", "geo_shape") + .field("tree", "quadtree") + .field("strategy", "term") + .field("precision", "1km") + .field("tree_levels", 26) + .field("distance_error_pct", 26) + .field("orientation", "cw") + ) + ) + ); assertThat(e.getMessage(), containsString("Cannot update parameter [strategy] from [recursive] to [term]")); assertThat(e.getMessage(), containsString("Cannot update parameter [tree] from [geohash] to [quadtree]")); assertThat(e.getMessage(), containsString("Cannot update parameter [tree_levels] from [8] to [26]")); @@ -488,12 +497,18 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(Orientation.CCW)); // correct mapping - merge(mapperService, fieldMapping(b -> b.field("type", "geo_shape") - .field("tree", "geohash") - .field("strategy", "recursive") - .field("precision", "1m") - .field("tree_levels", 8).field("distance_error_pct", 0.001) - .field("orientation", "cw"))); + merge( + mapperService, + fieldMapping( + b -> b.field("type", "geo_shape") + .field("tree", "geohash") + .field("strategy", "recursive") + .field("precision", "1m") + .field("tree_levels", 8) + .field("distance_error_pct", 0.001) + .field("orientation", "cw") + ) + ); fieldMapper = mapperService.documentMapper().mappers().getMapper("field"); assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class)); @@ -594,29 +609,32 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class)); LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper; - ElasticsearchException e = expectThrows(ElasticsearchException.class, - () -> geoShapeFieldMapper.fieldType().geoShapeQuery( - new Point(-10, 10), "location", SpatialStrategy.TERM, ShapeRelation.INTERSECTS, searchExecutionContext)); - assertEquals("[geo-shape] queries on [PrefixTree geo shapes] cannot be executed when " + - "'search.allow_expensive_queries' is set to false.", e.getMessage()); + ElasticsearchException e = expectThrows( + ElasticsearchException.class, + () -> geoShapeFieldMapper.fieldType() + .geoShapeQuery(new Point(-10, 10), "location", SpatialStrategy.TERM, ShapeRelation.INTERSECTS, searchExecutionContext) + ); + assertEquals( + "[geo-shape] queries on [PrefixTree geo shapes] cannot be executed when " + "'search.allow_expensive_queries' is set to false.", + e.getMessage() + ); assertFieldWarnings("tree", "strategy"); } @Override protected String[] getParseMinimalWarnings() { - return new String[]{"Parameter [strategy] is deprecated and will be removed in a future version"}; + return new String[] { "Parameter [strategy] is deprecated and will be removed in a future version" }; } @Override protected String[] getParseMaximalWarnings() { - return new String[]{ + return new String[] { "Parameter [strategy] is deprecated and will be removed in a future version", "Parameter [tree] is deprecated and will be removed in a future version", "Parameter [tree_levels] is deprecated and will be removed in a future version", "Parameter [precision] is deprecated and will be removed in a future version", "Parameter [distance_error_pct] is deprecated and will be removed in a future version", - "Parameter [points_only] is deprecated and will be removed in a future version" - }; + "Parameter [points_only] is deprecated and will be removed in a future version" }; } public void testGeoShapeArrayParsing() throws Exception { @@ -636,7 +654,7 @@ public class LegacyGeoShapeFieldMapperTests extends MapperTestCase { } protected void assertSearchable(MappedFieldType fieldType) { - //always searchable even if it uses TextSearchInfo.NONE + // always searchable even if it uses TextSearchInfo.NONE assertTrue(fieldType.isSearchable()); } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/LegacyGeoShapeFieldTypeTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/mapper/LegacyGeoShapeFieldTypeTests.java similarity index 86% rename from server/src/test/java/org/elasticsearch/index/mapper/LegacyGeoShapeFieldTypeTests.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/mapper/LegacyGeoShapeFieldTypeTests.java index 6da047abca08..71dd9e3ebd4e 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/LegacyGeoShapeFieldTypeTests.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/mapper/LegacyGeoShapeFieldTypeTests.java @@ -5,11 +5,14 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -package org.elasticsearch.index.mapper; +package org.elasticsearch.legacygeo.mapper; import org.elasticsearch.Version; import org.elasticsearch.common.geo.SpatialStrategy; -import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper.GeoShapeFieldType; +import org.elasticsearch.index.mapper.FieldTypeTestCase; +import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.index.mapper.MapperBuilderContext; +import org.elasticsearch.legacygeo.mapper.LegacyGeoShapeFieldMapper.GeoShapeFieldType; import java.io.IOException; import java.util.Arrays; @@ -32,18 +35,18 @@ public class LegacyGeoShapeFieldTypeTests extends FieldTypeTestCase { } public void testFetchSourceValue() throws IOException { + MappedFieldType mapper = new LegacyGeoShapeFieldMapper.Builder("field", Version.CURRENT, false, true).build( + MapperBuilderContext.ROOT + ).fieldType(); - MappedFieldType mapper = new LegacyGeoShapeFieldMapper.Builder("field", Version.CURRENT, false, true) - .build(MapperBuilderContext.ROOT).fieldType(); - - Map jsonLineString = org.elasticsearch.core.Map.of("type", "LineString", "coordinates", - Arrays.asList(Arrays.asList(42.0, 27.1), Arrays.asList(30.0, 50.0))); - Map jsonPoint = org.elasticsearch.core.Map.of( - "type", "Point", - "coordinates", Arrays.asList(14.0, 15.0)); - Map jsonMalformed = org.elasticsearch.core.Map.of( - "type", "LineString", - "coordinates", "foo"); + Map jsonLineString = org.elasticsearch.core.Map.of( + "type", + "LineString", + "coordinates", + Arrays.asList(Arrays.asList(42.0, 27.1), Arrays.asList(30.0, 50.0)) + ); + Map jsonPoint = org.elasticsearch.core.Map.of("type", "Point", "coordinates", Arrays.asList(14.0, 15.0)); + Map jsonMalformed = org.elasticsearch.core.Map.of("type", "LineString", "coordinates", "foo"); String wktLineString = "LINESTRING (42.0 27.1, 30.0 50.0)"; String wktPoint = "POINT (14.0 15.0)"; String wktMalformed = "POINT foo"; diff --git a/server/src/test/java/org/elasticsearch/search/geo/LegacyGeoShapeQueryTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/search/LegacyGeoShapeQueryTests.java similarity index 62% rename from server/src/test/java/org/elasticsearch/search/geo/LegacyGeoShapeQueryTests.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/search/LegacyGeoShapeQueryTests.java index a514723886ab..4dbd29481752 100644 --- a/server/src/test/java/org/elasticsearch/search/geo/LegacyGeoShapeQueryTests.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/search/LegacyGeoShapeQueryTests.java @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -package org.elasticsearch.search.geo; +package org.elasticsearch.legacygeo.search; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.Strings; @@ -19,10 +19,11 @@ import org.elasticsearch.geo.GeometryTestUtils; import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.MultiPoint; import org.elasticsearch.geometry.Point; -import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper; import org.elasticsearch.index.mapper.MapperParsingException; +import org.elasticsearch.legacygeo.mapper.LegacyGeoShapeFieldMapper; +import org.elasticsearch.legacygeo.test.TestLegacyGeoShapeFieldMapperPlugin; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.test.TestLegacyGeoShapeFieldMapperPlugin; +import org.elasticsearch.search.geo.GeoShapeQueryTestCase; import java.io.IOException; import java.util.Collection; @@ -39,8 +40,7 @@ public class LegacyGeoShapeQueryTests extends GeoShapeQueryTestCase { private static final String[] PREFIX_TREES = new String[] { LegacyGeoShapeFieldMapper.PrefixTrees.GEOHASH, - LegacyGeoShapeFieldMapper.PrefixTrees.QUADTREE - }; + LegacyGeoShapeFieldMapper.PrefixTrees.QUADTREE }; @Override protected Collection> getPlugins() { @@ -49,8 +49,10 @@ public class LegacyGeoShapeQueryTests extends GeoShapeQueryTestCase { @Override protected void createMapping(String indexName, String type, String fieldName, Settings settings) throws Exception { - final XContentBuilder xcb = XContentFactory.jsonBuilder().startObject() - .startObject("properties").startObject(fieldName) + final XContentBuilder xcb = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(fieldName) .field("type", "geo_shape") .field("tree", randomFrom(PREFIX_TREES)) .endObject() @@ -65,59 +67,72 @@ public class LegacyGeoShapeQueryTests extends GeoShapeQueryTestCase { } public void testPointsOnlyExplicit() throws Exception { - String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject() - .startObject("properties").startObject(defaultGeoFieldName) - .field("type", "geo_shape") - .field("tree", randomBoolean() ? "quadtree" : "geohash") - .field("tree_levels", "6") - .field("distance_error_pct", "0.01") - .field("points_only", true) - .endObject() - .endObject().endObject()); - + String mapping = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(defaultGeoFieldName) + .field("type", "geo_shape") + .field("tree", randomBoolean() ? "quadtree" : "geohash") + .field("tree_levels", "6") + .field("distance_error_pct", "0.01") + .field("points_only", true) + .endObject() + .endObject() + .endObject() + ); client().admin().indices().prepareCreate("geo_points_only").addMapping(defaultType, mapping, XContentType.JSON).get(); ensureGreen(); // MULTIPOINT MultiPoint multiPoint = GeometryTestUtils.randomMultiPoint(false); - client().prepareIndex("geo_points_only", defaultType).setId("1") + client().prepareIndex("geo_points_only", defaultType) + .setId("1") .setSource(GeoJson.toXContent(multiPoint, jsonBuilder().startObject().field(defaultGeoFieldName), null).endObject()) - .setRefreshPolicy(IMMEDIATE).get(); + .setRefreshPolicy(IMMEDIATE) + .get(); // POINT - Point point = GeometryTestUtils.randomPoint(false); - client().prepareIndex("geo_points_only", defaultType).setId("2") + Point point = GeometryTestUtils.randomPoint(false); + client().prepareIndex("geo_points_only", defaultType) + .setId("2") .setSource(GeoJson.toXContent(point, jsonBuilder().startObject().field(defaultGeoFieldName), null).endObject()) - .setRefreshPolicy(IMMEDIATE).get(); + .setRefreshPolicy(IMMEDIATE) + .get(); // test that point was inserted - SearchResponse response = client().prepareSearch("geo_points_only") - .setQuery(matchAllQuery()) - .get(); + SearchResponse response = client().prepareSearch("geo_points_only").setQuery(matchAllQuery()).get(); assertEquals(2, response.getHits().getTotalHits().value); } public void testPointsOnly() throws Exception { - String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject() - .startObject("properties").startObject(defaultGeoFieldName) - .field("type", "geo_shape") - .field("tree", randomBoolean() ? "quadtree" : "geohash") - .field("tree_levels", "6") - .field("distance_error_pct", "0.01") - .field("points_only", true) - .endObject() - .endObject().endObject()); + String mapping = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(defaultGeoFieldName) + .field("type", "geo_shape") + .field("tree", randomBoolean() ? "quadtree" : "geohash") + .field("tree_levels", "6") + .field("distance_error_pct", "0.01") + .field("points_only", true) + .endObject() + .endObject() + .endObject() + ); client().admin().indices().prepareCreate("geo_points_only").addMapping(defaultType, mapping, XContentType.JSON).get(); ensureGreen(); Geometry geometry = GeometryTestUtils.randomGeometry(false); try { - client().prepareIndex("geo_points_only", defaultType).setId("1") + client().prepareIndex("geo_points_only", defaultType) + .setId("1") .setSource(GeoJson.toXContent(geometry, jsonBuilder().startObject().field(defaultGeoFieldName), null).endObject()) - .setRefreshPolicy(IMMEDIATE).get(); + .setRefreshPolicy(IMMEDIATE) + .get(); } catch (MapperParsingException e) { // Random geometry generator created something other than a POINT type, verify the correct exception is thrown assertThat(e.getMessage(), containsString("is configured for points only")); @@ -125,37 +140,40 @@ public class LegacyGeoShapeQueryTests extends GeoShapeQueryTestCase { } // test that point was inserted - SearchResponse response = - client().prepareSearch("geo_points_only").setQuery(geoIntersectionQuery(defaultGeoFieldName, geometry)).get(); + SearchResponse response = client().prepareSearch("geo_points_only") + .setQuery(geoIntersectionQuery(defaultGeoFieldName, geometry)) + .get(); assertEquals(1, response.getHits().getTotalHits().value); } public void testFieldAlias() throws IOException { - String mapping = Strings.toString(XContentFactory.jsonBuilder() - .startObject() - .startObject("properties") - .startObject(defaultGeoFieldName) - .field("type", "geo_shape") - .field("tree", randomBoolean() ? "quadtree" : "geohash") - .endObject() - .startObject("alias") - .field("type", "alias") - .field("path", defaultGeoFieldName) - .endObject() - .endObject() - .endObject()); + String mapping = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject(defaultGeoFieldName) + .field("type", "geo_shape") + .field("tree", randomBoolean() ? "quadtree" : "geohash") + .endObject() + .startObject("alias") + .field("type", "alias") + .field("path", defaultGeoFieldName) + .endObject() + .endObject() + .endObject() + ); client().admin().indices().prepareCreate(defaultIndexName).addMapping(defaultType, mapping, XContentType.JSON).get(); ensureGreen(); MultiPoint multiPoint = GeometryTestUtils.randomMultiPoint(false); - client().prepareIndex(defaultIndexName, defaultType).setId("1") + client().prepareIndex(defaultIndexName, defaultType) + .setId("1") .setSource(GeoJson.toXContent(multiPoint, jsonBuilder().startObject().field(defaultGeoFieldName), null).endObject()) - .setRefreshPolicy(IMMEDIATE).get(); - - SearchResponse response = client().prepareSearch(defaultIndexName) - .setQuery(geoShapeQuery("alias", multiPoint)) + .setRefreshPolicy(IMMEDIATE) .get(); + + SearchResponse response = client().prepareSearch(defaultIndexName).setQuery(geoShapeQuery("alias", multiPoint)).get(); assertEquals(1, response.getHits().getTotalHits().value); } } diff --git a/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/search/LegacyGeoUtilsTests.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/search/LegacyGeoUtilsTests.java new file mode 100644 index 000000000000..59178d821825 --- /dev/null +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/search/LegacyGeoUtilsTests.java @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.legacygeo.search; + +import org.apache.lucene.spatial.prefix.tree.Cell; +import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; +import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree; +import org.elasticsearch.common.geo.GeoUtils; +import org.elasticsearch.test.ESTestCase; +import org.locationtech.spatial4j.context.SpatialContext; +import org.locationtech.spatial4j.distance.DistanceUtils; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; + +public class LegacyGeoUtilsTests extends ESTestCase { + + public void testPrefixTreeCellSizes() { + assertThat(GeoUtils.EARTH_SEMI_MAJOR_AXIS, equalTo(DistanceUtils.EARTH_EQUATORIAL_RADIUS_KM * 1000)); + assertThat(GeoUtils.quadTreeCellWidth(0), lessThanOrEqualTo(GeoUtils.EARTH_EQUATOR)); + + SpatialContext spatialContext = new SpatialContext(true); + + GeohashPrefixTree geohashPrefixTree = new GeohashPrefixTree(spatialContext, GeohashPrefixTree.getMaxLevelsPossible() / 2); + Cell gNode = geohashPrefixTree.getWorldCell(); + + for (int i = 0; i < geohashPrefixTree.getMaxLevels(); i++) { + double width = GeoUtils.geoHashCellWidth(i); + double height = GeoUtils.geoHashCellHeight(i); + double size = GeoUtils.geoHashCellSize(i); + double degrees = 360.0 * width / GeoUtils.EARTH_EQUATOR; + int level = GeoUtils.quadTreeLevelsForPrecision(size); + + assertThat(GeoUtils.quadTreeCellWidth(level), lessThanOrEqualTo(width)); + assertThat(GeoUtils.quadTreeCellHeight(level), lessThanOrEqualTo(height)); + assertThat(GeoUtils.geoHashLevelsForPrecision(size), equalTo(geohashPrefixTree.getLevelForDistance(degrees))); + + assertThat( + "width at level " + i, + gNode.getShape().getBoundingBox().getWidth(), + equalTo(360.d * width / GeoUtils.EARTH_EQUATOR) + ); + assertThat( + "height at level " + i, + gNode.getShape().getBoundingBox().getHeight(), + equalTo(180.d * height / GeoUtils.EARTH_POLAR_DISTANCE) + ); + + gNode = gNode.getNextLevelCells(null).next(); + } + + QuadPrefixTree quadPrefixTree = new QuadPrefixTree(spatialContext); + Cell qNode = quadPrefixTree.getWorldCell(); + for (int i = 0; i < quadPrefixTree.getMaxLevels(); i++) { + + double degrees = 360.0 / (1L << i); + double width = GeoUtils.quadTreeCellWidth(i); + double height = GeoUtils.quadTreeCellHeight(i); + double size = GeoUtils.quadTreeCellSize(i); + int level = GeoUtils.quadTreeLevelsForPrecision(size); + + assertThat(GeoUtils.quadTreeCellWidth(level), lessThanOrEqualTo(width)); + assertThat(GeoUtils.quadTreeCellHeight(level), lessThanOrEqualTo(height)); + assertThat(GeoUtils.quadTreeLevelsForPrecision(size), equalTo(quadPrefixTree.getLevelForDistance(degrees))); + + assertThat( + "width at level " + i, + qNode.getShape().getBoundingBox().getWidth(), + equalTo(360.d * width / GeoUtils.EARTH_EQUATOR) + ); + assertThat( + "height at level " + i, + qNode.getShape().getBoundingBox().getHeight(), + equalTo(180.d * height / GeoUtils.EARTH_POLAR_DISTANCE) + ); + + qNode = qNode.getNextLevelCells(null).next(); + } + } +} diff --git a/server/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/test/ElasticsearchGeoAssertions.java similarity index 74% rename from server/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/test/ElasticsearchGeoAssertions.java index a86dc0ef87aa..b37ac93159d7 100644 --- a/server/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/test/ElasticsearchGeoAssertions.java @@ -6,15 +6,15 @@ * Side Public License, v 1. */ -package org.elasticsearch.test.hamcrest; +package org.elasticsearch.legacygeo.test; import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoPoint; -import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.geometry.Line; import org.elasticsearch.geometry.MultiLine; +import org.elasticsearch.legacygeo.parsers.ShapeParser; import org.hamcrest.Matcher; import org.junit.Assert; import org.locationtech.jts.geom.Coordinate; @@ -40,13 +40,13 @@ import static org.junit.Assert.assertTrue; public class ElasticsearchGeoAssertions { - private static int top(Coordinate...points) { + private static int top(Coordinate... points) { int top = 0; for (int i = 1; i < points.length; i++) { - if(points[i].y < points[top].y) { + if (points[i].y < points[top].y) { top = i; - } else if(points[i].y == points[top].y) { - if(points[i].x <= points[top].x) { + } else if (points[i].y == points[top].y) { + if (points[i].x <= points[top].x) { top = i; } } @@ -54,20 +54,20 @@ public class ElasticsearchGeoAssertions { return top; } - private static int prev(int top, Coordinate...points) { + private static int prev(int top, Coordinate... points) { for (int i = 1; i < points.length; i++) { int p = (top + points.length - i) % points.length; - if((points[p].x != points[top].x) || (points[p].y != points[top].y)) { + if ((points[p].x != points[top].x) || (points[p].y != points[top].y)) { return p; } } return -1; } - private static int next(int top, Coordinate...points) { + private static int next(int top, Coordinate... points) { for (int i = 1; i < points.length; i++) { int n = (top + i) % points.length; - if((points[n].x != points[top].x) || (points[n].y != points[top].y)) { + if ((points[n].x != points[top].x) || (points[n].y != points[top].y)) { return n; } } @@ -85,16 +85,16 @@ public class ElasticsearchGeoAssertions { final int prev = prev(top, points); final boolean orientation = points[next].x < points[prev].x; - if(orientation != direction) { + if (orientation != direction) { List asList = Arrays.asList(points); Collections.reverse(asList); return fixedOrderedRing(asList, direction); } else { - if(top>0) { + if (top > 0) { Coordinate[] aligned = new Coordinate[points.length]; - System.arraycopy(points, top, aligned, 0, points.length-top-1); - System.arraycopy(points, 0, aligned, points.length-top-1, top); - aligned[aligned.length-1] = aligned[0]; + System.arraycopy(points, top, aligned, 0, points.length - top - 1); + System.arraycopy(points, 0, aligned, points.length - top - 1, top); + aligned[aligned.length - 1] = aligned[0]; return aligned; } else { return points; @@ -108,13 +108,13 @@ public class ElasticsearchGeoAssertions { } private static boolean isRing(Coordinate[] c) { - return (c[0].x == c[c.length-1].x) && (c[0].y == c[c.length-1].y); + return (c[0].x == c[c.length - 1].x) && (c[0].y == c[c.length - 1].y); } public static void assertEquals(Coordinate[] c1, Coordinate[] c2) { Assert.assertEquals(c1.length, c2.length); - if(isRing(c1) && isRing(c2)) { + if (isRing(c1) && isRing(c2)) { c1 = fixedOrderedRing(c1, true); c2 = fixedOrderedRing(c2, true); } @@ -157,7 +157,7 @@ public class ElasticsearchGeoAssertions { } public static void assertEquals(Geometry s1, Geometry s2) { - if(s1 instanceof LineString && s2 instanceof LineString) { + if (s1 instanceof LineString && s2 instanceof LineString) { assertEquals((LineString) s1, (LineString) s2); } else if (s1 instanceof Polygon && s2 instanceof Polygon) { @@ -173,8 +173,9 @@ public class ElasticsearchGeoAssertions { assertEquals((MultiLineString) s1, (MultiLineString) s2); } else { - throw new RuntimeException("equality of shape types not supported [" + s1.getClass().getName() + " and " + - s2.getClass().getName() + "]"); + throw new RuntimeException( + "equality of shape types not supported [" + s1.getClass().getName() + " and " + s2.getClass().getName() + "]" + ); } } @@ -190,9 +191,9 @@ public class ElasticsearchGeoAssertions { } public static void assertEquals(Object s1, Object s2) { - if(s1 instanceof JtsGeometry && s2 instanceof JtsGeometry) { + if (s1 instanceof JtsGeometry && s2 instanceof JtsGeometry) { assertEquals((JtsGeometry) s1, (JtsGeometry) s2); - } else if(s1 instanceof JtsPoint && s2 instanceof JtsPoint) { + } else if (s1 instanceof JtsPoint && s2 instanceof JtsPoint) { JtsPoint p1 = (JtsPoint) s1; JtsPoint p2 = (JtsPoint) s2; Assert.assertEquals(p1, p2); @@ -203,76 +204,78 @@ public class ElasticsearchGeoAssertions { } else if (s1 instanceof RectangleImpl && s2 instanceof RectangleImpl) { Assert.assertEquals(s1, s2); } else if (s1 instanceof org.apache.lucene.geo.Line[] && s2 instanceof org.apache.lucene.geo.Line[]) { - Assert.assertArrayEquals((org.apache.lucene.geo.Line[])s1, (org.apache.lucene.geo.Line[])s2); - } else if (s1 instanceof org.apache.lucene.geo.Polygon[] && s2 instanceof org.apache.lucene.geo.Polygon[]) { + Assert.assertArrayEquals((org.apache.lucene.geo.Line[]) s1, (org.apache.lucene.geo.Line[]) s2); + } else if (s1 instanceof org.apache.lucene.geo.Polygon[] && s2 instanceof org.apache.lucene.geo.Polygon[]) { Assert.assertArrayEquals((org.apache.lucene.geo.Polygon[]) s1, (org.apache.lucene.geo.Polygon[]) s2); } else if ((s1 instanceof org.apache.lucene.geo.Line && s2 instanceof org.apache.lucene.geo.Line) || (s1 instanceof org.apache.lucene.geo.Polygon && s2 instanceof org.apache.lucene.geo.Polygon) || (s1 instanceof org.apache.lucene.geo.Rectangle && s2 instanceof org.apache.lucene.geo.Rectangle) || (s1 instanceof GeoPoint && s2 instanceof GeoPoint)) { - Assert.assertEquals(s1, s2); - } else if (s1 instanceof Object[] && s2 instanceof Object[]) { - Assert.assertArrayEquals((Object[]) s1, (Object[]) s2); - } else if (s1 instanceof org.elasticsearch.geometry.Geometry && s2 instanceof org.elasticsearch.geometry.Geometry) { - Assert.assertEquals(s1, s2); - } else { - //We want to know the type of the shape because we test shape equality in a special way... - //... in particular we test that one ring is equivalent to another ring even if the points are rotated or reversed. - throw new RuntimeException( - "equality of shape types not supported [" + s1.getClass().getName() + " and " + s2.getClass().getName() + "]"); - } + Assert.assertEquals(s1, s2); + } else if (s1 instanceof Object[] && s2 instanceof Object[]) { + Assert.assertArrayEquals((Object[]) s1, (Object[]) s2); + } else if (s1 instanceof org.elasticsearch.geometry.Geometry && s2 instanceof org.elasticsearch.geometry.Geometry) { + Assert.assertEquals(s1, s2); + } else { + // We want to know the type of the shape because we test shape equality in a special way... + // ... in particular we test that one ring is equivalent to another ring even if the points are rotated or reversed. + throw new RuntimeException( + "equality of shape types not supported [" + s1.getClass().getName() + " and " + s2.getClass().getName() + "]" + ); + } } @Deprecated private static Geometry unwrapJTS(Object shape) { assertThat(shape, instanceOf(JtsGeometry.class)); - return ((JtsGeometry)shape).getGeom(); + return ((JtsGeometry) shape).getGeom(); } public static void assertMultiPolygon(Object shape, boolean useJTS) { if (useJTS) { - assertTrue("expected MultiPolygon but found " + unwrapJTS(shape).getClass().getName(), - unwrapJTS(shape) instanceof MultiPolygon); + assertTrue( + "expected MultiPolygon but found " + unwrapJTS(shape).getClass().getName(), + unwrapJTS(shape) instanceof MultiPolygon + ); } else { - assertTrue("expected Polygon[] but found " + shape.getClass().getName(), - shape instanceof org.elasticsearch.geometry.MultiPolygon); + assertTrue( + "expected Polygon[] but found " + shape.getClass().getName(), + shape instanceof org.elasticsearch.geometry.MultiPolygon + ); } } public static void assertPolygon(Object shape, boolean useJTS) { if (useJTS) { - assertTrue("expected Polygon but found " - + unwrapJTS(shape).getClass().getName(), unwrapJTS(shape) instanceof Polygon); + assertTrue("expected Polygon but found " + unwrapJTS(shape).getClass().getName(), unwrapJTS(shape) instanceof Polygon); } else { - assertTrue("expected Polygon but found " + shape.getClass().getName(), - shape instanceof org.elasticsearch.geometry.Polygon); + assertTrue("expected Polygon but found " + shape.getClass().getName(), shape instanceof org.elasticsearch.geometry.Polygon); } } public static void assertLineString(Object shape, boolean useJTS) { if (useJTS) { - assertTrue("expected LineString but found " - + unwrapJTS(shape).getClass().getName(), unwrapJTS(shape) instanceof LineString); + assertTrue("expected LineString but found " + unwrapJTS(shape).getClass().getName(), unwrapJTS(shape) instanceof LineString); } else { - assertTrue("expected Line but found " + shape.getClass().getName(), - shape instanceof Line); + assertTrue("expected Line but found " + shape.getClass().getName(), shape instanceof Line); } } public static void assertMultiLineString(Object shape, boolean useJTS) { if (useJTS) { - assertTrue("expected MultiLineString but found " - + unwrapJTS(shape).getClass().getName(), unwrapJTS(shape) instanceof MultiLineString); + assertTrue( + "expected MultiLineString but found " + unwrapJTS(shape).getClass().getName(), + unwrapJTS(shape) instanceof MultiLineString + ); } else { - assertTrue("expected Line[] but found " + shape.getClass().getName(), - shape instanceof MultiLine); + assertTrue("expected Line[] but found " + shape.getClass().getName(), shape instanceof MultiLine); } } public static void assertDistance(String geohash1, String geohash2, Matcher match) { GeoPoint p1 = new GeoPoint(geohash1); GeoPoint p2 = new GeoPoint(geohash2); - assertDistance(p1.lat(), p1.lon(), p2.lat(),p2.lon(), match); + assertDistance(p1.lat(), p1.lon(), p2.lat(), p2.lon(), match); } public static void assertDistance(double lat1, double lon1, double lat2, double lon2, Matcher match) { @@ -288,8 +291,10 @@ public class ElasticsearchGeoAssertions { ShapeParser.parse(parser).buildS4J(); Assert.fail("process completed successfully when " + expectedException.getName() + " expected"); } catch (Exception e) { - assertTrue("expected " + expectedException.getName() + " but found " + e.getClass().getName(), - e.getClass().equals(expectedException)); + assertTrue( + "expected " + expectedException.getName() + " but found " + e.getClass().getName(), + e.getClass().equals(expectedException) + ); } } } diff --git a/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/test/RandomGeoGenerator.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/test/RandomGeoGenerator.java new file mode 100644 index 000000000000..d70c07f0e9ea --- /dev/null +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/test/RandomGeoGenerator.java @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.legacygeo.test; + +import org.elasticsearch.common.geo.GeoPoint; + +import java.util.Random; + +/** + * Random geo generation utilities for randomized {@code geo_point} type testing + * does not depend on jts or spatial4j. Use {@link org.elasticsearch.legacygeo.test.RandomShapeGenerator} + * to create random OGC compliant shapes. + */ +public class RandomGeoGenerator { + + public static void randomPoint(Random r, double[] pt) { + final double[] min = { -180, -90 }; + final double[] max = { 180, 90 }; + randomPointIn(r, min[0], min[1], max[0], max[1], pt); + } + + public static void randomPointIn( + Random r, + final double minLon, + final double minLat, + final double maxLon, + final double maxLat, + double[] pt + ) { + assert pt != null && pt.length == 2; + + // normalize min and max + double[] min = { normalizeLongitude(minLon), normalizeLatitude(minLat) }; + double[] max = { normalizeLongitude(maxLon), normalizeLatitude(maxLat) }; + final double[] tMin = new double[2]; + final double[] tMax = new double[2]; + tMin[0] = Math.min(min[0], max[0]); + tMax[0] = Math.max(min[0], max[0]); + tMin[1] = Math.min(min[1], max[1]); + tMax[1] = Math.max(min[1], max[1]); + + pt[0] = tMin[0] + r.nextDouble() * (tMax[0] - tMin[0]); + pt[1] = tMin[1] + r.nextDouble() * (tMax[1] - tMin[1]); + } + + public static GeoPoint randomPoint(Random r) { + return randomPointIn(r, -180, -90, 180, 90); + } + + public static GeoPoint randomPointIn(Random r, final double minLon, final double minLat, final double maxLon, final double maxLat) { + double[] pt = new double[2]; + randomPointIn(r, minLon, minLat, maxLon, maxLat, pt); + return new GeoPoint(pt[1], pt[0]); + } + + /** Puts latitude in range of -90 to 90. */ + private static double normalizeLatitude(double latitude) { + if (latitude >= -90 && latitude <= 90) { + return latitude; // common case, and avoids slight double precision shifting + } + double off = Math.abs((latitude + 90) % 360); + return (off <= 180 ? off : 360 - off) - 90; + } + + /** Puts longitude in range of -180 to +180. */ + private static double normalizeLongitude(double longitude) { + if (longitude >= -180 && longitude <= 180) { + return longitude; // common case, and avoids slight double precision shifting + } + double off = (longitude + 180) % 360; + if (off < 0) { + return 180 + off; + } else if (off == 0 && longitude > 0) { + return 180; + } else { + return -180 + off; + } + } +} diff --git a/server/src/test/java/org/elasticsearch/test/geo/RandomShapeGenerator.java b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/test/RandomShapeGenerator.java similarity index 89% rename from server/src/test/java/org/elasticsearch/test/geo/RandomShapeGenerator.java rename to modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/test/RandomShapeGenerator.java index 5587bbc65037..37d1a5a0eee2 100644 --- a/server/src/test/java/org/elasticsearch/test/geo/RandomShapeGenerator.java +++ b/modules/legacy-geo/src/test/java/org/elasticsearch/legacygeo/test/RandomShapeGenerator.java @@ -6,22 +6,23 @@ * Side Public License, v 1. */ -package org.elasticsearch.test.geo; +package org.elasticsearch.legacygeo.test; import com.carrotsearch.randomizedtesting.generators.RandomNumbers; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.legacygeo.builders.CoordinatesBuilder; +import org.elasticsearch.legacygeo.builders.GeometryCollectionBuilder; +import org.elasticsearch.legacygeo.builders.LineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiLineStringBuilder; +import org.elasticsearch.legacygeo.builders.MultiPointBuilder; +import org.elasticsearch.legacygeo.builders.PointBuilder; +import org.elasticsearch.legacygeo.builders.PolygonBuilder; +import org.elasticsearch.legacygeo.builders.ShapeBuilder; +import org.junit.Assert; import org.locationtech.jts.algorithm.ConvexHull; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.common.geo.builders.CoordinatesBuilder; -import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder; -import org.elasticsearch.common.geo.builders.LineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiLineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiPointBuilder; -import org.elasticsearch.common.geo.builders.PointBuilder; -import org.elasticsearch.common.geo.builders.PolygonBuilder; -import org.elasticsearch.common.geo.builders.ShapeBuilder; -import org.junit.Assert; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.exception.InvalidShapeException; @@ -44,7 +45,12 @@ public class RandomShapeGenerator extends RandomGeoGenerator { protected static boolean ST_VALIDATE = true; public enum ShapeType { - POINT, MULTIPOINT, LINESTRING, MULTILINESTRING, POLYGON; + POINT, + MULTIPOINT, + LINESTRING, + MULTILINESTRING, + POLYGON; + private static final ShapeType[] types = values(); public static ShapeType randomType(Random r) { @@ -84,8 +90,7 @@ public class RandomShapeGenerator extends RandomGeoGenerator { return createGeometryCollection(r, nearPoint, null, 0); } - public static GeometryCollectionBuilder createGeometryCollectionNear(Random r, Point nearPoint, int size) throws - InvalidShapeException { + public static GeometryCollectionBuilder createGeometryCollectionNear(Random r, Point nearPoint, int size) throws InvalidShapeException { return createGeometryCollection(r, nearPoint, null, size); } @@ -93,13 +98,13 @@ public class RandomShapeGenerator extends RandomGeoGenerator { return createGeometryCollection(r, null, within, 0); } - public static GeometryCollectionBuilder createGeometryCollectionWithin(Random r, Rectangle within, int size) throws - InvalidShapeException { + public static GeometryCollectionBuilder createGeometryCollectionWithin(Random r, Rectangle within, int size) + throws InvalidShapeException { return createGeometryCollection(r, null, within, size); } protected static GeometryCollectionBuilder createGeometryCollection(Random r, Point nearPoint, Rectangle bounds, int numGeometries) - throws InvalidShapeException { + throws InvalidShapeException { if (numGeometries <= 0) { // cap geometry collection at 4 shapes (to save test time) numGeometries = RandomNumbers.randomIntBetween(r, 2, 4); @@ -114,7 +119,7 @@ public class RandomShapeGenerator extends RandomGeoGenerator { } GeometryCollectionBuilder gcb = new GeometryCollectionBuilder(); - for (int i=0; i builder = createShapeWithin(r, bounds); // due to world wrapping, and the possibility for ambiguous polygons, the random shape generation could bail with // a null shape. We catch that situation here, and only increment the counter when a valid shape is returned. @@ -130,7 +135,7 @@ public class RandomShapeGenerator extends RandomGeoGenerator { private static ShapeBuilder createShape(Random r, Point nearPoint, Rectangle within, ShapeType st) throws InvalidShapeException { ShapeBuilder shape; - short i=0; + short i = 0; do { shape = createShape(r, nearPoint, within, st, ST_VALIDATE); if (shape != null) { @@ -150,8 +155,8 @@ public class RandomShapeGenerator extends RandomGeoGenerator { * @param st Create a random shape of the provided type * @return the ShapeBuilder for a random shape */ - private static ShapeBuilder createShape(Random r, Point nearPoint, Rectangle within, ShapeType st, boolean validate) throws - InvalidShapeException { + private static ShapeBuilder createShape(Random r, Point nearPoint, Rectangle within, ShapeType st, boolean validate) + throws InvalidShapeException { if (st == null) { st = ShapeType.randomType(r); @@ -176,7 +181,7 @@ public class RandomShapeGenerator extends RandomGeoGenerator { // (n^2-n)/2 and computing the relation intersection matrix will become NP-Hard int numPoints = RandomNumbers.randomIntBetween(r, 3, 10); CoordinatesBuilder coordinatesBuilder = new CoordinatesBuilder(); - for (int i=0; i getMappers() { diff --git a/server/build.gradle b/server/build.gradle index 03d528ab734f..c93823b7a076 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -84,7 +84,6 @@ dependencies { api "org.apache.lucene:lucene-queries:${versions.lucene}" api "org.apache.lucene:lucene-queryparser:${versions.lucene}" api "org.apache.lucene:lucene-sandbox:${versions.lucene}" - api "org.apache.lucene:lucene-spatial-extras:${versions.lucene}" api "org.apache.lucene:lucene-spatial3d:${versions.lucene}" api "org.apache.lucene:lucene-suggest:${versions.lucene}" @@ -103,10 +102,6 @@ dependencies { // precentil ranks aggregation api 'org.hdrhistogram:HdrHistogram:2.1.9' - // lucene spatial - api "org.locationtech.spatial4j:spatial4j:${versions.spatial4j}", optional - api "org.locationtech.jts:jts-core:${versions.jts}", optional - // logging api "org.apache.logging.log4j:log4j-api:${versions.log4j}" api "org.apache.logging.log4j:log4j-core:${versions.log4j}", optional @@ -202,7 +197,6 @@ tasks.named("thirdPartyAudit").configure { 'com.fasterxml.jackson.dataformat.xml.JacksonXmlModule', 'com.fasterxml.jackson.dataformat.xml.XmlMapper', 'com.fasterxml.jackson.dataformat.xml.util.DefaultXmlPrettyPrinter', - 'com.fasterxml.jackson.databind.node.ObjectNode', 'org.fusesource.jansi.Ansi', 'org.fusesource.jansi.AnsiRenderer$Code', 'com.lmax.disruptor.BlockingWaitStrategy', @@ -270,20 +264,6 @@ tasks.named("thirdPartyAudit").configure { 'org.zeromq.ZMQ$Context', 'org.zeromq.ZMQ$Socket', 'org.zeromq.ZMQ', - - // from org.locationtech.spatial4j.io.GeoJSONReader (spatial4j) - 'org.noggit.JSONParser', - - // from lucene-spatial - 'com.fasterxml.jackson.databind.JsonSerializer', - 'com.fasterxml.jackson.databind.JsonDeserializer', - 'com.fasterxml.jackson.databind.node.ArrayNode', - 'com.google.common.geometry.S2Cell', - 'com.google.common.geometry.S2CellId', - 'com.google.common.geometry.S2Projections', - 'com.google.common.geometry.S2Point', - 'com.google.common.geometry.S2$Metric', - 'com.google.common.geometry.S2LatLng' ) if (BuildParams.runtimeJavaVersion > JavaVersion.VERSION_1_8) { diff --git a/server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java b/server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java index 670913440733..29811afa61fd 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java +++ b/server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java @@ -8,8 +8,6 @@ package org.elasticsearch.common.geo; -import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; -import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree; import org.apache.lucene.util.SloppyMath; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.unit.DistanceUnit; @@ -66,6 +64,9 @@ public class GeoUtils { /** rounding error for quantized latitude and longitude values */ public static final double TOLERANCE = 1E-6; + private static final int QUAD_MAX_LEVELS_POSSIBLE = 50; + private static final int GEOHASH_MAX_LEVELS_POSSIBLE = 24; + /** Returns true if latitude is actually a valid latitude value.*/ public static boolean isValidLatitude(double latitude) { if (Double.isNaN(latitude) || Double.isInfinite(latitude) || latitude < GeoUtils.MIN_LAT || latitude > GeoUtils.MAX_LAT) { @@ -158,7 +159,7 @@ public class GeoUtils { public static int quadTreeLevelsForPrecision(double meters) { assert meters >= 0; if(meters == 0) { - return QuadPrefixTree.MAX_LEVELS_POSSIBLE; + return QUAD_MAX_LEVELS_POSSIBLE; } else { final double ratio = 1+(EARTH_POLAR_DISTANCE / EARTH_EQUATOR); // cell ratio final double width = Math.sqrt((meters*meters)/(ratio*ratio)); // convert to cell width @@ -188,7 +189,7 @@ public class GeoUtils { assert meters >= 0; if(meters == 0) { - return GeohashPrefixTree.getMaxLevelsPossible(); + return GEOHASH_MAX_LEVELS_POSSIBLE; } else { final double ratio = 1+(EARTH_POLAR_DISTANCE / EARTH_EQUATOR); // cell ratio final double width = Math.sqrt((meters*meters)/(ratio*ratio)); // convert to cell width diff --git a/server/src/main/java/org/elasticsearch/index/mapper/AbstractShapeGeometryFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/AbstractShapeGeometryFieldMapper.java index 36506ae08448..cc123dc7bddb 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/AbstractShapeGeometryFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/AbstractShapeGeometryFieldMapper.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.function.Function; /** - * Base class for {@link GeoShapeFieldMapper} and {@link LegacyGeoShapeFieldMapper} + * Base class for {@link GeoShapeFieldMapper} */ public abstract class AbstractShapeGeometryFieldMapper extends AbstractGeometryFieldMapper { diff --git a/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeFieldMapper.java index 88ae4eb44358..74f855e63d7b 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/GeoShapeFieldMapper.java @@ -26,8 +26,10 @@ import org.elasticsearch.index.query.SearchExecutionContext; import java.io.IOException; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; /** @@ -54,6 +56,10 @@ public class GeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper DEPRECATED_PARAMETERS = new HashSet<>( + Arrays.asList("strategy", "tree", "tree_levels", "precision", "distance_error_pct", "points_only") + ); + public static final String CONTENT_TYPE = "geo_shape"; private static Builder builder(FieldMapper in) { diff --git a/server/src/main/java/org/elasticsearch/index/query/QueryBuilders.java b/server/src/main/java/org/elasticsearch/index/query/QueryBuilders.java index f306fed363ac..b38f2f7b9eb0 100644 --- a/server/src/main/java/org/elasticsearch/index/query/QueryBuilders.java +++ b/server/src/main/java/org/elasticsearch/index/query/QueryBuilders.java @@ -12,7 +12,6 @@ import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.ShapeRelation; -import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.geometry.Geometry; import org.elasticsearch.index.query.DistanceFeatureQueryBuilder.Origin; import org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item; @@ -676,14 +675,6 @@ public final class QueryBuilders { return new GeoShapeQueryBuilder(name, shape); } - /** - * @deprecated use {@link #geoShapeQuery(String, Geometry)} instead - */ - @Deprecated - public static GeoShapeQueryBuilder geoShapeQuery(String name, ShapeBuilder shape) throws IOException { - return new GeoShapeQueryBuilder(name, shape.buildGeometry()); - } - public static GeoShapeQueryBuilder geoShapeQuery(String name, String indexedShapeId) { return new GeoShapeQueryBuilder(name, indexedShapeId); } @@ -708,16 +699,6 @@ public final class QueryBuilders { return builder; } - /** - * @deprecated use {@link #geoIntersectionQuery(String, Geometry)} instead - */ - @Deprecated - public static GeoShapeQueryBuilder geoIntersectionQuery(String name, ShapeBuilder shape) throws IOException { - GeoShapeQueryBuilder builder = geoShapeQuery(name, shape); - builder.relation(ShapeRelation.INTERSECTS); - return builder; - } - public static GeoShapeQueryBuilder geoIntersectionQuery(String name, String indexedShapeId) { GeoShapeQueryBuilder builder = geoShapeQuery(name, indexedShapeId); builder.relation(ShapeRelation.INTERSECTS); @@ -746,16 +727,6 @@ public final class QueryBuilders { return builder; } - /** - * @deprecated use {@link #geoWithinQuery(String, Geometry)} instead - */ - @Deprecated - public static GeoShapeQueryBuilder geoWithinQuery(String name, ShapeBuilder shape) throws IOException { - GeoShapeQueryBuilder builder = geoShapeQuery(name, shape); - builder.relation(ShapeRelation.WITHIN); - return builder; - } - public static GeoShapeQueryBuilder geoWithinQuery(String name, String indexedShapeId) { GeoShapeQueryBuilder builder = geoShapeQuery(name, indexedShapeId); builder.relation(ShapeRelation.WITHIN); @@ -784,16 +755,6 @@ public final class QueryBuilders { return builder; } - /** - * @deprecated use {@link #geoDisjointQuery(String, Geometry)} instead - */ - @Deprecated - public static GeoShapeQueryBuilder geoDisjointQuery(String name, ShapeBuilder shape) throws IOException { - GeoShapeQueryBuilder builder = geoShapeQuery(name, shape); - builder.relation(ShapeRelation.DISJOINT); - return builder; - } - public static GeoShapeQueryBuilder geoDisjointQuery(String name, String indexedShapeId) { GeoShapeQueryBuilder builder = geoShapeQuery(name, indexedShapeId); builder.relation(ShapeRelation.DISJOINT); diff --git a/server/src/main/java/org/elasticsearch/search/SearchModule.java b/server/src/main/java/org/elasticsearch/search/SearchModule.java index 334fea63ff02..c79e513d07ea 100644 --- a/server/src/main/java/org/elasticsearch/search/SearchModule.java +++ b/server/src/main/java/org/elasticsearch/search/SearchModule.java @@ -12,8 +12,6 @@ import org.apache.lucene.search.BooleanQuery; import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.NamedRegistry; import org.elasticsearch.common.xcontent.ParseField; -import org.elasticsearch.common.geo.GeoShapeType; -import org.elasticsearch.common.geo.ShapesAvailability; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry; import org.elasticsearch.common.io.stream.StreamOutput; @@ -293,7 +291,7 @@ import static org.elasticsearch.index.query.CommonTermsQueryBuilder.COMMON_TERMS */ public class SearchModule { public static final Setting INDICES_MAX_CLAUSE_COUNT_SETTING = Setting.intSetting("indices.query.bool.max_clause_count", - 1024, 1, Integer.MAX_VALUE, Setting.Property.NodeScope); + 1024, 1, Integer.MAX_VALUE, Setting.Property.NodeScope); public static final Setting INDICES_MAX_NESTED_DEPTH_SETTING = Setting.intSetting("indices.query.bool.max_nested_depth", 20, 1, Integer.MAX_VALUE, Setting.Property.NodeScope); @@ -301,7 +299,7 @@ public class SearchModule { private final boolean transportClient; private final Map highlighters; private final ParseFieldRegistry movingAverageModelParserRegistry = new ParseFieldRegistry<>( - "moving_avg_model"); + "moving_avg_model"); private final List fetchSubPhases = new ArrayList<>(); @@ -336,7 +334,6 @@ public class SearchModule { registerPipelineAggregations(plugins); registerFetchSubPhases(plugins); registerSearchExts(plugins); - registerShapes(); registerIntervalsSourceProviders(); requestCacheKeyDifferentiator = registerRequestCacheKeyDifferentiator(plugins); namedWriteables.addAll(SortValue.namedWriteables()); @@ -385,137 +382,137 @@ public class SearchModule { .addResultReader(InternalSum::new) .setAggregatorRegistrar(SumAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(MinAggregationBuilder.NAME, MinAggregationBuilder::new, MinAggregationBuilder.PARSER) - .addResultReader(InternalMin::new) - .setAggregatorRegistrar(MinAggregationBuilder::registerAggregators), builder); + .addResultReader(InternalMin::new) + .setAggregatorRegistrar(MinAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(MaxAggregationBuilder.NAME, MaxAggregationBuilder::new, MaxAggregationBuilder.PARSER) - .addResultReader(InternalMax::new) - .setAggregatorRegistrar(MaxAggregationBuilder::registerAggregators), builder); + .addResultReader(InternalMax::new) + .setAggregatorRegistrar(MaxAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(StatsAggregationBuilder.NAME, StatsAggregationBuilder::new, StatsAggregationBuilder.PARSER) .addResultReader(InternalStats::new) .setAggregatorRegistrar(StatsAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(ExtendedStatsAggregationBuilder.NAME, ExtendedStatsAggregationBuilder::new, ExtendedStatsAggregationBuilder.PARSER) - .addResultReader(InternalExtendedStats::new) - .setAggregatorRegistrar(ExtendedStatsAggregationBuilder::registerAggregators), builder); + .addResultReader(InternalExtendedStats::new) + .setAggregatorRegistrar(ExtendedStatsAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(ValueCountAggregationBuilder.NAME, ValueCountAggregationBuilder::new, - ValueCountAggregationBuilder.PARSER) - .addResultReader(InternalValueCount::new) - .setAggregatorRegistrar(ValueCountAggregationBuilder::registerAggregators), builder); + ValueCountAggregationBuilder.PARSER) + .addResultReader(InternalValueCount::new) + .setAggregatorRegistrar(ValueCountAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(PercentilesAggregationBuilder.NAME, PercentilesAggregationBuilder::new, - PercentilesAggregationBuilder::parse) - .addResultReader(InternalTDigestPercentiles.NAME, InternalTDigestPercentiles::new) - .addResultReader(InternalHDRPercentiles.NAME, InternalHDRPercentiles::new) - .setAggregatorRegistrar(PercentilesAggregationBuilder::registerAggregators), builder); + PercentilesAggregationBuilder::parse) + .addResultReader(InternalTDigestPercentiles.NAME, InternalTDigestPercentiles::new) + .addResultReader(InternalHDRPercentiles.NAME, InternalHDRPercentiles::new) + .setAggregatorRegistrar(PercentilesAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(PercentileRanksAggregationBuilder.NAME, PercentileRanksAggregationBuilder::new, - PercentileRanksAggregationBuilder::parse) - .addResultReader(InternalTDigestPercentileRanks.NAME, InternalTDigestPercentileRanks::new) - .addResultReader(InternalHDRPercentileRanks.NAME, InternalHDRPercentileRanks::new) - .setAggregatorRegistrar(PercentileRanksAggregationBuilder::registerAggregators), builder); + PercentileRanksAggregationBuilder::parse) + .addResultReader(InternalTDigestPercentileRanks.NAME, InternalTDigestPercentileRanks::new) + .addResultReader(InternalHDRPercentileRanks.NAME, InternalHDRPercentileRanks::new) + .setAggregatorRegistrar(PercentileRanksAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(MedianAbsoluteDeviationAggregationBuilder.NAME, MedianAbsoluteDeviationAggregationBuilder::new, MedianAbsoluteDeviationAggregationBuilder.PARSER) - .addResultReader(InternalMedianAbsoluteDeviation::new) - .setAggregatorRegistrar(MedianAbsoluteDeviationAggregationBuilder::registerAggregators), builder); + .addResultReader(InternalMedianAbsoluteDeviation::new) + .setAggregatorRegistrar(MedianAbsoluteDeviationAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(CardinalityAggregationBuilder.NAME, CardinalityAggregationBuilder::new, - CardinalityAggregationBuilder.PARSER).addResultReader(InternalCardinality::new) + CardinalityAggregationBuilder.PARSER).addResultReader(InternalCardinality::new) .setAggregatorRegistrar(CardinalityAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(GlobalAggregationBuilder.NAME, GlobalAggregationBuilder::new, - GlobalAggregationBuilder::parse).addResultReader(InternalGlobal::new), builder); + GlobalAggregationBuilder::parse).addResultReader(InternalGlobal::new), builder); registerAggregation(new AggregationSpec(MissingAggregationBuilder.NAME, MissingAggregationBuilder::new, - MissingAggregationBuilder.PARSER) - .addResultReader(InternalMissing::new) - .setAggregatorRegistrar(MissingAggregationBuilder::registerAggregators), builder); + MissingAggregationBuilder.PARSER) + .addResultReader(InternalMissing::new) + .setAggregatorRegistrar(MissingAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(FilterAggregationBuilder.NAME, FilterAggregationBuilder::new, - FilterAggregationBuilder::parse).addResultReader(InternalFilter::new), builder); + FilterAggregationBuilder::parse).addResultReader(InternalFilter::new), builder); registerAggregation(new AggregationSpec(FiltersAggregationBuilder.NAME, FiltersAggregationBuilder::new, - FiltersAggregationBuilder::parse).addResultReader(InternalFilters::new), builder); + FiltersAggregationBuilder::parse).addResultReader(InternalFilters::new), builder); registerAggregation(new AggregationSpec(AdjacencyMatrixAggregationBuilder.NAME, AdjacencyMatrixAggregationBuilder::new, - AdjacencyMatrixAggregationBuilder::parse).addResultReader(InternalAdjacencyMatrix::new), builder); + AdjacencyMatrixAggregationBuilder::parse).addResultReader(InternalAdjacencyMatrix::new), builder); registerAggregation(new AggregationSpec(SamplerAggregationBuilder.NAME, SamplerAggregationBuilder::new, SamplerAggregationBuilder::parse) - .addResultReader(InternalSampler.NAME, InternalSampler::new) - .addResultReader(UnmappedSampler.NAME, UnmappedSampler::new), + .addResultReader(InternalSampler.NAME, InternalSampler::new) + .addResultReader(UnmappedSampler.NAME, UnmappedSampler::new), builder); registerAggregation(new AggregationSpec(DiversifiedAggregationBuilder.NAME, DiversifiedAggregationBuilder::new, DiversifiedAggregationBuilder.PARSER).setAggregatorRegistrar(DiversifiedAggregationBuilder::registerAggregators) - /* Reuses result readers from SamplerAggregator*/, builder); + /* Reuses result readers from SamplerAggregator*/, builder); registerAggregation(new AggregationSpec(TermsAggregationBuilder.NAME, TermsAggregationBuilder::new, - TermsAggregationBuilder.PARSER) - .addResultReader(StringTerms.NAME, StringTerms::new) - .addResultReader(UnmappedTerms.NAME, UnmappedTerms::new) - .addResultReader(LongTerms.NAME, LongTerms::new) - .addResultReader(DoubleTerms.NAME, DoubleTerms::new) + TermsAggregationBuilder.PARSER) + .addResultReader(StringTerms.NAME, StringTerms::new) + .addResultReader(UnmappedTerms.NAME, UnmappedTerms::new) + .addResultReader(LongTerms.NAME, LongTerms::new) + .addResultReader(DoubleTerms.NAME, DoubleTerms::new) .setAggregatorRegistrar(TermsAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(RareTermsAggregationBuilder.NAME, RareTermsAggregationBuilder::new, - RareTermsAggregationBuilder.PARSER) - .addResultReader(StringRareTerms.NAME, StringRareTerms::new) - .addResultReader(UnmappedRareTerms.NAME, UnmappedRareTerms::new) - .addResultReader(LongRareTerms.NAME, LongRareTerms::new) - .setAggregatorRegistrar(RareTermsAggregationBuilder::registerAggregators), builder); + RareTermsAggregationBuilder.PARSER) + .addResultReader(StringRareTerms.NAME, StringRareTerms::new) + .addResultReader(UnmappedRareTerms.NAME, UnmappedRareTerms::new) + .addResultReader(LongRareTerms.NAME, LongRareTerms::new) + .setAggregatorRegistrar(RareTermsAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(SignificantTermsAggregationBuilder.NAME, SignificantTermsAggregationBuilder::new, - SignificantTermsAggregationBuilder::parse) - .addResultReader(SignificantStringTerms.NAME, SignificantStringTerms::new) - .addResultReader(SignificantLongTerms.NAME, SignificantLongTerms::new) - .addResultReader(UnmappedSignificantTerms.NAME, UnmappedSignificantTerms::new) - .setAggregatorRegistrar(SignificantTermsAggregationBuilder::registerAggregators), builder); + SignificantTermsAggregationBuilder::parse) + .addResultReader(SignificantStringTerms.NAME, SignificantStringTerms::new) + .addResultReader(SignificantLongTerms.NAME, SignificantLongTerms::new) + .addResultReader(UnmappedSignificantTerms.NAME, UnmappedSignificantTerms::new) + .setAggregatorRegistrar(SignificantTermsAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(SignificantTextAggregationBuilder.NAME, SignificantTextAggregationBuilder::new, - SignificantTextAggregationBuilder::parse), builder); + SignificantTextAggregationBuilder::parse), builder); registerAggregation(new AggregationSpec(RangeAggregationBuilder.NAME, RangeAggregationBuilder::new, - RangeAggregationBuilder.PARSER) - .addResultReader(InternalRange::new) - .setAggregatorRegistrar(RangeAggregationBuilder::registerAggregators), builder); + RangeAggregationBuilder.PARSER) + .addResultReader(InternalRange::new) + .setAggregatorRegistrar(RangeAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(DateRangeAggregationBuilder.NAME, DateRangeAggregationBuilder::new, - DateRangeAggregationBuilder.PARSER) - .addResultReader(InternalDateRange::new) - .setAggregatorRegistrar(DateRangeAggregationBuilder::registerAggregators), builder); + DateRangeAggregationBuilder.PARSER) + .addResultReader(InternalDateRange::new) + .setAggregatorRegistrar(DateRangeAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(IpRangeAggregationBuilder.NAME, IpRangeAggregationBuilder::new, - IpRangeAggregationBuilder.PARSER) - .addResultReader(InternalBinaryRange::new) - .setAggregatorRegistrar(IpRangeAggregationBuilder::registerAggregators), builder); + IpRangeAggregationBuilder.PARSER) + .addResultReader(InternalBinaryRange::new) + .setAggregatorRegistrar(IpRangeAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(HistogramAggregationBuilder.NAME, HistogramAggregationBuilder::new, - HistogramAggregationBuilder.PARSER) - .addResultReader(InternalHistogram::new) - .setAggregatorRegistrar(HistogramAggregationBuilder::registerAggregators), builder); + HistogramAggregationBuilder.PARSER) + .addResultReader(InternalHistogram::new) + .setAggregatorRegistrar(HistogramAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(DateHistogramAggregationBuilder.NAME, DateHistogramAggregationBuilder::new, - DateHistogramAggregationBuilder.PARSER) - .addResultReader(InternalDateHistogram::new) - .setAggregatorRegistrar(DateHistogramAggregationBuilder::registerAggregators), builder); + DateHistogramAggregationBuilder.PARSER) + .addResultReader(InternalDateHistogram::new) + .setAggregatorRegistrar(DateHistogramAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(AutoDateHistogramAggregationBuilder.NAME, AutoDateHistogramAggregationBuilder::new, - AutoDateHistogramAggregationBuilder.PARSER) - .addResultReader(InternalAutoDateHistogram::new) - .setAggregatorRegistrar(AutoDateHistogramAggregationBuilder::registerAggregators), builder); + AutoDateHistogramAggregationBuilder.PARSER) + .addResultReader(InternalAutoDateHistogram::new) + .setAggregatorRegistrar(AutoDateHistogramAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(VariableWidthHistogramAggregationBuilder.NAME, - VariableWidthHistogramAggregationBuilder::new, - VariableWidthHistogramAggregationBuilder.PARSER) - .addResultReader(InternalVariableWidthHistogram::new) - .setAggregatorRegistrar(VariableWidthHistogramAggregationBuilder::registerAggregators), builder); + VariableWidthHistogramAggregationBuilder::new, + VariableWidthHistogramAggregationBuilder.PARSER) + .addResultReader(InternalVariableWidthHistogram::new) + .setAggregatorRegistrar(VariableWidthHistogramAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(GeoDistanceAggregationBuilder.NAME, GeoDistanceAggregationBuilder::new, - GeoDistanceAggregationBuilder::parse) - .addResultReader(InternalGeoDistance::new) - .setAggregatorRegistrar(GeoDistanceAggregationBuilder::registerAggregators), builder); + GeoDistanceAggregationBuilder::parse) + .addResultReader(InternalGeoDistance::new) + .setAggregatorRegistrar(GeoDistanceAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(GeoHashGridAggregationBuilder.NAME, GeoHashGridAggregationBuilder::new, - GeoHashGridAggregationBuilder.PARSER) - .addResultReader(InternalGeoHashGrid::new) - .setAggregatorRegistrar(GeoHashGridAggregationBuilder::registerAggregators), builder); + GeoHashGridAggregationBuilder.PARSER) + .addResultReader(InternalGeoHashGrid::new) + .setAggregatorRegistrar(GeoHashGridAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(GeoTileGridAggregationBuilder.NAME, GeoTileGridAggregationBuilder::new, - GeoTileGridAggregationBuilder.PARSER) - .addResultReader(InternalGeoTileGrid::new) - .setAggregatorRegistrar(GeoTileGridAggregationBuilder::registerAggregators), builder); + GeoTileGridAggregationBuilder.PARSER) + .addResultReader(InternalGeoTileGrid::new) + .setAggregatorRegistrar(GeoTileGridAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(NestedAggregationBuilder.NAME, NestedAggregationBuilder::new, - NestedAggregationBuilder::parse).addResultReader(InternalNested::new), builder); + NestedAggregationBuilder::parse).addResultReader(InternalNested::new), builder); registerAggregation(new AggregationSpec(ReverseNestedAggregationBuilder.NAME, ReverseNestedAggregationBuilder::new, - ReverseNestedAggregationBuilder::parse).addResultReader(InternalReverseNested::new), builder); + ReverseNestedAggregationBuilder::parse).addResultReader(InternalReverseNested::new), builder); registerAggregation(new AggregationSpec(TopHitsAggregationBuilder.NAME, TopHitsAggregationBuilder::new, - TopHitsAggregationBuilder::parse).addResultReader(InternalTopHits::new), builder); + TopHitsAggregationBuilder::parse).addResultReader(InternalTopHits::new), builder); registerAggregation(new AggregationSpec(GeoBoundsAggregationBuilder.NAME, GeoBoundsAggregationBuilder::new, - GeoBoundsAggregationBuilder.PARSER) - .addResultReader(InternalGeoBounds::new) - .setAggregatorRegistrar(GeoBoundsAggregationBuilder::registerAggregators), builder); + GeoBoundsAggregationBuilder.PARSER) + .addResultReader(InternalGeoBounds::new) + .setAggregatorRegistrar(GeoBoundsAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(GeoCentroidAggregationBuilder.NAME, GeoCentroidAggregationBuilder::new, - GeoCentroidAggregationBuilder.PARSER) - .addResultReader(InternalGeoCentroid::new) - .setAggregatorRegistrar(GeoCentroidAggregationBuilder::registerAggregators), builder); + GeoCentroidAggregationBuilder.PARSER) + .addResultReader(InternalGeoCentroid::new) + .setAggregatorRegistrar(GeoCentroidAggregationBuilder::registerAggregators), builder); registerAggregation(new AggregationSpec(ScriptedMetricAggregationBuilder.NAME, ScriptedMetricAggregationBuilder::new, - ScriptedMetricAggregationBuilder.PARSER).addResultReader(InternalScriptedMetric::new), builder); + ScriptedMetricAggregationBuilder.PARSER).addResultReader(InternalScriptedMetric::new), builder); registerAggregation( new AggregationSpec(CompositeAggregationBuilder.NAME, CompositeAggregationBuilder::new, CompositeAggregationBuilder.PARSER) .addResultReader(InternalComposite::new) @@ -539,7 +536,7 @@ public class SearchModule { })); } namedWriteables.add( - new NamedWriteableRegistry.Entry(AggregationBuilder.class, spec.getName().getPreferredName(), spec.getReader())); + new NamedWriteableRegistry.Entry(AggregationBuilder.class, spec.getName().getPreferredName(), spec.getReader())); for (Map.Entry> t : spec.getResultReaders().entrySet()) { String writeableName = t.getKey(); Writeable.Reader internalReader = t.getValue(); @@ -557,92 +554,92 @@ public class SearchModule { private void registerPipelineAggregations(List plugins) { registerPipelineAggregation(new PipelineAggregationSpec( - DerivativePipelineAggregationBuilder.NAME, - DerivativePipelineAggregationBuilder::new, - DerivativePipelineAggregator::new, - DerivativePipelineAggregationBuilder::parse) - .addResultReader(InternalDerivative::new)); + DerivativePipelineAggregationBuilder.NAME, + DerivativePipelineAggregationBuilder::new, + DerivativePipelineAggregator::new, + DerivativePipelineAggregationBuilder::parse) + .addResultReader(InternalDerivative::new)); registerPipelineAggregation(new PipelineAggregationSpec( - MaxBucketPipelineAggregationBuilder.NAME, - MaxBucketPipelineAggregationBuilder::new, - MaxBucketPipelineAggregator::new, - MaxBucketPipelineAggregationBuilder.PARSER) - // This bucket is used by many pipeline aggreations. - .addResultReader(InternalBucketMetricValue.NAME, InternalBucketMetricValue::new)); + MaxBucketPipelineAggregationBuilder.NAME, + MaxBucketPipelineAggregationBuilder::new, + MaxBucketPipelineAggregator::new, + MaxBucketPipelineAggregationBuilder.PARSER) + // This bucket is used by many pipeline aggreations. + .addResultReader(InternalBucketMetricValue.NAME, InternalBucketMetricValue::new)); registerPipelineAggregation(new PipelineAggregationSpec( MinBucketPipelineAggregationBuilder.NAME, MinBucketPipelineAggregationBuilder::new, MinBucketPipelineAggregator::new, MinBucketPipelineAggregationBuilder.PARSER) - /* Uses InternalBucketMetricValue */); + /* Uses InternalBucketMetricValue */); registerPipelineAggregation(new PipelineAggregationSpec( - AvgBucketPipelineAggregationBuilder.NAME, - AvgBucketPipelineAggregationBuilder::new, - AvgBucketPipelineAggregator::new, - AvgBucketPipelineAggregationBuilder.PARSER) - // This bucket is used by many pipeline aggreations. - .addResultReader(InternalSimpleValue.NAME, InternalSimpleValue::new)); + AvgBucketPipelineAggregationBuilder.NAME, + AvgBucketPipelineAggregationBuilder::new, + AvgBucketPipelineAggregator::new, + AvgBucketPipelineAggregationBuilder.PARSER) + // This bucket is used by many pipeline aggreations. + .addResultReader(InternalSimpleValue.NAME, InternalSimpleValue::new)); registerPipelineAggregation(new PipelineAggregationSpec( SumBucketPipelineAggregationBuilder.NAME, SumBucketPipelineAggregationBuilder::new, SumBucketPipelineAggregator::new, SumBucketPipelineAggregationBuilder.PARSER) - /* Uses InternalSimpleValue */); + /* Uses InternalSimpleValue */); registerPipelineAggregation(new PipelineAggregationSpec( - StatsBucketPipelineAggregationBuilder.NAME, - StatsBucketPipelineAggregationBuilder::new, - StatsBucketPipelineAggregator::new, - StatsBucketPipelineAggregationBuilder.PARSER) - .addResultReader(InternalStatsBucket::new)); + StatsBucketPipelineAggregationBuilder.NAME, + StatsBucketPipelineAggregationBuilder::new, + StatsBucketPipelineAggregator::new, + StatsBucketPipelineAggregationBuilder.PARSER) + .addResultReader(InternalStatsBucket::new)); registerPipelineAggregation(new PipelineAggregationSpec( - ExtendedStatsBucketPipelineAggregationBuilder.NAME, - ExtendedStatsBucketPipelineAggregationBuilder::new, - ExtendedStatsBucketPipelineAggregator::new, - new ExtendedStatsBucketParser()) - .addResultReader(InternalExtendedStatsBucket::new)); + ExtendedStatsBucketPipelineAggregationBuilder.NAME, + ExtendedStatsBucketPipelineAggregationBuilder::new, + ExtendedStatsBucketPipelineAggregator::new, + new ExtendedStatsBucketParser()) + .addResultReader(InternalExtendedStatsBucket::new)); registerPipelineAggregation(new PipelineAggregationSpec( - PercentilesBucketPipelineAggregationBuilder.NAME, - PercentilesBucketPipelineAggregationBuilder::new, - PercentilesBucketPipelineAggregator::new, - PercentilesBucketPipelineAggregationBuilder.PARSER) - .addResultReader(InternalPercentilesBucket::new)); + PercentilesBucketPipelineAggregationBuilder.NAME, + PercentilesBucketPipelineAggregationBuilder::new, + PercentilesBucketPipelineAggregator::new, + PercentilesBucketPipelineAggregationBuilder.PARSER) + .addResultReader(InternalPercentilesBucket::new)); registerPipelineAggregation(new PipelineAggregationSpec( - MovAvgPipelineAggregationBuilder.NAME, - MovAvgPipelineAggregationBuilder::new, - MovAvgPipelineAggregator::new, - (XContentParser parser, String name) -> - MovAvgPipelineAggregationBuilder.parse(movingAverageModelParserRegistry, name, parser) - )/* Uses InternalHistogram for buckets */); + MovAvgPipelineAggregationBuilder.NAME, + MovAvgPipelineAggregationBuilder::new, + MovAvgPipelineAggregator::new, + (XContentParser parser, String name) -> + MovAvgPipelineAggregationBuilder.parse(movingAverageModelParserRegistry, name, parser) + )/* Uses InternalHistogram for buckets */); registerPipelineAggregation(new PipelineAggregationSpec( - CumulativeSumPipelineAggregationBuilder.NAME, - CumulativeSumPipelineAggregationBuilder::new, - CumulativeSumPipelineAggregator::new, - CumulativeSumPipelineAggregationBuilder.PARSER)); + CumulativeSumPipelineAggregationBuilder.NAME, + CumulativeSumPipelineAggregationBuilder::new, + CumulativeSumPipelineAggregator::new, + CumulativeSumPipelineAggregationBuilder.PARSER)); registerPipelineAggregation(new PipelineAggregationSpec( - BucketScriptPipelineAggregationBuilder.NAME, - BucketScriptPipelineAggregationBuilder::new, - BucketScriptPipelineAggregator::new, - BucketScriptPipelineAggregationBuilder.PARSER)); + BucketScriptPipelineAggregationBuilder.NAME, + BucketScriptPipelineAggregationBuilder::new, + BucketScriptPipelineAggregator::new, + BucketScriptPipelineAggregationBuilder.PARSER)); registerPipelineAggregation(new PipelineAggregationSpec( - BucketSelectorPipelineAggregationBuilder.NAME, - BucketSelectorPipelineAggregationBuilder::new, - BucketSelectorPipelineAggregator::new, - BucketSelectorPipelineAggregationBuilder::parse)); + BucketSelectorPipelineAggregationBuilder.NAME, + BucketSelectorPipelineAggregationBuilder::new, + BucketSelectorPipelineAggregator::new, + BucketSelectorPipelineAggregationBuilder::parse)); registerPipelineAggregation(new PipelineAggregationSpec( - BucketSortPipelineAggregationBuilder.NAME, - BucketSortPipelineAggregationBuilder::new, - BucketSortPipelineAggregator::new, - BucketSortPipelineAggregationBuilder::parse)); + BucketSortPipelineAggregationBuilder.NAME, + BucketSortPipelineAggregationBuilder::new, + BucketSortPipelineAggregator::new, + BucketSortPipelineAggregationBuilder::parse)); registerPipelineAggregation(new PipelineAggregationSpec( - SerialDiffPipelineAggregationBuilder.NAME, - SerialDiffPipelineAggregationBuilder::new, - SerialDiffPipelineAggregator::new, - SerialDiffPipelineAggregationBuilder::parse)); + SerialDiffPipelineAggregationBuilder.NAME, + SerialDiffPipelineAggregationBuilder::new, + SerialDiffPipelineAggregator::new, + SerialDiffPipelineAggregationBuilder::parse)); registerPipelineAggregation(new PipelineAggregationSpec( - MovFnPipelineAggregationBuilder.NAME, - MovFnPipelineAggregationBuilder::new, - MovFnPipelineAggregator::new, - MovFnPipelineAggregationBuilder.PARSER)); + MovFnPipelineAggregationBuilder.NAME, + MovFnPipelineAggregationBuilder::new, + MovFnPipelineAggregator::new, + MovFnPipelineAggregationBuilder.PARSER)); registerFromPlugin(plugins, SearchPlugin::getPipelineAggregations, this::registerPipelineAggregation); } @@ -650,23 +647,17 @@ public class SearchModule { private void registerPipelineAggregation(PipelineAggregationSpec spec) { if (false == transportClient) { namedXContents.add(new NamedXContentRegistry.Entry(BaseAggregationBuilder.class, spec.getName(), - (p, c) -> spec.getParser().parse(p, (String) c))); + (p, c) -> spec.getParser().parse(p, (String) c))); } namedWriteables.add( - new NamedWriteableRegistry.Entry(PipelineAggregationBuilder.class, spec.getName().getPreferredName(), spec.getReader())); + new NamedWriteableRegistry.Entry(PipelineAggregationBuilder.class, spec.getName().getPreferredName(), spec.getReader())); if (spec.getAggregatorReader() != null) { namedWriteables.add(new NamedWriteableRegistry.Entry( PipelineAggregator.class, spec.getName().getPreferredName(), spec.getAggregatorReader())); } for (Map.Entry> resultReader : spec.getResultReaders().entrySet()) { namedWriteables - .add(new NamedWriteableRegistry.Entry(InternalAggregation.class, resultReader.getKey(), resultReader.getValue())); - } - } - - private void registerShapes() { - if (ShapesAvailability.JTS_AVAILABLE && ShapesAvailability.SPATIAL4J_AVAILABLE) { - namedWriteables.addAll(GeoShapeType.getShapeWriteables()); + .add(new NamedWriteableRegistry.Entry(InternalAggregation.class, resultReader.getKey(), resultReader.getValue())); } } @@ -720,9 +711,9 @@ public class SearchModule { private void registerSuggester(SuggesterSpec suggester) { namedWriteables.add(new NamedWriteableRegistry.Entry( - SuggestionBuilder.class, suggester.getName().getPreferredName(), suggester.getReader())); + SuggestionBuilder.class, suggester.getName().getPreferredName(), suggester.getReader())); namedXContents.add(new NamedXContentRegistry.Entry(SuggestionBuilder.class, suggester.getName(), - suggester.getParser())); + suggester.getParser())); namedWriteables.add(new NamedWriteableRegistry.Entry( Suggest.Suggestion.class, suggester.getName().getPreferredName(), suggester.getSuggestionReader() @@ -744,18 +735,18 @@ public class SearchModule { namedWriteables.add(new NamedWriteableRegistry.Entry( ScriptScoreFunctionBuilder.class, ScriptScoreFunctionBuilder.NAME, ScriptScoreFunctionBuilder::new)); registerScoreFunction(new ScoreFunctionSpec<>(ScriptScoreFunctionBuilder.NAME, ScriptScoreFunctionBuilder::new, - ScriptScoreFunctionBuilder::fromXContent)); + ScriptScoreFunctionBuilder::fromXContent)); registerScoreFunction( - new ScoreFunctionSpec<>(GaussDecayFunctionBuilder.NAME, GaussDecayFunctionBuilder::new, GaussDecayFunctionBuilder.PARSER)); + new ScoreFunctionSpec<>(GaussDecayFunctionBuilder.NAME, GaussDecayFunctionBuilder::new, GaussDecayFunctionBuilder.PARSER)); registerScoreFunction(new ScoreFunctionSpec<>(LinearDecayFunctionBuilder.NAME, LinearDecayFunctionBuilder::new, - LinearDecayFunctionBuilder.PARSER)); + LinearDecayFunctionBuilder.PARSER)); registerScoreFunction(new ScoreFunctionSpec<>(ExponentialDecayFunctionBuilder.NAME, ExponentialDecayFunctionBuilder::new, - ExponentialDecayFunctionBuilder.PARSER)); + ExponentialDecayFunctionBuilder.PARSER)); registerScoreFunction(new ScoreFunctionSpec<>(RandomScoreFunctionBuilder.NAME, RandomScoreFunctionBuilder::new, - RandomScoreFunctionBuilder::fromXContent)); + RandomScoreFunctionBuilder::fromXContent)); registerScoreFunction(new ScoreFunctionSpec<>(FieldValueFactorFunctionBuilder.NAME, FieldValueFactorFunctionBuilder::new, - FieldValueFactorFunctionBuilder::fromXContent)); + FieldValueFactorFunctionBuilder::fromXContent)); //weight doesn't have its own parser, so every function supports it out of the box. //Can be a single function too when not associated to any other function, which is why it needs to be registered manually here. @@ -766,11 +757,11 @@ public class SearchModule { private void registerScoreFunction(ScoreFunctionSpec scoreFunction) { namedWriteables.add(new NamedWriteableRegistry.Entry( - ScoreFunctionBuilder.class, scoreFunction.getName().getPreferredName(), scoreFunction.getReader())); + ScoreFunctionBuilder.class, scoreFunction.getName().getPreferredName(), scoreFunction.getReader())); // TODO remove funky contexts namedXContents.add(new NamedXContentRegistry.Entry( - ScoreFunctionBuilder.class, scoreFunction.getName(), - (XContentParser p, Object c) -> scoreFunction.getParser().fromXContent(p))); + ScoreFunctionBuilder.class, scoreFunction.getName(), + (XContentParser p, Object c) -> scoreFunction.getParser().fromXContent(p))); } private void registerValueFormats() { @@ -824,7 +815,7 @@ public class SearchModule { private void registerMovingAverageModel(SearchExtensionSpec movAvgModel) { movingAverageModelParserRegistry.register(movAvgModel.getParser(), movAvgModel.getName()); namedWriteables.add( - new NamedWriteableRegistry.Entry(MovAvgModel.class, movAvgModel.getName().getPreferredName(), movAvgModel.getReader())); + new NamedWriteableRegistry.Entry(MovAvgModel.class, movAvgModel.getName().getPreferredName(), movAvgModel.getReader())); } private void registerFetchSubPhases(List plugins) { @@ -864,7 +855,7 @@ public class SearchModule { registerQuery(new QuerySpec<>(MatchQueryBuilder.NAME, MatchQueryBuilder::new, MatchQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(MatchPhraseQueryBuilder.NAME, MatchPhraseQueryBuilder::new, MatchPhraseQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(MatchPhrasePrefixQueryBuilder.NAME, MatchPhrasePrefixQueryBuilder::new, - MatchPhrasePrefixQueryBuilder::fromXContent)); + MatchPhrasePrefixQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(MultiMatchQueryBuilder.NAME, MultiMatchQueryBuilder::new, MultiMatchQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(CombinedFieldsQueryBuilder.NAME, CombinedFieldsQueryBuilder::new, CombinedFieldsQueryBuilder::fromXContent)); @@ -885,35 +876,35 @@ public class SearchModule { registerQuery(new QuerySpec<>(PrefixQueryBuilder.NAME, PrefixQueryBuilder::new, PrefixQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(WildcardQueryBuilder.NAME, WildcardQueryBuilder::new, WildcardQueryBuilder::fromXContent)); registerQuery( - new QuerySpec<>(ConstantScoreQueryBuilder.NAME, ConstantScoreQueryBuilder::new, ConstantScoreQueryBuilder::fromXContent)); + new QuerySpec<>(ConstantScoreQueryBuilder.NAME, ConstantScoreQueryBuilder::new, ConstantScoreQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(SpanTermQueryBuilder.NAME, SpanTermQueryBuilder::new, SpanTermQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(SpanNotQueryBuilder.NAME, SpanNotQueryBuilder::new, SpanNotQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(SpanWithinQueryBuilder.NAME, SpanWithinQueryBuilder::new, SpanWithinQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(SpanContainingQueryBuilder.NAME, SpanContainingQueryBuilder::new, - SpanContainingQueryBuilder::fromXContent)); + SpanContainingQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(FieldMaskingSpanQueryBuilder.NAME, FieldMaskingSpanQueryBuilder::new, - FieldMaskingSpanQueryBuilder::fromXContent)); + FieldMaskingSpanQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(SpanFirstQueryBuilder.NAME, SpanFirstQueryBuilder::new, SpanFirstQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(SpanNearQueryBuilder.NAME, SpanNearQueryBuilder::new, SpanNearQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(SpanGapQueryBuilder.NAME, SpanGapQueryBuilder::new, SpanGapQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(SpanOrQueryBuilder.NAME, SpanOrQueryBuilder::new, SpanOrQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(MoreLikeThisQueryBuilder.NAME, MoreLikeThisQueryBuilder::new, - MoreLikeThisQueryBuilder::fromXContent)); + MoreLikeThisQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(WrapperQueryBuilder.NAME, WrapperQueryBuilder::new, WrapperQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(new ParseField(CommonTermsQueryBuilder.NAME).withAllDeprecated(COMMON_TERMS_QUERY_DEPRECATION_MSG), - CommonTermsQueryBuilder::new, CommonTermsQueryBuilder::fromXContent)); + CommonTermsQueryBuilder::new, CommonTermsQueryBuilder::fromXContent)); registerQuery( - new QuerySpec<>(SpanMultiTermQueryBuilder.NAME, SpanMultiTermQueryBuilder::new, SpanMultiTermQueryBuilder::fromXContent)); + new QuerySpec<>(SpanMultiTermQueryBuilder.NAME, SpanMultiTermQueryBuilder::new, SpanMultiTermQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(FunctionScoreQueryBuilder.NAME, FunctionScoreQueryBuilder::new, - FunctionScoreQueryBuilder::fromXContent)); + FunctionScoreQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(ScriptScoreQueryBuilder.NAME, ScriptScoreQueryBuilder::new, ScriptScoreQueryBuilder::fromXContent)); registerQuery( - new QuerySpec<>(SimpleQueryStringBuilder.NAME, SimpleQueryStringBuilder::new, SimpleQueryStringBuilder::fromXContent)); + new QuerySpec<>(SimpleQueryStringBuilder.NAME, SimpleQueryStringBuilder::new, SimpleQueryStringBuilder::fromXContent)); registerQuery(new QuerySpec<>(TypeQueryBuilder.NAME, TypeQueryBuilder::new, TypeQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(ScriptQueryBuilder.NAME, ScriptQueryBuilder::new, ScriptQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(GeoDistanceQueryBuilder.NAME, GeoDistanceQueryBuilder::new, GeoDistanceQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(GeoBoundingBoxQueryBuilder.NAME, GeoBoundingBoxQueryBuilder::new, - GeoBoundingBoxQueryBuilder::fromXContent)); + GeoBoundingBoxQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>( (new ParseField(GeoPolygonQueryBuilder.NAME).withAllDeprecated(GeoPolygonQueryBuilder.GEO_POLYGON_DEPRECATION_MSG)), GeoPolygonQueryBuilder::new, GeoPolygonQueryBuilder::fromXContent)); @@ -925,10 +916,8 @@ public class SearchModule { DistanceFeatureQueryBuilder::fromXContent)); registerQuery( new QuerySpec<>(MatchBoolPrefixQueryBuilder.NAME, MatchBoolPrefixQueryBuilder::new, MatchBoolPrefixQueryBuilder::fromXContent)); + registerQuery(new QuerySpec<>(GeoShapeQueryBuilder.NAME, GeoShapeQueryBuilder::new, GeoShapeQueryBuilder::fromXContent)); - if (ShapesAvailability.JTS_AVAILABLE && ShapesAvailability.SPATIAL4J_AVAILABLE) { - registerQuery(new QuerySpec<>(GeoShapeQueryBuilder.NAME, GeoShapeQueryBuilder::new, GeoShapeQueryBuilder::fromXContent)); - } registerFromPlugin(plugins, SearchPlugin::getQueries, this::registerQuery); } @@ -955,24 +944,24 @@ public class SearchModule { public static List getIntervalsSourceProviderNamedWritables() { return unmodifiableList(Arrays.asList( - new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Match.NAME, - IntervalsSourceProvider.Match::new), - new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Combine.NAME, - IntervalsSourceProvider.Combine::new), - new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Disjunction.NAME, - IntervalsSourceProvider.Disjunction::new), - new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Prefix.NAME, - IntervalsSourceProvider.Prefix::new), - new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Wildcard.NAME, - IntervalsSourceProvider.Wildcard::new), - new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Fuzzy.NAME, - IntervalsSourceProvider.Fuzzy::new))); + new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Match.NAME, + IntervalsSourceProvider.Match::new), + new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Combine.NAME, + IntervalsSourceProvider.Combine::new), + new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Disjunction.NAME, + IntervalsSourceProvider.Disjunction::new), + new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Prefix.NAME, + IntervalsSourceProvider.Prefix::new), + new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Wildcard.NAME, + IntervalsSourceProvider.Wildcard::new), + new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class, IntervalsSourceProvider.Fuzzy.NAME, + IntervalsSourceProvider.Fuzzy::new))); } private void registerQuery(QuerySpec spec) { namedWriteables.add(new NamedWriteableRegistry.Entry(QueryBuilder.class, spec.getName().getPreferredName(), spec.getReader())); namedXContents.add(new NamedXContentRegistry.Entry(QueryBuilder.class, spec.getName(), - (p, c) -> spec.getParser().fromXContent(p))); + (p, c) -> spec.getParser().fromXContent(p))); } private void registerBoolQuery(ParseField name, Writeable.Reader reader) { diff --git a/server/src/test/java/org/elasticsearch/common/geo/GeoJsonParserTests.java b/server/src/test/java/org/elasticsearch/common/geo/GeoJsonParserTests.java index 287e150e98ad..57504c7c0da7 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/GeoJsonParserTests.java +++ b/server/src/test/java/org/elasticsearch/common/geo/GeoJsonParserTests.java @@ -26,6 +26,7 @@ import org.elasticsearch.geometry.Point; import org.elasticsearch.geometry.Polygon; import org.elasticsearch.geometry.Rectangle; import org.elasticsearch.geometry.utils.GeographyValidator; +import org.elasticsearch.test.ESTestCase; import java.io.IOException; import java.text.ParseException; @@ -36,9 +37,8 @@ import java.util.Collections; /** * Tests for {@code GeoJSONShapeParser} */ -public class GeoJsonParserTests extends BaseGeoParsingTestCase { +public class GeoJsonParserTests extends ESTestCase { - @Override public void testParsePoint() throws IOException { XContentBuilder pointGeoJson = XContentFactory.jsonBuilder() .startObject() @@ -49,7 +49,6 @@ public class GeoJsonParserTests extends BaseGeoParsingTestCase { assertGeometryEquals(expected, pointGeoJson); } - @Override public void testParseLineString() throws IOException { XContentBuilder lineGeoJson = XContentFactory.jsonBuilder() .startObject() @@ -67,7 +66,6 @@ public class GeoJsonParserTests extends BaseGeoParsingTestCase { } } - @Override public void testParseMultiLineString() throws IOException { XContentBuilder multilinesGeoJson = XContentFactory.jsonBuilder() .startObject() @@ -138,7 +136,6 @@ public class GeoJsonParserTests extends BaseGeoParsingTestCase { } } - @Override public void testParseEnvelope() throws IOException { // test #1: envelope with expected coordinate order (TopLeft, BottomRight) XContentBuilder multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", randomBoolean() ? "envelope" : "bbox") @@ -189,7 +186,6 @@ public class GeoJsonParserTests extends BaseGeoParsingTestCase { } } - @Override public void testParsePolygon() throws IOException { XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() .startObject() @@ -516,7 +512,6 @@ public class GeoJsonParserTests extends BaseGeoParsingTestCase { assertGeometryEquals(p, polygonGeoJson); } - @Override public void testParseMultiPoint() throws IOException { XContentBuilder multiPointGeoJson = XContentFactory.jsonBuilder() .startObject() @@ -532,7 +527,6 @@ public class GeoJsonParserTests extends BaseGeoParsingTestCase { new Point(101, 1))), multiPointGeoJson); } - @Override public void testParseMultiPolygon() throws IOException { // two polygons; one without hole, one with hole XContentBuilder multiPolygonGeoJson = XContentFactory.jsonBuilder() @@ -580,7 +574,6 @@ public class GeoJsonParserTests extends BaseGeoParsingTestCase { assertGeometryEquals(polygons, multiPolygonGeoJson); } - @Override public void testParseGeometryCollection() throws IOException { XContentBuilder geometryCollectionGeoJson = XContentFactory.jsonBuilder() .startObject() @@ -641,7 +634,7 @@ public class GeoJsonParserTests extends BaseGeoParsingTestCase { .endObject(); Point expectedPt = new Point(100, 0); - assertGeometryEquals(expectedPt, pointGeoJson, false); + assertGeometryEquals(expectedPt, pointGeoJson); } public void testParseOrientationOption() throws IOException { @@ -763,4 +756,11 @@ public class GeoJsonParserTests extends BaseGeoParsingTestCase { assertNull(parser.nextToken()); // no more elements afterwards } } + + private void assertGeometryEquals(org.elasticsearch.geometry.Geometry expected, XContentBuilder geoJson) throws IOException { + try (XContentParser parser = createParser(geoJson)) { + parser.nextToken(); + assertEquals(expected, GeoJson.fromXContent(GeographyValidator.instance(false), false, true, parser)); + } + } } diff --git a/server/src/test/java/org/elasticsearch/common/geo/GeoJsonShapeParserTests.java b/server/src/test/java/org/elasticsearch/common/geo/GeoJsonShapeParserTests.java deleted file mode 100644 index 0f3d064c0c6a..000000000000 --- a/server/src/test/java/org/elasticsearch/common/geo/GeoJsonShapeParserTests.java +++ /dev/null @@ -1,1420 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -package org.elasticsearch.common.geo; - -import org.elasticsearch.ElasticsearchParseException; -import org.elasticsearch.Version; -import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.UUIDs; -import org.elasticsearch.common.geo.parsers.ShapeParser; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.json.JsonXContent; -import org.elasticsearch.geometry.Geometry; -import org.elasticsearch.geometry.GeometryCollection; -import org.elasticsearch.geometry.Line; -import org.elasticsearch.geometry.MultiLine; -import org.elasticsearch.geometry.MultiPoint; -import org.elasticsearch.index.mapper.GeoShapeIndexer; -import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper; -import org.elasticsearch.index.mapper.MapperBuilderContext; -import org.elasticsearch.test.VersionUtils; -import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.geom.LinearRing; -import org.locationtech.jts.geom.MultiLineString; -import org.locationtech.jts.geom.MultiPolygon; -import org.locationtech.jts.geom.Point; -import org.locationtech.jts.geom.Polygon; -import org.locationtech.spatial4j.exception.InvalidShapeException; -import org.locationtech.spatial4j.shape.Circle; -import org.locationtech.spatial4j.shape.Rectangle; -import org.locationtech.spatial4j.shape.Shape; -import org.locationtech.spatial4j.shape.ShapeCollection; -import org.locationtech.spatial4j.shape.jts.JtsPoint; - -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.elasticsearch.common.geo.builders.ShapeBuilder.SPATIAL_CONTEXT; - - -/** - * Tests for {@code GeoJSONShapeParser} - */ -public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase { - - @Override - public void testParsePoint() throws IOException, ParseException { - XContentBuilder pointGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Point") - .startArray("coordinates").value(100.0).value(0.0).endArray() - .endObject(); - Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0)); - assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson, true); - assertGeometryEquals(new org.elasticsearch.geometry.Point(100d, 0d), pointGeoJson, false); - } - - @Override - public void testParseLineString() throws IOException, ParseException { - XContentBuilder lineGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "LineString") - .startArray("coordinates") - .startArray().value(100.0).value(0.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .endArray() - .endObject(); - - List lineCoordinates = new ArrayList<>(); - lineCoordinates.add(new Coordinate(100, 0)); - lineCoordinates.add(new Coordinate(101, 1)); - - try (XContentParser parser = createParser(lineGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertLineString(shape, true); - } - - try (XContentParser parser = createParser(lineGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertLineString(parse(parser), false); - } - } - - @Override - public void testParseMultiLineString() throws IOException, ParseException { - XContentBuilder multilinesGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "MultiLineString") - .startArray("coordinates") - .startArray() - .startArray().value(100.0).value(0.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .endArray() - .startArray() - .startArray().value(102.0).value(2.0).endArray() - .startArray().value(103.0).value(3.0).endArray() - .endArray() - .endArray() - .endObject(); - - MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString(new LineString[]{ - GEOMETRY_FACTORY.createLineString(new Coordinate[]{ - new Coordinate(100, 0), - new Coordinate(101, 1), - }), - GEOMETRY_FACTORY.createLineString(new Coordinate[]{ - new Coordinate(102, 2), - new Coordinate(103, 3), - }), - }); - assertGeometryEquals(jtsGeom(expected), multilinesGeoJson, true); - assertGeometryEquals(new MultiLine(Arrays.asList( - new Line(new double[] {100d, 101d}, new double[] {0d, 1d}), - new Line(new double[] {102d, 103d}, new double[] {2d, 3d}))), - multilinesGeoJson, false); - } - - public void testParseCircle() throws IOException, ParseException { - XContentBuilder multilinesGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "circle") - .startArray("coordinates").value(100.0).value(0.0).endArray() - .field("radius", "100m") - .endObject(); - - Circle expected = SPATIAL_CONTEXT.makeCircle(100.0, 0.0, 360 * 100 / GeoUtils.EARTH_EQUATOR); - assertGeometryEquals(expected, multilinesGeoJson, true); - } - - public void testParseMultiDimensionShapes() throws IOException { - // multi dimension point - XContentBuilder pointGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Point") - .startArray("coordinates").value(100.0).value(0.0).value(15.0).value(18.0).endArray() - .endObject(); - - XContentParser parser = createParser(pointGeoJson); - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - - // multi dimension linestring - XContentBuilder lineGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "LineString") - .startArray("coordinates") - .startArray().value(100.0).value(0.0).value(15.0).endArray() - .startArray().value(101.0).value(1.0).value(18.0).value(19.0).endArray() - .endArray() - .endObject(); - - parser = createParser(lineGeoJson); - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - - @Override - public void testParseEnvelope() throws IOException, ParseException { - // test #1: envelope with expected coordinate order (TopLeft, BottomRight) - XContentBuilder multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope") - .startArray("coordinates") - .startArray().value(-50).value(30).endArray() - .startArray().value(50).value(-30).endArray() - .endArray() - .endObject(); - Rectangle expected = SPATIAL_CONTEXT.makeRectangle(-50, 50, -30, 30); - assertGeometryEquals(expected, multilinesGeoJson, true); - assertGeometryEquals(new org.elasticsearch.geometry.Rectangle(-50, 50, 30, -30), - multilinesGeoJson, false); - - // test #2: envelope that spans dateline - multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope") - .startArray("coordinates") - .startArray().value(50).value(30).endArray() - .startArray().value(-50).value(-30).endArray() - .endArray() - .endObject(); - - expected = SPATIAL_CONTEXT.makeRectangle(50, -50, -30, 30); - assertGeometryEquals(expected, multilinesGeoJson, true); - assertGeometryEquals(new org.elasticsearch.geometry.Rectangle(50, -50, 30, -30), - multilinesGeoJson, false); - - // test #3: "envelope" (actually a triangle) with invalid number of coordinates (TopRight, BottomLeft, BottomRight) - multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope") - .startArray("coordinates") - .startArray().value(50).value(30).endArray() - .startArray().value(-50).value(-30).endArray() - .startArray().value(50).value(-39).endArray() - .endArray() - .endObject(); - try (XContentParser parser = createParser(multilinesGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - - // test #4: "envelope" with empty coordinates - multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope") - .startArray("coordinates") - .endArray() - .endObject(); - try (XContentParser parser = createParser(multilinesGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - } - - @Override - public void testParsePolygon() throws IOException, ParseException { - XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(100.0).value(1.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .startArray().value(101.0).value(0.0).endArray() - .startArray().value(100.0).value(0.0).endArray() - .startArray().value(100.0).value(1.0).endArray() - .endArray() - .endArray() - .endObject(); - - List shellCoordinates = new ArrayList<>(); - shellCoordinates.add(new Coordinate(100, 0)); - shellCoordinates.add(new Coordinate(101, 0)); - shellCoordinates.add(new Coordinate(101, 1)); - shellCoordinates.add(new Coordinate(100, 1)); - shellCoordinates.add(new Coordinate(100, 0)); - Coordinate[] coordinates = shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]); - LinearRing shell = GEOMETRY_FACTORY.createLinearRing(coordinates); - Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null); - assertGeometryEquals(jtsGeom(expected), polygonGeoJson, true); - - org.elasticsearch.geometry.Polygon p = new org.elasticsearch.geometry.Polygon( - new org.elasticsearch.geometry.LinearRing( - new double[] {100d, 101d, 101d, 100d, 100d}, new double[] {0d, 0d, 1d, 1d, 0d} - )); - assertGeometryEquals(p, polygonGeoJson, false); - } - - public void testParse3DPolygon() throws IOException, ParseException { - XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(100.0).value(1.0).value(10.0).endArray() - .startArray().value(101.0).value(1.0).value(10.0).endArray() - .startArray().value(101.0).value(0.0).value(10.0).endArray() - .startArray().value(100.0).value(0.0).value(10.0).endArray() - .startArray().value(100.0).value(1.0).value(10.0).endArray() - .endArray() - .endArray() - .endObject(); - - List shellCoordinates = new ArrayList<>(); - shellCoordinates.add(new Coordinate(100, 0, 10)); - shellCoordinates.add(new Coordinate(101, 0, 10)); - shellCoordinates.add(new Coordinate(101, 1, 10)); - shellCoordinates.add(new Coordinate(100, 1, 10)); - shellCoordinates.add(new Coordinate(100, 0, 10)); - Coordinate[] coordinates = shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]); - - Version randomVersion = VersionUtils.randomVersionBetween(random(), Version.V_6_0_0, Version.CURRENT); - Settings indexSettings = Settings.builder() - .put(IndexMetadata.SETTING_VERSION_CREATED, randomVersion) - .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0) - .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetadata.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build(); - - LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); - Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null); - final LegacyGeoShapeFieldMapper mapperBuilder = - new LegacyGeoShapeFieldMapper.Builder("test", Version.CURRENT, false, true) - .build(MapperBuilderContext.ROOT); - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertEquals(jtsGeom(expected), ShapeParser.parse(parser, mapperBuilder).buildS4J()); - } - - org.elasticsearch.geometry.Polygon p = new org.elasticsearch.geometry.Polygon(new org.elasticsearch.geometry.LinearRing( - Arrays.stream(coordinates).mapToDouble(i->i.x).toArray(), Arrays.stream(coordinates).mapToDouble(i->i.y).toArray() - )); - assertGeometryEquals(p, polygonGeoJson, false); - } - - public void testInvalidDimensionalPolygon() throws IOException { - XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(100.0).value(1.0).value(10.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .startArray().value(101.0).value(0.0).value(10.0).endArray() - .startArray().value(100.0).value(0.0).value(10.0).endArray() - .startArray().value(100.0).value(1.0).value(10.0).endArray() - .endArray() - .endArray() - .endObject(); - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - } - - public void testParseInvalidPoint() throws IOException { - // test case 1: create an invalid point object with multipoint data format - XContentBuilder invalidPoint1 = XContentFactory.jsonBuilder() - .startObject() - .field("type", "point") - .startArray("coordinates") - .startArray().value(-74.011).value(40.753).endArray() - .endArray() - .endObject(); - try (XContentParser parser = createParser(invalidPoint1)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - - // test case 2: create an invalid point object with an empty number of coordinates - XContentBuilder invalidPoint2 = XContentFactory.jsonBuilder() - .startObject() - .field("type", "point") - .startArray("coordinates") - .endArray() - .endObject(); - try (XContentParser parser = createParser(invalidPoint2)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - } - - public void testParseInvalidMultipoint() throws IOException { - // test case 1: create an invalid multipoint object with single coordinate - XContentBuilder invalidMultipoint1 = XContentFactory.jsonBuilder() - .startObject() - .field("type", "multipoint") - .startArray("coordinates").value(-74.011).value(40.753).endArray() - .endObject(); - try (XContentParser parser = createParser(invalidMultipoint1)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - - // test case 2: create an invalid multipoint object with null coordinate - XContentBuilder invalidMultipoint2 = XContentFactory.jsonBuilder() - .startObject() - .field("type", "multipoint") - .startArray("coordinates") - .endArray() - .endObject(); - try (XContentParser parser = createParser(invalidMultipoint2)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - - // test case 3: create a valid formatted multipoint object with invalid number (0) of coordinates - XContentBuilder invalidMultipoint3 = XContentFactory.jsonBuilder() - .startObject() - .field("type", "multipoint") - .startArray("coordinates") - .startArray().endArray() - .endArray() - .endObject(); - try (XContentParser parser = createParser(invalidMultipoint3)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - } - - public void testParseInvalidMultiPolygon() throws IOException { - // test invalid multipolygon (an "accidental" polygon with inner rings outside outer ring) - String multiPolygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "MultiPolygon") - .startArray("coordinates") - .startArray()//one poly (with two holes) - .startArray() - .startArray().value(102.0).value(2.0).endArray() - .startArray().value(103.0).value(2.0).endArray() - .startArray().value(103.0).value(3.0).endArray() - .startArray().value(102.0).value(3.0).endArray() - .startArray().value(102.0).value(2.0).endArray() - .endArray() - .startArray()// first hole - .startArray().value(100.0).value(0.0).endArray() - .startArray().value(101.0).value(0.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .startArray().value(100.0).value(1.0).endArray() - .startArray().value(100.0).value(0.0).endArray() - .endArray() - .startArray()//second hole - .startArray().value(100.2).value(0.8).endArray() - .startArray().value(100.2).value(0.2).endArray() - .startArray().value(100.8).value(0.2).endArray() - .startArray().value(100.8).value(0.8).endArray() - .startArray().value(100.2).value(0.8).endArray() - .endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, multiPolygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class); - assertNull(parser.nextToken()); - } - } - - public void testParseInvalidDimensionalMultiPolygon() throws IOException { - // test invalid multipolygon (an "accidental" polygon with inner rings outside outer ring) - String multiPolygonGeoJson = Strings.toString(XContentFactory.jsonBuilder() - .startObject() - .field("type", "MultiPolygon") - .startArray("coordinates") - .startArray()//first poly (without holes) - .startArray() - .startArray().value(102.0).value(2.0).endArray() - .startArray().value(103.0).value(2.0).endArray() - .startArray().value(103.0).value(3.0).endArray() - .startArray().value(102.0).value(3.0).endArray() - .startArray().value(102.0).value(2.0).endArray() - .endArray() - .endArray() - .startArray()//second poly (with hole) - .startArray() - .startArray().value(100.0).value(0.0).endArray() - .startArray().value(101.0).value(0.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .startArray().value(100.0).value(1.0).endArray() - .startArray().value(100.0).value(0.0).endArray() - .endArray() - .startArray()//hole - .startArray().value(100.2).value(0.8).endArray() - .startArray().value(100.2).value(0.2).value(10.0).endArray() - .startArray().value(100.8).value(0.2).endArray() - .startArray().value(100.8).value(0.8).endArray() - .startArray().value(100.2).value(0.8).endArray() - .endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, multiPolygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - } - - - public void testParseOGCPolygonWithoutHoles() throws IOException, ParseException { - // test 1: ccw poly not crossing dateline - String polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertPolygon(shape, true); - } - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); - } - - // test 2: ccw poly crossing dateline - polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); - } - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); - } - - // test 3: cw poly not crossing dateline - polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(180.0).value(10.0).endArray() - .startArray().value(180.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertPolygon(shape, true); - } - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); - } - - // test 4: cw poly crossing dateline - polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(184.0).value(15.0).endArray() - .startArray().value(184.0).value(0.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(174.0).value(-10.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); - } - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); - } - } - - public void testParseOGCPolygonWithHoles() throws IOException, ParseException { - // test 1: ccw poly not crossing dateline - String polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .startArray() - .startArray().value(-172.0).value(8.0).endArray() - .startArray().value(174.0).value(10.0).endArray() - .startArray().value(-172.0).value(-8.0).endArray() - .startArray().value(-172.0).value(8.0).endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertPolygon(shape, true); - } - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); - } - - // test 2: ccw poly crossing dateline - polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .endArray() - .startArray() - .startArray().value(178.0).value(8.0).endArray() - .startArray().value(-178.0).value(8.0).endArray() - .startArray().value(-180.0).value(-8.0).endArray() - .startArray().value(178.0).value(8.0).endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); - } - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); - } - - // test 3: cw poly not crossing dateline - polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(180.0).value(10.0).endArray() - .startArray().value(179.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .startArray() - .startArray().value(177.0).value(8.0).endArray() - .startArray().value(179.0).value(10.0).endArray() - .startArray().value(179.0).value(-8.0).endArray() - .startArray().value(177.0).value(8.0).endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertPolygon(shape, true); - } - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); - } - - // test 4: cw poly crossing dateline - polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(183.0).value(10.0).endArray() - .startArray().value(183.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(183.0).value(10.0).endArray() - .endArray() - .startArray() - .startArray().value(178.0).value(8.0).endArray() - .startArray().value(182.0).value(8.0).endArray() - .startArray().value(180.0).value(-8.0).endArray() - .startArray().value(178.0).value(8.0).endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); - } - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); - } - } - - public void testParseInvalidPolygon() throws IOException { - /** - * The following 3 test cases ensure proper error handling of invalid polygons - * per the GeoJSON specification - */ - // test case 1: create an invalid polygon with only 2 points - String invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon") - .startArray("coordinates") - .startArray() - .startArray().value(-74.011).value(40.753).endArray() - .startArray().value(-75.022).value(41.783).endArray() - .endArray() - .endArray() - .endObject()); - try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - - // test case 2: create an invalid polygon with only 1 point - invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon") - .startArray("coordinates") - .startArray() - .startArray().value(-74.011).value(40.753).endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - - // test case 3: create an invalid polygon with 0 points - invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon") - .startArray("coordinates") - .startArray() - .startArray().endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - - // test case 4: create an invalid polygon with null value points - invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon") - .startArray("coordinates") - .startArray() - .startArray().nullValue().nullValue().endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, IllegalArgumentException.class); - assertNull(parser.nextToken()); - } - - // test case 5: create an invalid polygon with 1 invalid LinearRing - invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon") - .startArray("coordinates") - .nullValue().nullValue() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, IllegalArgumentException.class); - assertNull(parser.nextToken()); - } - - // test case 6: create an invalid polygon with 0 LinearRings - invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon") - .startArray("coordinates").endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - - // test case 7: create an invalid polygon with 0 LinearRings - invalidPoly = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "polygon") - .startArray("coordinates") - .startArray().value(-74.011).value(40.753).endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, invalidPoly)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - } - - public void testParsePolygonWithHole() throws IOException, ParseException { - XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(100.0).value(1.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .startArray().value(101.0).value(0.0).endArray() - .startArray().value(100.0).value(0.0).endArray() - .startArray().value(100.0).value(1.0).endArray() - .endArray() - .startArray() - .startArray().value(100.2).value(0.8).endArray() - .startArray().value(100.2).value(0.2).endArray() - .startArray().value(100.8).value(0.2).endArray() - .startArray().value(100.8).value(0.8).endArray() - .startArray().value(100.2).value(0.8).endArray() - .endArray() - .endArray() - .endObject(); - - // add 3d point to test ISSUE #10501 - List shellCoordinates = new ArrayList<>(); - shellCoordinates.add(new Coordinate(100, 0, 15.0)); - shellCoordinates.add(new Coordinate(101, 0)); - shellCoordinates.add(new Coordinate(101, 1)); - shellCoordinates.add(new Coordinate(100, 1, 10.0)); - shellCoordinates.add(new Coordinate(100, 0)); - - List holeCoordinates = new ArrayList<>(); - holeCoordinates.add(new Coordinate(100.2, 0.2)); - holeCoordinates.add(new Coordinate(100.8, 0.2)); - holeCoordinates.add(new Coordinate(100.8, 0.8)); - holeCoordinates.add(new Coordinate(100.2, 0.8)); - holeCoordinates.add(new Coordinate(100.2, 0.2)); - - LinearRing shell = GEOMETRY_FACTORY.createLinearRing( - shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); - LinearRing[] holes = new LinearRing[1]; - holes[0] = GEOMETRY_FACTORY.createLinearRing( - holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); - Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes); - assertGeometryEquals(jtsGeom(expected), polygonGeoJson, true); - - org.elasticsearch.geometry.LinearRing hole = - new org.elasticsearch.geometry.LinearRing( - new double[] {100.8d, 100.8d, 100.2d, 100.2d, 100.8d}, new double[] {0.8d, 0.2d, 0.2d, 0.8d, 0.8d}); - org.elasticsearch.geometry.Polygon p = - new org.elasticsearch.geometry.Polygon(new org.elasticsearch.geometry.LinearRing( - new double[] {100d, 101d, 101d, 100d, 100d}, new double[] {0d, 0d, 1d, 1d, 0d}), Collections.singletonList(hole)); - assertGeometryEquals(p, polygonGeoJson, false); - } - - public void testParseSelfCrossingPolygon() throws IOException { - // test self crossing ccw poly not crossing dateline - String polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(-177.0).value(15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .endArray() - .endObject()); - - try (XContentParser parser = createParser(JsonXContent.jsonXContent, polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class); - assertNull(parser.nextToken()); - } - } - - @Override - public void testParseMultiPoint() throws IOException, ParseException { - XContentBuilder multiPointGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "MultiPoint") - .startArray("coordinates") - .startArray().value(100.0).value(0.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .endArray() - .endObject(); - ShapeCollection expected = shapeCollection( - SPATIAL_CONTEXT.makePoint(100, 0), - SPATIAL_CONTEXT.makePoint(101, 1.0)); - assertGeometryEquals(expected, multiPointGeoJson, true); - - assertGeometryEquals(new MultiPoint(Arrays.asList( - new org.elasticsearch.geometry.Point(100, 0), - new org.elasticsearch.geometry.Point(101, 1))), multiPointGeoJson, false); - } - - @Override - public void testParseMultiPolygon() throws IOException, ParseException { - // test #1: two polygons; one without hole, one with hole - XContentBuilder multiPolygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "MultiPolygon") - .startArray("coordinates") - .startArray()//first poly (without holes) - .startArray() - .startArray().value(102.0).value(2.0).endArray() - .startArray().value(103.0).value(2.0).endArray() - .startArray().value(103.0).value(3.0).endArray() - .startArray().value(102.0).value(3.0).endArray() - .startArray().value(102.0).value(2.0).endArray() - .endArray() - .endArray() - .startArray()//second poly (with hole) - .startArray() - .startArray().value(100.0).value(0.0).endArray() - .startArray().value(101.0).value(0.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .startArray().value(100.0).value(1.0).endArray() - .startArray().value(100.0).value(0.0).endArray() - .endArray() - .startArray()//hole - .startArray().value(100.2).value(0.8).endArray() - .startArray().value(100.2).value(0.2).endArray() - .startArray().value(100.8).value(0.2).endArray() - .startArray().value(100.8).value(0.8).endArray() - .startArray().value(100.2).value(0.8).endArray() - .endArray() - .endArray() - .endArray() - .endObject(); - - List shellCoordinates = new ArrayList<>(); - shellCoordinates.add(new Coordinate(100, 0)); - shellCoordinates.add(new Coordinate(101, 0)); - shellCoordinates.add(new Coordinate(101, 1)); - shellCoordinates.add(new Coordinate(100, 1)); - shellCoordinates.add(new Coordinate(100, 0)); - - List holeCoordinates = new ArrayList<>(); - holeCoordinates.add(new Coordinate(100.2, 0.2)); - holeCoordinates.add(new Coordinate(100.8, 0.2)); - holeCoordinates.add(new Coordinate(100.8, 0.8)); - holeCoordinates.add(new Coordinate(100.2, 0.8)); - holeCoordinates.add(new Coordinate(100.2, 0.2)); - - LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); - LinearRing[] holes = new LinearRing[1]; - holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); - Polygon withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes); - - shellCoordinates = new ArrayList<>(); - shellCoordinates.add(new Coordinate(102, 3)); - shellCoordinates.add(new Coordinate(103, 3)); - shellCoordinates.add(new Coordinate(103, 2)); - shellCoordinates.add(new Coordinate(102, 2)); - shellCoordinates.add(new Coordinate(102, 3)); - - shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); - Polygon withoutHoles = GEOMETRY_FACTORY.createPolygon(shell, null); - - Shape expected = shapeCollection(withoutHoles, withHoles); - - assertGeometryEquals(expected, multiPolygonGeoJson, true); - - org.elasticsearch.geometry.LinearRing hole = new org.elasticsearch.geometry.LinearRing( - new double[] {100.8d, 100.8d, 100.2d, 100.2d, 100.8d}, new double[] {0.8d, 0.2d, 0.2d, 0.8d, 0.8d}); - - org.elasticsearch.geometry.MultiPolygon polygons = new org.elasticsearch.geometry.MultiPolygon(Arrays.asList( - new org.elasticsearch.geometry.Polygon(new org.elasticsearch.geometry.LinearRing( - new double[] {103d, 103d, 102d, 102d, 103d}, new double[] {2d, 3d, 3d, 2d, 2d})), - new org.elasticsearch.geometry.Polygon(new org.elasticsearch.geometry.LinearRing( - new double[] {101d, 101d, 100d, 100d, 101d}, new double[] {0d, 1d, 1d, 0d, 0d}), Collections.singletonList(hole)))); - - assertGeometryEquals(polygons, multiPolygonGeoJson, false); - - // test #2: multipolygon; one polygon with one hole - // this test converting the multipolygon from a ShapeCollection type - // to a simple polygon (jtsGeom) - multiPolygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "MultiPolygon") - .startArray("coordinates") - .startArray() - .startArray() - .startArray().value(100.0).value(1.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .startArray().value(101.0).value(0.0).endArray() - .startArray().value(100.0).value(0.0).endArray() - .startArray().value(100.0).value(1.0).endArray() - .endArray() - .startArray() // hole - .startArray().value(100.2).value(0.8).endArray() - .startArray().value(100.2).value(0.2).endArray() - .startArray().value(100.8).value(0.2).endArray() - .startArray().value(100.8).value(0.8).endArray() - .startArray().value(100.2).value(0.8).endArray() - .endArray() - .endArray() - .endArray() - .endObject(); - - shellCoordinates = new ArrayList<>(); - shellCoordinates.add(new Coordinate(100, 1)); - shellCoordinates.add(new Coordinate(101, 1)); - shellCoordinates.add(new Coordinate(101, 0)); - shellCoordinates.add(new Coordinate(100, 0)); - shellCoordinates.add(new Coordinate(100, 1)); - - holeCoordinates = new ArrayList<>(); - holeCoordinates.add(new Coordinate(100.2, 0.8)); - holeCoordinates.add(new Coordinate(100.2, 0.2)); - holeCoordinates.add(new Coordinate(100.8, 0.2)); - holeCoordinates.add(new Coordinate(100.8, 0.8)); - holeCoordinates.add(new Coordinate(100.2, 0.8)); - - shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); - holes = new LinearRing[1]; - holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); - withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes); - - assertGeometryEquals(jtsGeom(withHoles), multiPolygonGeoJson, true); - - org.elasticsearch.geometry.LinearRing luceneHole = - new org.elasticsearch.geometry.LinearRing( - new double[] {100.8d, 100.8d, 100.2d, 100.2d, 100.8d}, new double[] {0.8d, 0.2d, 0.2d, 0.8d, 0.8d}); - - org.elasticsearch.geometry.Polygon lucenePolygons = (new org.elasticsearch.geometry.Polygon( - new org.elasticsearch.geometry.LinearRing( - new double[] {100d, 101d, 101d, 100d, 100d}, new double[] {0d, 0d, 1d, 1d, 0d}), Collections.singletonList(luceneHole))); - assertGeometryEquals(lucenePolygons, multiPolygonGeoJson, false); - } - - @Override - public void testParseGeometryCollection() throws IOException, ParseException { - XContentBuilder geometryCollectionGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "GeometryCollection") - .startArray("geometries") - .startObject() - .field("type", "LineString") - .startArray("coordinates") - .startArray().value(100.0).value(0.0).endArray() - .startArray().value(101.0).value(1.0).endArray() - .endArray() - .endObject() - .startObject() - .field("type", "Point") - .startArray("coordinates").value(102.0).value(2.0).endArray() - .endObject() - .startObject() - .field("type", "Polygon") - .startArray("coordinates") - .startArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .endArray() - .endArray() - .endObject() - .endArray() - .endObject(); - - ArrayList shellCoordinates1 = new ArrayList<>(); - shellCoordinates1.add(new Coordinate(180.0, -12.142857142857142)); - shellCoordinates1.add(new Coordinate(180.0, 12.142857142857142)); - shellCoordinates1.add(new Coordinate(176.0, 15.0)); - shellCoordinates1.add(new Coordinate(172.0, 0.0)); - shellCoordinates1.add(new Coordinate(176.0, -15)); - shellCoordinates1.add(new Coordinate(180.0, -12.142857142857142)); - - ArrayList shellCoordinates2 = new ArrayList<>(); - shellCoordinates2.add(new Coordinate(-180.0, 12.142857142857142)); - shellCoordinates2.add(new Coordinate(-180.0, -12.142857142857142)); - shellCoordinates2.add(new Coordinate(-177.0, -10.0)); - shellCoordinates2.add(new Coordinate(-177.0, 10.0)); - shellCoordinates2.add(new Coordinate(-180.0, 12.142857142857142)); - - Shape[] expected = new Shape[3]; - LineString expectedLineString = GEOMETRY_FACTORY.createLineString(new Coordinate[]{ - new Coordinate(100, 0), - new Coordinate(101, 1), - }); - expected[0] = jtsGeom(expectedLineString); - Point expectedPoint = GEOMETRY_FACTORY.createPoint(new Coordinate(102.0, 2.0)); - expected[1] = new JtsPoint(expectedPoint, SPATIAL_CONTEXT); - LinearRing shell1 = GEOMETRY_FACTORY.createLinearRing( - shellCoordinates1.toArray(new Coordinate[shellCoordinates1.size()])); - LinearRing shell2 = GEOMETRY_FACTORY.createLinearRing( - shellCoordinates2.toArray(new Coordinate[shellCoordinates2.size()])); - MultiPolygon expectedMultiPoly = GEOMETRY_FACTORY.createMultiPolygon( - new Polygon[] { - GEOMETRY_FACTORY.createPolygon(shell1), - GEOMETRY_FACTORY.createPolygon(shell2) - } - ); - expected[2] = jtsGeom(expectedMultiPoly); - - - //equals returns true only if geometries are in the same order - assertGeometryEquals(shapeCollection(expected), geometryCollectionGeoJson, true); - - GeometryCollection geometryExpected = new GeometryCollection<> (Arrays.asList( - new Line(new double[] {100d, 101d}, new double[] {0d, 1d}), - new org.elasticsearch.geometry.Point(102d, 2d), - new org.elasticsearch.geometry.MultiPolygon(Arrays.asList( - new org.elasticsearch.geometry.Polygon(new org.elasticsearch.geometry.LinearRing( - new double[] {180d, 180d, 176d, 172d, 176d, 180d}, - new double[] {-12.142857142857142d, 12.142857142857142d, 15d, 0d, -15d, -12.142857142857142d} - )), - new org.elasticsearch.geometry.Polygon(new org.elasticsearch.geometry.LinearRing( - new double[] {-180d, -180d, -177d, -177d, -180d}, - new double[] {12.142857142857142d, -12.142857142857142d, -10d, 10d, 12.142857142857142d} - )) - )) - )); - assertGeometryEquals(geometryExpected, geometryCollectionGeoJson, false); - } - - public void testThatParserExtractsCorrectTypeAndCoordinatesFromArbitraryJson() throws IOException, ParseException { - XContentBuilder pointGeoJson = XContentFactory.jsonBuilder() - .startObject() - .startObject("crs") - .field("type", "name") - .startObject("properties") - .field("name", "urn:ogc:def:crs:OGC:1.3:CRS84") - .endObject() - .endObject() - .field("bbox", "foobar") - .field("type", "point") - .field("bubu", "foobar") - .startArray("coordinates").value(100.0).value(0.0).endArray() - .startObject("nested").startArray("coordinates").value(200.0).value(0.0).endArray().endObject() - .startObject("lala").field("type", "NotAPoint").endObject() - .endObject(); - Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0)); - assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson, true); - - org.elasticsearch.geometry.Point expectedPt = new org.elasticsearch.geometry.Point(100, 0); - assertGeometryEquals(expectedPt, pointGeoJson, false); - } - - public void testParseOrientationOption() throws IOException, ParseException { - // test 1: valid ccw (right handed system) poly not crossing dateline (with 'right' field) - XContentBuilder polygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Polygon") - .field("orientation", "right") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .startArray() - .startArray().value(-172.0).value(8.0).endArray() - .startArray().value(174.0).value(10.0).endArray() - .startArray().value(-172.0).value(-8.0).endArray() - .startArray().value(-172.0).value(8.0).endArray() - .endArray() - .endArray() - .endObject(); - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertPolygon(shape, true); - } - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); - } - - // test 2: valid ccw (right handed system) poly not crossing dateline (with 'ccw' field) - polygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Polygon") - .field("orientation", "ccw") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .startArray() - .startArray().value(-172.0).value(8.0).endArray() - .startArray().value(174.0).value(10.0).endArray() - .startArray().value(-172.0).value(-8.0).endArray() - .startArray().value(-172.0).value(8.0).endArray() - .endArray() - .endArray() - .endObject(); - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertPolygon(shape, true); - } - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); - } - - // test 3: valid ccw (right handed system) poly not crossing dateline (with 'counterclockwise' field) - polygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Polygon") - .field("orientation", "counterclockwise") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .startArray() - .startArray().value(-172.0).value(8.0).endArray() - .startArray().value(174.0).value(10.0).endArray() - .startArray().value(-172.0).value(-8.0).endArray() - .startArray().value(-172.0).value(8.0).endArray() - .endArray() - .endArray() - .endObject(); - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertPolygon(shape, true); - } - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertPolygon(parse(parser), false); - } - - // test 4: valid cw (left handed system) poly crossing dateline (with 'left' field) - polygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Polygon") - .field("orientation", "left") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .startArray() - .startArray().value(-178.0).value(8.0).endArray() - .startArray().value(178.0).value(8.0).endArray() - .startArray().value(180.0).value(-8.0).endArray() - .startArray().value(-178.0).value(8.0).endArray() - .endArray() - .endArray() - .endObject(); - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); - } - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); - } - - // test 5: valid cw multipoly (left handed system) poly crossing dateline (with 'cw' field) - polygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Polygon") - .field("orientation", "cw") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .startArray() - .startArray().value(-178.0).value(8.0).endArray() - .startArray().value(178.0).value(8.0).endArray() - .startArray().value(180.0).value(-8.0).endArray() - .startArray().value(-178.0).value(8.0).endArray() - .endArray() - .endArray() - .endObject(); - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); - } - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); - } - - // test 6: valid cw multipoly (left handed system) poly crossing dateline (with 'clockwise' field) - polygonGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Polygon") - .field("orientation", "clockwise") - .startArray("coordinates") - .startArray() - .startArray().value(176.0).value(15.0).endArray() - .startArray().value(-177.0).value(10.0).endArray() - .startArray().value(-177.0).value(-10.0).endArray() - .startArray().value(176.0).value(-15.0).endArray() - .startArray().value(172.0).value(0.0).endArray() - .startArray().value(176.0).value(15.0).endArray() - .endArray() - .startArray() - .startArray().value(-178.0).value(8.0).endArray() - .startArray().value(178.0).value(8.0).endArray() - .startArray().value(180.0).value(-8.0).endArray() - .startArray().value(-178.0).value(8.0).endArray() - .endArray() - .endArray() - .endObject(); - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - Shape shape = ShapeParser.parse(parser).buildS4J(); - ElasticsearchGeoAssertions.assertMultiPolygon(shape, true); - } - - try (XContentParser parser = createParser(polygonGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertMultiPolygon(parse(parser), false); - } - } - - public void testParseInvalidShapes() throws IOException { - // single dimensions point - XContentBuilder tooLittlePointGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Point") - .startArray("coordinates").value(10.0).endArray() - .endObject(); - - try (XContentParser parser = createParser(tooLittlePointGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - - // zero dimensions point - XContentBuilder emptyPointGeoJson = XContentFactory.jsonBuilder() - .startObject() - .field("type", "Point") - .startObject("coordinates").field("foo", "bar").endObject() - .endObject(); - - try (XContentParser parser = createParser(emptyPointGeoJson)) { - parser.nextToken(); - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertNull(parser.nextToken()); - } - } - - public void testParseInvalidGeometryCollectionShapes() throws IOException { - // single dimensions point - XContentBuilder invalidPoints = XContentFactory.jsonBuilder() - .startObject() - .startObject("foo") - .field("type", "geometrycollection") - .startArray("geometries") - .startObject() - .field("type", "polygon") - .startArray("coordinates") - .startArray().value("46.6022226498514").value("24.7237442867977").endArray() - .startArray().value("46.6031857243798").value("24.722968774929").endArray() - .endArray() // coordinates - .endObject() - .endArray() // geometries - .endObject() - .endObject(); - try (XContentParser parser = createParser(invalidPoints)) { - parser.nextToken(); // foo - parser.nextToken(); // start object - parser.nextToken(); // start object - ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class); - assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); // end of the document - assertNull(parser.nextToken()); // no more elements afterwards - } - } - - public Geometry parse(XContentParser parser) throws IOException, ParseException { - GeometryParser geometryParser = new GeometryParser(true, true, true); - GeoShapeIndexer indexer = new GeoShapeIndexer(true, "name"); - return indexer.prepareForIndexing(geometryParser.parse(parser)); - } -} diff --git a/server/src/test/java/org/elasticsearch/common/geo/GeometryIOTests.java b/server/src/test/java/org/elasticsearch/common/geo/GeometryIOTests.java index 96dba312020b..fe73c27618be 100644 --- a/server/src/test/java/org/elasticsearch/common/geo/GeometryIOTests.java +++ b/server/src/test/java/org/elasticsearch/common/geo/GeometryIOTests.java @@ -8,18 +8,12 @@ package org.elasticsearch.common.geo; -import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.io.stream.BytesStreamOutput; -import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput; -import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.geometry.Geometry; -import org.elasticsearch.geometry.GeometryCollection; -import org.elasticsearch.geometry.ShapeType; import org.elasticsearch.test.ESTestCase; import static org.elasticsearch.geo.GeometryTestUtils.randomGeometry; -import static org.elasticsearch.index.query.LegacyGeoShapeQueryProcessor.geometryToShapeBuilder; public class GeometryIOTests extends ESTestCase { @@ -27,69 +21,16 @@ public class GeometryIOTests extends ESTestCase { for (int i = 0; i < randomIntBetween(1, 20); i++) { boolean hasAlt = randomBoolean(); Geometry geometry = randomGeometry(hasAlt); - if (shapeSupported(geometry) && randomBoolean()) { - // Shape builder conversion doesn't support altitude - ShapeBuilder shapeBuilder = geometryToShapeBuilder(geometry); - if (randomBoolean()) { - Geometry actual = shapeBuilder.buildGeometry(); + // Test Geometry -> Geometry + try (BytesStreamOutput out = new BytesStreamOutput()) { + GeometryIO.writeGeometry(out, geometry); + ; + try (StreamInput in = out.bytes().streamInput()) { + Geometry actual = GeometryIO.readGeometry(in); assertEquals(geometry, actual); - } - if (randomBoolean()) { - // Test ShapeBuilder -> Geometry Serialization - try (BytesStreamOutput out = new BytesStreamOutput()) { - out.writeNamedWriteable(shapeBuilder); - try (StreamInput in = out.bytes().streamInput()) { - Geometry actual = GeometryIO.readGeometry(in); - assertEquals(geometry, actual); - assertEquals(0, in.available()); - } - } - } else { - // Test Geometry -> ShapeBuilder Serialization - try (BytesStreamOutput out = new BytesStreamOutput()) { - GeometryIO.writeGeometry(out, geometry); - try (StreamInput in = out.bytes().streamInput()) { - try (StreamInput nin = new NamedWriteableAwareStreamInput(in, this.writableRegistry())) { - ShapeBuilder actual = nin.readNamedWriteable(ShapeBuilder.class); - assertEquals(shapeBuilder, actual); - assertEquals(0, in.available()); - } - } - } - } - // Test Geometry -> Geometry - try (BytesStreamOutput out = new BytesStreamOutput()) { - GeometryIO.writeGeometry(out, geometry); - ; - try (StreamInput in = out.bytes().streamInput()) { - Geometry actual = GeometryIO.readGeometry(in); - assertEquals(geometry, actual); - assertEquals(0, in.available()); - } - } - - } - } - } - - private boolean shapeSupported(Geometry geometry) { - if (geometry.hasZ()) { - return false; - } - - if (geometry.type() == ShapeType.GEOMETRYCOLLECTION) { - GeometryCollection collection = (GeometryCollection) geometry; - for (Geometry g : collection) { - if (shapeSupported(g) == false) { - return false; + assertEquals(0, in.available()); } } } - return true; - } - - @Override - protected NamedWriteableRegistry writableRegistry() { - return new NamedWriteableRegistry(GeoShapeType.getShapeWriteables()); } } diff --git a/server/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java b/server/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java deleted file mode 100644 index 94c85db2e366..000000000000 --- a/server/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java +++ /dev/null @@ -1,842 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -package org.elasticsearch.common.geo; - -import org.elasticsearch.common.geo.builders.CircleBuilder; -import org.elasticsearch.common.geo.builders.CoordinatesBuilder; -import org.elasticsearch.common.geo.builders.EnvelopeBuilder; -import org.elasticsearch.common.geo.builders.LineStringBuilder; -import org.elasticsearch.common.geo.builders.MultiLineStringBuilder; -import org.elasticsearch.common.geo.builders.PointBuilder; -import org.elasticsearch.common.geo.builders.PolygonBuilder; -import org.elasticsearch.common.geo.builders.ShapeBuilder; -import org.elasticsearch.geometry.LinearRing; -import org.elasticsearch.index.mapper.GeoShapeIndexer; -import org.elasticsearch.test.ESTestCase; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.geom.Polygon; -import org.locationtech.spatial4j.exception.InvalidShapeException; -import org.locationtech.spatial4j.shape.Circle; -import org.locationtech.spatial4j.shape.Point; -import org.locationtech.spatial4j.shape.Rectangle; -import org.locationtech.spatial4j.shape.impl.PointImpl; - -import static org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions.assertMultiLineString; -import static org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions.assertMultiPolygon; -import static org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions.assertPolygon; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; - -/** - * Tests for {@link ShapeBuilder} - */ -public class ShapeBuilderTests extends ESTestCase { - - public void testNewPoint() { - PointBuilder pb = new PointBuilder().coordinate(-100, 45); - Point point = pb.buildS4J(); - assertEquals(-100D, point.getX(), 0.0d); - assertEquals(45D, point.getY(), 0.0d); - org.elasticsearch.geometry.Point geoPoint = pb.buildGeometry(); - assertEquals(-100D, geoPoint.getX(), 0.0d); - assertEquals(45D, geoPoint.getY(), 0.0d); - } - - public void testNewRectangle() { - EnvelopeBuilder eb = new EnvelopeBuilder(new Coordinate(-45, 30), new Coordinate(45, -30)); - Rectangle rectangle = eb.buildS4J(); - assertEquals(-45D, rectangle.getMinX(), 0.0d); - assertEquals(-30D, rectangle.getMinY(), 0.0d); - assertEquals(45D, rectangle.getMaxX(), 0.0d); - assertEquals(30D, rectangle.getMaxY(), 0.0d); - - org.elasticsearch.geometry.Rectangle luceneRectangle = eb.buildGeometry(); - assertEquals(-45D, luceneRectangle.getMinX(), 0.0d); - assertEquals(-30D, luceneRectangle.getMinY(), 0.0d); - assertEquals(45D, luceneRectangle.getMaxX(), 0.0d); - assertEquals(30D, luceneRectangle.getMaxY(), 0.0d); - } - - public void testNewPolygon() { - PolygonBuilder pb = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-45, 30) - .coordinate(45, 30) - .coordinate(45, -30) - .coordinate(-45, -30) - .coordinate(-45, 30)); - - Polygon poly = pb.toPolygonS4J(); - LineString exterior = poly.getExteriorRing(); - assertEquals(exterior.getCoordinateN(0), new Coordinate(-45, 30)); - assertEquals(exterior.getCoordinateN(1), new Coordinate(45, 30)); - assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30)); - assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30)); - - LinearRing polygon = pb.toPolygonGeometry().getPolygon(); - assertEquals(polygon.getY(0), 30, 0d); - assertEquals(polygon.getX(0), -45, 0d); - assertEquals(polygon.getY(1), 30, 0d); - assertEquals(polygon.getX(1), 45, 0d); - assertEquals(polygon.getY(2), -30, 0d); - assertEquals(polygon.getX(2), 45, 0d); - assertEquals(polygon.getY(3), -30, 0d); - assertEquals(polygon.getX(3), -45, 0d); - } - - public void testNewPolygon_coordinate() { - PolygonBuilder pb = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(new Coordinate(-45, 30)) - .coordinate(new Coordinate(45, 30)) - .coordinate(new Coordinate(45, -30)) - .coordinate(new Coordinate(-45, -30)) - .coordinate(new Coordinate(-45, 30))); - - Polygon poly = pb.toPolygonS4J(); - LineString exterior = poly.getExteriorRing(); - assertEquals(exterior.getCoordinateN(0), new Coordinate(-45, 30)); - assertEquals(exterior.getCoordinateN(1), new Coordinate(45, 30)); - assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30)); - assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30)); - - LinearRing polygon = pb.toPolygonGeometry().getPolygon(); - assertEquals(polygon.getY(0), 30, 0d); - assertEquals(polygon.getX(0), -45, 0d); - assertEquals(polygon.getY(1), 30, 0d); - assertEquals(polygon.getX(1), 45, 0d); - assertEquals(polygon.getY(2), -30, 0d); - assertEquals(polygon.getX(2), 45, 0d); - assertEquals(polygon.getY(3), -30, 0d); - assertEquals(polygon.getX(3), -45, 0d); - } - - public void testNewPolygon_coordinates() { - PolygonBuilder pb = new PolygonBuilder(new CoordinatesBuilder() - .coordinates(new Coordinate(-45, 30), new Coordinate(45, 30), - new Coordinate(45, -30), new Coordinate(-45, -30), new Coordinate(-45, 30)) - ); - - Polygon poly = pb.toPolygonS4J(); - LineString exterior = poly.getExteriorRing(); - assertEquals(exterior.getCoordinateN(0), new Coordinate(-45, 30)); - assertEquals(exterior.getCoordinateN(1), new Coordinate(45, 30)); - assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30)); - assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30)); - - LinearRing polygon = pb.toPolygonGeometry().getPolygon(); - assertEquals(polygon.getY(0), 30, 0d); - assertEquals(polygon.getX(0), -45, 0d); - assertEquals(polygon.getY(1), 30, 0d); - assertEquals(polygon.getX(1), 45, 0d); - assertEquals(polygon.getY(2), -30, 0d); - assertEquals(polygon.getX(2), 45, 0d); - assertEquals(polygon.getY(3), -30, 0d); - assertEquals(polygon.getX(3), -45, 0d); - } - - public void testLineStringBuilder() { - // Building a simple LineString - LineStringBuilder lsb = new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-130.0, 55.0) - .coordinate(-130.0, -40.0) - .coordinate(-15.0, -40.0) - .coordinate(-20.0, 50.0) - .coordinate(-45.0, 50.0) - .coordinate(-45.0, -15.0) - .coordinate(-110.0, -15.0) - .coordinate(-110.0, 55.0)); - - lsb.buildS4J(); - buildGeometry(lsb); - - // Building a linestring that needs to be wrapped - lsb = new LineStringBuilder(new CoordinatesBuilder() - .coordinate(100.0, 50.0) - .coordinate(110.0, -40.0) - .coordinate(240.0, -40.0) - .coordinate(230.0, 60.0) - .coordinate(200.0, 60.0) - .coordinate(200.0, -30.0) - .coordinate(130.0, -30.0) - .coordinate(130.0, 60.0)); - - lsb.buildS4J(); - buildGeometry(lsb); - - // Building a lineString on the dateline - lsb = new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-180.0, 80.0) - .coordinate(-180.0, 40.0) - .coordinate(-180.0, -40.0) - .coordinate(-180.0, -80.0)); - - lsb.buildS4J(); - buildGeometry(lsb); - - // Building a lineString on the dateline - lsb = new LineStringBuilder(new CoordinatesBuilder() - .coordinate(180.0, 80.0) - .coordinate(180.0, 40.0) - .coordinate(180.0, -40.0) - .coordinate(180.0, -80.0)); - - lsb.buildS4J(); - buildGeometry(lsb); - } - - public void testMultiLineString() { - MultiLineStringBuilder mlsb = new MultiLineStringBuilder() - .linestring(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-100.0, 50.0) - .coordinate(50.0, 50.0) - .coordinate(50.0, 20.0) - .coordinate(-100.0, 20.0) - ) - ) - .linestring(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-100.0, 20.0) - .coordinate(50.0, 20.0) - .coordinate(50.0, 0.0) - .coordinate(-100.0, 0.0) - ) - ); - mlsb.buildS4J(); - buildGeometry(mlsb); - - // LineString that needs to be wrapped - new MultiLineStringBuilder() - .linestring(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(150.0, 60.0) - .coordinate(200.0, 60.0) - .coordinate(200.0, 40.0) - .coordinate(150.0, 40.0) - ) - ) - .linestring(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(150.0, 20.0) - .coordinate(200.0, 20.0) - .coordinate(200.0, 0.0) - .coordinate(150.0, 0.0) - ) - ); - - mlsb.buildS4J(); - buildGeometry(mlsb); - } - - public void testPolygonSelfIntersection() { - PolygonBuilder newPolygon = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-40.0, 50.0) - .coordinate(40.0, 50.0) - .coordinate(-40.0, -50.0) - .coordinate(40.0, -50.0).close()); - Exception e = expectThrows(InvalidShapeException.class, () -> newPolygon.buildS4J()); - assertThat(e.getMessage(), containsString("Cannot determine orientation: signed area equal to 0")); - } - - /** note: only supported by S4J at the moment */ - public void testGeoCircle() { - double earthCircumference = 40075016.69; - Circle circle = new CircleBuilder().center(0, 0).radius("100m").buildS4J(); - assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001); - assertEquals(new PointImpl(0, 0, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); - circle = new CircleBuilder().center(+180, 0).radius("100m").buildS4J(); - assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001); - assertEquals(new PointImpl(180, 0, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); - circle = new CircleBuilder().center(-180, 0).radius("100m").buildS4J(); - assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001); - assertEquals(new PointImpl(-180, 0, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); - circle = new CircleBuilder().center(0, 90).radius("100m").buildS4J(); - assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001); - assertEquals(new PointImpl(0, 90, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); - circle = new CircleBuilder().center(0, -90).radius("100m").buildS4J(); - assertEquals((360 * 100) / earthCircumference, circle.getRadius(), 0.00000001); - assertEquals(new PointImpl(0, -90, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); - double randomLat = (randomDouble() * 180) - 90; - double randomLon = (randomDouble() * 360) - 180; - double randomRadius = randomIntBetween(1, (int) earthCircumference / 4); - circle = new CircleBuilder().center(randomLon, randomLat).radius(randomRadius + "m").buildS4J(); - assertEquals((360 * randomRadius) / earthCircumference, circle.getRadius(), 0.00000001); - assertEquals(new PointImpl(randomLon, randomLat, ShapeBuilder.SPATIAL_CONTEXT), circle.getCenter()); - } - - public void testPolygonWrapping() { - PolygonBuilder pb = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-150.0, 65.0) - .coordinate(-250.0, 65.0) - .coordinate(-250.0, -65.0) - .coordinate(-150.0, -65.0) - .close()); - - assertMultiPolygon(pb.buildS4J(), true); - assertMultiPolygon(buildGeometry(pb), false); - } - - public void testLineStringWrapping() { - LineStringBuilder lsb = new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-150.0, 65.0) - .coordinate(-250.0, 65.0) - .coordinate(-250.0, -65.0) - .coordinate(-150.0, -65.0) - .close()); - - assertMultiLineString(lsb.buildS4J(), true); - assertMultiLineString(buildGeometry(lsb), false); - } - - public void testDatelineOGC() { - // tests that the following shape (defined in counterclockwise OGC order) - // https://gist.github.com/anonymous/7f1bb6d7e9cd72f5977c crosses the dateline - // expected results: 3 polygons, 1 with a hole - - // a giant c shape - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(174,0) - .coordinate(-176,0) - .coordinate(-176,3) - .coordinate(177,3) - .coordinate(177,5) - .coordinate(-176,5) - .coordinate(-176,8) - .coordinate(174,8) - .coordinate(174,0) - ); - - // 3/4 of an embedded 'c', crossing dateline once - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(175, 1) - .coordinate(175, 7) - .coordinate(-178, 7) - .coordinate(-178, 6) - .coordinate(176, 6) - .coordinate(176, 2) - .coordinate(179, 2) - .coordinate(179,1) - .coordinate(175, 1) - )); - - // embedded hole right of the dateline - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-179, 1) - .coordinate(-179, 2) - .coordinate(-177, 2) - .coordinate(-177,1) - .coordinate(-179,1) - )); - - assertMultiPolygon(builder.close().buildS4J(), true); - assertMultiPolygon(buildGeometry(builder.close()), false); - } - - public void testDateline() { - // tests that the following shape (defined in clockwise non-OGC order) - // https://gist.github.com/anonymous/7f1bb6d7e9cd72f5977c crosses the dateline - // expected results: 3 polygons, 1 with a hole - - // a giant c shape - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-186,0) - .coordinate(-176,0) - .coordinate(-176,3) - .coordinate(-183,3) - .coordinate(-183,5) - .coordinate(-176,5) - .coordinate(-176,8) - .coordinate(-186,8) - .coordinate(-186,0) - ); - - // 3/4 of an embedded 'c', crossing dateline once - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-185,1) - .coordinate(-181,1) - .coordinate(-181,2) - .coordinate(-184,2) - .coordinate(-184,6) - .coordinate(-178,6) - .coordinate(-178,7) - .coordinate(-185,7) - .coordinate(-185,1) - )); - - // embedded hole right of the dateline - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-179,1) - .coordinate(-177,1) - .coordinate(-177,2) - .coordinate(-179,2) - .coordinate(-179,1) - )); - - assertMultiPolygon(builder.close().buildS4J(), true); - assertMultiPolygon(buildGeometry(builder.close()), false); - } - - public void testComplexShapeWithHole() { - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-85.0018514,37.1311314) - .coordinate(-85.0016645,37.1315293) - .coordinate(-85.0016246,37.1317069) - .coordinate(-85.0016526,37.1318183) - .coordinate(-85.0017119,37.1319196) - .coordinate(-85.0019371,37.1321182) - .coordinate(-85.0019972,37.1322115) - .coordinate(-85.0019942,37.1323234) - .coordinate(-85.0019543,37.1324336) - .coordinate(-85.001906,37.1324985) - .coordinate(-85.001834,37.1325497) - .coordinate(-85.0016965,37.1325907) - .coordinate(-85.0016011,37.1325873) - .coordinate(-85.0014816,37.1325353) - .coordinate(-85.0011755,37.1323509) - .coordinate(-85.000955,37.1322802) - .coordinate(-85.0006241,37.1322529) - .coordinate(-85.0000002,37.1322307) - .coordinate(-84.9994,37.1323001) - .coordinate(-84.999109,37.1322864) - .coordinate(-84.998934,37.1322415) - .coordinate(-84.9988639,37.1321888) - .coordinate(-84.9987841,37.1320944) - .coordinate(-84.9987208,37.131954) - .coordinate(-84.998736,37.1316611) - .coordinate(-84.9988091,37.131334) - .coordinate(-84.9989283,37.1311337) - .coordinate(-84.9991943,37.1309198) - .coordinate(-84.9993573,37.1308459) - .coordinate(-84.9995888,37.1307924) - .coordinate(-84.9998746,37.130806) - .coordinate(-85.0000002,37.1308358) - .coordinate(-85.0004984,37.1310658) - .coordinate(-85.0008008,37.1311625) - .coordinate(-85.0009461,37.1311684) - .coordinate(-85.0011373,37.1311515) - .coordinate(-85.0016455,37.1310491) - .coordinate(-85.0018514,37.1311314) - ); - - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-85.0000002,37.1317672) - .coordinate(-85.0001983,37.1317538) - .coordinate(-85.0003378,37.1317582) - .coordinate(-85.0004697,37.131792) - .coordinate(-85.0008048,37.1319439) - .coordinate(-85.0009342,37.1319838) - .coordinate(-85.0010184,37.1319463) - .coordinate(-85.0010618,37.13184) - .coordinate(-85.0010057,37.1315102) - .coordinate(-85.000977,37.1314403) - .coordinate(-85.0009182,37.1313793) - .coordinate(-85.0005366,37.1312209) - .coordinate(-85.000224,37.1311466) - .coordinate(-85.000087,37.1311356) - .coordinate(-85.0000002,37.1311433) - .coordinate(-84.9995021,37.1312336) - .coordinate(-84.9993308,37.1312859) - .coordinate(-84.9992567,37.1313252) - .coordinate(-84.9991868,37.1314277) - .coordinate(-84.9991593,37.1315381) - .coordinate(-84.9991841,37.1316527) - .coordinate(-84.9992329,37.1317117) - .coordinate(-84.9993527,37.1317788) - .coordinate(-84.9994931,37.1318061) - .coordinate(-84.9996815,37.1317979) - .coordinate(-85.0000002,37.1317672) - ) - ); - assertPolygon(builder.close().buildS4J(), true); - assertPolygon(buildGeometry(builder.close()), false); - } - - public void testShapeWithHoleAtEdgeEndPoints() { - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-4, 2) - .coordinate(4, 2) - .coordinate(6, 0) - .coordinate(4, -2) - .coordinate(-4, -2) - .coordinate(-6, 0) - .coordinate(-4, 2) - ); - - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(4, 1) - .coordinate(4, -1) - .coordinate(-4, -1) - .coordinate(-4, 1) - .coordinate(4, 1) - )); - assertPolygon(builder.close().buildS4J(), true); - assertPolygon(buildGeometry(builder.close()), false); - } - - public void testShapeWithPointOnDateline() { - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(180, 0) - .coordinate(176, 4) - .coordinate(176, -4) - .coordinate(180, 0) - ); - assertPolygon(builder.close().buildS4J(), true); - assertPolygon(buildGeometry(builder.close()), false); - } - - public void testShapeWithEdgeAlongDateline() { - // test case 1: test the positive side of the dateline - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(180, 0) - .coordinate(176, 4) - .coordinate(180, -4) - .coordinate(180, 0) - ); - - assertPolygon(builder.close().buildS4J(), true); - assertPolygon(buildGeometry(builder.close()), false); - - // test case 2: test the negative side of the dateline - builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-176, 4) - .coordinate(-180, 0) - .coordinate(-180, -4) - .coordinate(-176, 4) - ); - - assertPolygon(builder.close().buildS4J(), true); - assertPolygon(buildGeometry(builder.close()), false); - } - - public void testShapeWithBoundaryHoles() { - // test case 1: test the positive side of the dateline - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-177, 10) - .coordinate(176, 15) - .coordinate(172, 0) - .coordinate(176, -15) - .coordinate(-177, -10) - .coordinate(-177, 10) - ); - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(176, 10) - .coordinate(180, 5) - .coordinate(180, -5) - .coordinate(176, -10) - .coordinate(176, 10) - )); - - assertMultiPolygon(builder.close().buildS4J(), true); - assertMultiPolygon(buildGeometry(builder.close()), false); - - // test case 2: test the negative side of the dateline - builder = new PolygonBuilder( - new CoordinatesBuilder() - .coordinate(-176, 15) - .coordinate(179, 10) - .coordinate(179, -10) - .coordinate(-176, -15) - .coordinate(-172, 0) - .close() - ); - builder.hole(new LineStringBuilder( - new CoordinatesBuilder() - .coordinate(-176, 10) - .coordinate(-176, -10) - .coordinate(-180, -5) - .coordinate(-180, 5) - .coordinate(-176, 10) - .close() - )); - - assertMultiPolygon(builder.close().buildS4J(), true); - assertMultiPolygon(buildGeometry(builder.close()), false); - } - - public void testShapeWithHoleTouchingAtDateline() throws Exception { - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-180, 90) - .coordinate(-180, -90) - .coordinate(180, -90) - .coordinate(180, 90) - .coordinate(-180, 90) - ); - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(180.0, -16.14) - .coordinate(178.53, -16.64) - .coordinate(178.49, -16.82) - .coordinate(178.73, -17.02) - .coordinate(178.86, -16.86) - .coordinate(180.0, -16.14) - )); - - assertPolygon(builder.close().buildS4J(), true); - assertPolygon(buildGeometry(builder.close()), false); - } - - public void testShapeWithTangentialHole() { - // test a shape with one tangential (shared) vertex (should pass) - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(179, 10) - .coordinate(168, 15) - .coordinate(164, 0) - .coordinate(166, -15) - .coordinate(179, -10) - .coordinate(179, 10) - ); - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-177, 10) - .coordinate(-178, -10) - .coordinate(-180, -5) - .coordinate(-180, 5) - .coordinate(-177, 10) - )); - - assertMultiPolygon(builder.close().buildS4J(), true); - assertMultiPolygon(buildGeometry(builder.close()), false); - } - - public void testShapeWithInvalidTangentialHole() { - // test a shape with one invalid tangential (shared) vertex (should throw exception) - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(179, 10) - .coordinate(168, 15) - .coordinate(164, 0) - .coordinate(166, -15) - .coordinate(179, -10) - .coordinate(179, 10) - ); - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(164, 0) - .coordinate(175, 10) - .coordinate(175, 5) - .coordinate(179, -10) - .coordinate(164, 0) - )); - Exception e; - - e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J()); - assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); - e = expectThrows(IllegalArgumentException.class, () -> buildGeometry(builder.close())); - assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); - } - - public void testBoundaryShapeWithTangentialHole() { - // test a shape with one tangential (shared) vertex for each hole (should pass) - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-177, 10) - .coordinate(176, 15) - .coordinate(172, 0) - .coordinate(176, -15) - .coordinate(-177, -10) - .coordinate(-177, 10) - ); - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-177, 10) - .coordinate(-178, -10) - .coordinate(-180, -5) - .coordinate(-180, 5) - .coordinate(-177, 10) - )); - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(172, 0) - .coordinate(176, 10) - .coordinate(176, -5) - .coordinate(172, 0) - )); - assertMultiPolygon(builder.close().buildS4J(), true); - assertMultiPolygon(buildGeometry(builder.close()), false); - } - - public void testBoundaryShapeWithInvalidTangentialHole() { - // test shape with two tangential (shared) vertices (should throw exception) - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-177, 10) - .coordinate(176, 15) - .coordinate(172, 0) - .coordinate(176, -15) - .coordinate(-177, -10) - .coordinate(-177, 10) - ); - builder.hole(new LineStringBuilder(new CoordinatesBuilder() - .coordinate(-177, 10) - .coordinate(172, 0) - .coordinate(180, -5) - .coordinate(176, -10) - .coordinate(-177, 10) - )); - Exception e; - e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J()); - assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); - e = expectThrows(IllegalArgumentException.class, () -> buildGeometry(builder.close())); - assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); - } - - /** - * Test an enveloping polygon around the max mercator bounds - */ - public void testBoundaryShape() { - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(-180, 90) - .coordinate(180, 90) - .coordinate(180, -90) - .coordinate(-180, 90) - ); - - assertPolygon(builder.close().buildS4J(), true); - assertPolygon(buildGeometry(builder.close()), false); - } - - public void testShapeWithAlternateOrientation() { - // cw: should produce a multi polygon spanning hemispheres - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(180, 0) - .coordinate(176, 4) - .coordinate(-176, 4) - .coordinate(180, 0) - ); - - assertPolygon(builder.close().buildS4J(), true); - assertPolygon(buildGeometry(builder.close()), false); - - // cw: geo core will convert to ccw across the dateline - builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(180, 0) - .coordinate(-176, 4) - .coordinate(176, 4) - .coordinate(180, 0) - ); - - assertMultiPolygon(builder.close().buildS4J(), true); - assertMultiPolygon(buildGeometry(builder.close()), false); - } - - public void testShapeWithConsecutiveDuplicatePoints() { - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(180, 0) - .coordinate(176, 4) - .coordinate(176, 4) - .coordinate(-176, 4) - .coordinate(180, 0) - ); - - // duplicated points are removed - PolygonBuilder expected = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(180, 0) - .coordinate(176, 4) - .coordinate(-176, 4) - .coordinate(180, 0) - ); - - assertEquals(buildGeometry(expected.close()), buildGeometry(builder.close())); - assertEquals(expected.close().buildS4J(), builder.close().buildS4J()); - } - - public void testShapeWithCoplanarVerticalPoints() throws Exception { - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(180, -36) - .coordinate(180, 90) - .coordinate(-180, 90) - .coordinate(-180, 79) - .coordinate(16, 58) - .coordinate(8, 13) - .coordinate(-180, 74) - .coordinate(-180, -85) - .coordinate(-180, -90) - .coordinate(180, -90) - .coordinate(180, -85) - .coordinate(26, 6) - .coordinate(33, 62) - .coordinate(180, -36) - ); - - //coplanar points on vertical edge are removed. - PolygonBuilder expected = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(180, -36) - .coordinate(180, 90) - .coordinate(-180, 90) - .coordinate(-180, 79) - .coordinate(16, 58) - .coordinate(8, 13) - .coordinate(-180, 74) - .coordinate(-180, -90) - .coordinate(180, -90) - .coordinate(180, -85) - .coordinate(26, 6) - .coordinate(33, 62) - .coordinate(180, -36) - ); - - assertEquals(buildGeometry(expected.close()), buildGeometry(builder.close())); - assertEquals(expected.close().buildS4J(), builder.close().buildS4J()); - - } - - public void testPolygon3D() { - String expected = "{\n" + - " \"type\" : \"polygon\",\n" + - " \"orientation\" : \"right\",\n" + - " \"coordinates\" : [\n" + - " [\n" + - " [\n" + - " -45.0,\n" + - " 30.0,\n" + - " 100.0\n" + - " ],\n" + - " [\n" + - " 45.0,\n" + - " 30.0,\n" + - " 75.0\n" + - " ],\n" + - " [\n" + - " 45.0,\n" + - " -30.0,\n" + - " 77.0\n" + - " ],\n" + - " [\n" + - " -45.0,\n" + - " -30.0,\n" + - " 101.0\n" + - " ],\n" + - " [\n" + - " -45.0,\n" + - " 30.0,\n" + - " 110.0\n" + - " ]\n" + - " ]\n" + - " ]\n" + - "}"; - - PolygonBuilder pb = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(new Coordinate(-45, 30, 100)) - .coordinate(new Coordinate(45, 30, 75)) - .coordinate(new Coordinate(45, -30, 77)) - .coordinate(new Coordinate(-45, -30, 101)) - .coordinate(new Coordinate(-45, 30, 110))); - - assertEquals(expected, pb.toString()); - } - - public void testInvalidSelfCrossingPolygon() { - PolygonBuilder builder = new PolygonBuilder(new CoordinatesBuilder() - .coordinate(0, 0) - .coordinate(0, 2) - .coordinate(1, 1.9) - .coordinate(0.5, 1.8) - .coordinate(1.5, 1.8) - .coordinate(1, 1.9) - .coordinate(2, 2) - .coordinate(2, 0) - .coordinate(0, 0) - ); - Exception e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J()); - assertThat(e.getMessage(), containsString("Self-intersection at or near point [")); - assertThat(e.getMessage(), not(containsString("NaN"))); - } - - public Object buildGeometry(ShapeBuilder builder) { - return new GeoShapeIndexer(true, "name").prepareForIndexing(builder.buildGeometry()); - } -} diff --git a/server/src/test/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilderTests.java b/server/src/test/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilderTests.java deleted file mode 100644 index 5548ac8d62fa..000000000000 --- a/server/src/test/java/org/elasticsearch/common/geo/builders/GeometryCollectionBuilderTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -package org.elasticsearch.common.geo.builders; - -import org.elasticsearch.test.geo.RandomShapeGenerator; - -import java.io.IOException; - -public class GeometryCollectionBuilderTests extends AbstractShapeBuilderTestCase { - - @Override - protected GeometryCollectionBuilder createTestShapeBuilder() { - GeometryCollectionBuilder geometryCollection = new GeometryCollectionBuilder(); - int shapes = randomIntBetween(0, 8); - for (int i = 0; i < shapes; i++) { - switch (randomIntBetween(0, 7)) { - case 0: - geometryCollection.shape(PointBuilderTests.createRandomShape()); - break; - case 1: - geometryCollection.shape(CircleBuilderTests.createRandomShape()); - break; - case 2: - geometryCollection.shape(EnvelopeBuilderTests.createRandomShape()); - break; - case 3: - geometryCollection.shape(LineStringBuilderTests.createRandomShape()); - break; - case 4: - geometryCollection.shape(MultiLineStringBuilderTests.createRandomShape()); - break; - case 5: - geometryCollection.shape(MultiPolygonBuilderTests.createRandomShape()); - break; - case 6: - geometryCollection.shape(MultiPointBuilderTests.createRandomShape()); - break; - case 7: - geometryCollection.shape(PolygonBuilderTests.createRandomShape()); - break; - } - } - return geometryCollection; - } - - @Override - protected GeometryCollectionBuilder createMutation(GeometryCollectionBuilder original) throws IOException { - return mutate(original); - } - - static GeometryCollectionBuilder mutate(GeometryCollectionBuilder original) throws IOException { - GeometryCollectionBuilder mutation = copyShape(original); - if (mutation.shapes.size() > 0) { - int shapePosition = randomIntBetween(0, mutation.shapes.size() - 1); - ShapeBuilder shapeToChange = mutation.shapes.get(shapePosition); - switch (shapeToChange.type()) { - case POINT: - shapeToChange = PointBuilderTests.mutate((PointBuilder) shapeToChange); - break; - case CIRCLE: - shapeToChange = CircleBuilderTests.mutate((CircleBuilder) shapeToChange); - break; - case ENVELOPE: - shapeToChange = EnvelopeBuilderTests.mutate((EnvelopeBuilder) shapeToChange); - break; - case LINESTRING: - shapeToChange = LineStringBuilderTests.mutate((LineStringBuilder) shapeToChange); - break; - case MULTILINESTRING: - shapeToChange = MultiLineStringBuilderTests.mutate((MultiLineStringBuilder) shapeToChange); - break; - case MULTIPOLYGON: - shapeToChange = MultiPolygonBuilderTests.mutate((MultiPolygonBuilder) shapeToChange); - break; - case MULTIPOINT: - shapeToChange = MultiPointBuilderTests.mutate((MultiPointBuilder) shapeToChange); - break; - case POLYGON: - shapeToChange = PolygonBuilderTests.mutate((PolygonBuilder) shapeToChange); - break; - case GEOMETRYCOLLECTION: - throw new UnsupportedOperationException("GeometryCollection should not be nested inside each other"); - } - mutation.shapes.set(shapePosition, shapeToChange); - } else { - mutation.shape(RandomShapeGenerator.createShape(random())); - } - return mutation; - } -} diff --git a/server/src/test/java/org/elasticsearch/index/query/LegacyGeoShapeFieldQueryTests.java b/server/src/test/java/org/elasticsearch/index/query/LegacyGeoShapeFieldQueryTests.java deleted file mode 100644 index 74539cdc56e4..000000000000 --- a/server/src/test/java/org/elasticsearch/index/query/LegacyGeoShapeFieldQueryTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -package org.elasticsearch.index.query; - -import org.elasticsearch.Version; -import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.common.geo.ShapeRelation; -import org.elasticsearch.common.geo.SpatialStrategy; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.geo.GeometryTestUtils; -import org.elasticsearch.geometry.Geometry; -import org.elasticsearch.geometry.ShapeType; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.test.TestLegacyGeoShapeFieldMapperPlugin; -import org.elasticsearch.test.VersionUtils; - -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; - -public class LegacyGeoShapeFieldQueryTests extends GeoShapeQueryBuilderTests { - - @SuppressWarnings("deprecation") - @Override - protected Collection> getPlugins() { - return Collections.singletonList(TestLegacyGeoShapeFieldMapperPlugin.class); - } - - @Override - protected String fieldName() { - return GEO_SHAPE_FIELD_NAME; - } - - @Override - protected Settings createTestIndexSettings() { - // force the legacy shape impl - Version version = VersionUtils.randomVersionBetween(random(), Version.V_6_0_0, Version.V_6_5_0); - return Settings.builder() - .put(super.createTestIndexSettings()) - .put(IndexMetadata.SETTING_VERSION_CREATED, version) - .build(); - } - - @Override - protected GeoShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) { - Geometry geometry = GeometryTestUtils.randomGeometry(false); - GeoShapeQueryBuilder builder; - clearShapeFields(); - if (indexedShape == false) { - builder = new GeoShapeQueryBuilder(fieldName(), geometry); - } else { - indexedShapeToReturn = geometry; - indexedShapeId = randomAlphaOfLengthBetween(3, 20); - builder = new GeoShapeQueryBuilder(fieldName(), indexedShapeId); - if (randomBoolean()) { - indexedShapeIndex = randomAlphaOfLengthBetween(3, 20); - builder.indexedShapeIndex(indexedShapeIndex); - } - if (randomBoolean()) { - indexedShapePath = randomAlphaOfLengthBetween(3, 20); - builder.indexedShapePath(indexedShapePath); - } - if (randomBoolean()) { - indexedShapeRouting = randomAlphaOfLengthBetween(3, 20); - builder.indexedShapeRouting(indexedShapeRouting); - } - } - if (randomBoolean()) { - SpatialStrategy strategy = randomFrom(SpatialStrategy.values()); - // ShapeType.MULTILINESTRING + SpatialStrategy.TERM can lead to large queries and will slow down tests, so - // we try to avoid that combination - while (geometry.type() == ShapeType.MULTILINESTRING && strategy == SpatialStrategy.TERM) { - strategy = randomFrom(SpatialStrategy.values()); - } - builder.strategy(strategy); - if (strategy != SpatialStrategy.TERM) { - builder.relation(randomFrom(ShapeRelation.values())); - } - } - - if (randomBoolean()) { - builder.ignoreUnmapped(randomBoolean()); - } - return builder; - } - - public void testInvalidRelation() throws IOException { - Geometry shape = GeometryTestUtils.randomGeometry(false); - GeoShapeQueryBuilder builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape); - builder.strategy(SpatialStrategy.TERM); - expectThrows(IllegalArgumentException.class, () -> builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.WITHIN))); - GeoShapeQueryBuilder builder2 = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape); - builder2.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.WITHIN)); - expectThrows(IllegalArgumentException.class, () -> builder2.strategy(SpatialStrategy.TERM)); - GeoShapeQueryBuilder builder3 = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape); - builder3.strategy(SpatialStrategy.TERM); - expectThrows(IllegalArgumentException.class, () -> builder3.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.WITHIN))); - } -} diff --git a/server/src/test/java/org/elasticsearch/index/search/geo/GeoUtilsTests.java b/server/src/test/java/org/elasticsearch/index/search/geo/GeoUtilsTests.java index ca78d9077ca8..df0d3fbf55ae 100644 --- a/server/src/test/java/org/elasticsearch/index/search/geo/GeoUtilsTests.java +++ b/server/src/test/java/org/elasticsearch/index/search/geo/GeoUtilsTests.java @@ -8,9 +8,6 @@ package org.elasticsearch.index.search.geo; -import org.apache.lucene.spatial.prefix.tree.Cell; -import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; -import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoUtils; @@ -19,8 +16,6 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser.Token; import org.elasticsearch.geometry.utils.Geohash; import org.elasticsearch.test.ESTestCase; -import org.locationtech.spatial4j.context.SpatialContext; -import org.locationtech.spatial4j.distance.DistanceUtils; import java.io.IOException; @@ -617,56 +612,7 @@ public class GeoUtilsTests extends ESTestCase { } } - public void testPrefixTreeCellSizes() { - assertThat(GeoUtils.EARTH_SEMI_MAJOR_AXIS, equalTo(DistanceUtils.EARTH_EQUATORIAL_RADIUS_KM * 1000)); - assertThat(GeoUtils.quadTreeCellWidth(0), lessThanOrEqualTo(GeoUtils.EARTH_EQUATOR)); - SpatialContext spatialContext = new SpatialContext(true); - - GeohashPrefixTree geohashPrefixTree = new GeohashPrefixTree(spatialContext, GeohashPrefixTree.getMaxLevelsPossible() / 2); - Cell gNode = geohashPrefixTree.getWorldCell(); - - for (int i = 0; i < geohashPrefixTree.getMaxLevels(); i++) { - double width = GeoUtils.geoHashCellWidth(i); - double height = GeoUtils.geoHashCellHeight(i); - double size = GeoUtils.geoHashCellSize(i); - double degrees = 360.0 * width / GeoUtils.EARTH_EQUATOR; - int level = GeoUtils.quadTreeLevelsForPrecision(size); - - assertThat(GeoUtils.quadTreeCellWidth(level), lessThanOrEqualTo(width)); - assertThat(GeoUtils.quadTreeCellHeight(level), lessThanOrEqualTo(height)); - assertThat(GeoUtils.geoHashLevelsForPrecision(size), equalTo(geohashPrefixTree.getLevelForDistance(degrees))); - - assertThat("width at level " + i, - gNode.getShape().getBoundingBox().getWidth(), equalTo(360.d * width / GeoUtils.EARTH_EQUATOR)); - assertThat("height at level " + i, gNode.getShape().getBoundingBox().getHeight(), equalTo(180.d * height - / GeoUtils.EARTH_POLAR_DISTANCE)); - - gNode = gNode.getNextLevelCells(null).next(); - } - - QuadPrefixTree quadPrefixTree = new QuadPrefixTree(spatialContext); - Cell qNode = quadPrefixTree.getWorldCell(); - for (int i = 0; i < quadPrefixTree.getMaxLevels(); i++) { - - double degrees = 360.0 / (1L << i); - double width = GeoUtils.quadTreeCellWidth(i); - double height = GeoUtils.quadTreeCellHeight(i); - double size = GeoUtils.quadTreeCellSize(i); - int level = GeoUtils.quadTreeLevelsForPrecision(size); - - assertThat(GeoUtils.quadTreeCellWidth(level), lessThanOrEqualTo(width)); - assertThat(GeoUtils.quadTreeCellHeight(level), lessThanOrEqualTo(height)); - assertThat(GeoUtils.quadTreeLevelsForPrecision(size), equalTo(quadPrefixTree.getLevelForDistance(degrees))); - - assertThat("width at level " + i, - qNode.getShape().getBoundingBox().getWidth(), equalTo(360.d * width / GeoUtils.EARTH_EQUATOR)); - assertThat("height at level " + i, qNode.getShape().getBoundingBox().getHeight(), equalTo(180.d * height - / GeoUtils.EARTH_POLAR_DISTANCE)); - - qNode = qNode.getNextLevelCells(null).next(); - } - } public void testParseGeoPointGeohashPositions() throws IOException { assertNormalizedPoint(parseGeohash("drt5", diff --git a/server/src/test/java/org/elasticsearch/test/geo/RandomGeoGenerator.java b/server/src/test/java/org/elasticsearch/test/geo/RandomGeoGenerator.java index 7f91436d69a7..e43836ecb9b0 100644 --- a/server/src/test/java/org/elasticsearch/test/geo/RandomGeoGenerator.java +++ b/server/src/test/java/org/elasticsearch/test/geo/RandomGeoGenerator.java @@ -14,8 +14,7 @@ import java.util.Random; /** * Random geo generation utilities for randomized {@code geo_point} type testing - * does not depend on jts or spatial4j. Use {@link org.elasticsearch.test.geo.RandomShapeGenerator} - * to create random OGC compliant shapes. + * does not depend on jts or spatial4j. */ public class RandomGeoGenerator { diff --git a/x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/ClusterDeprecationChecks.java b/x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/ClusterDeprecationChecks.java index 79e153f36d20..156cf1427a24 100644 --- a/x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/ClusterDeprecationChecks.java +++ b/x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/ClusterDeprecationChecks.java @@ -22,7 +22,7 @@ import org.elasticsearch.core.TimeValue; import org.elasticsearch.core.Tuple; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.mapper.FieldNamesFieldMapper; -import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper; +import org.elasticsearch.index.mapper.GeoShapeFieldMapper; import org.elasticsearch.ingest.IngestService; import org.elasticsearch.ingest.PipelineConfiguration; import org.elasticsearch.xpack.core.deprecation.DeprecationIssue; @@ -224,7 +224,7 @@ public class ClusterDeprecationChecks { XContentType.JSON); Map mappingAsMap = tuple.v2(); List messages = mappingAsMap == null ? Collections.emptyList() : - IndexDeprecationChecks.findInPropertiesRecursively(LegacyGeoShapeFieldMapper.CONTENT_TYPE, + IndexDeprecationChecks.findInPropertiesRecursively(GeoShapeFieldMapper.CONTENT_TYPE, mappingAsMap, IndexDeprecationChecks::isGeoShapeFieldWithDeprecatedParam, IndexDeprecationChecks::formatDeprecatedGeoShapeParamMessage); @@ -253,7 +253,7 @@ public class ClusterDeprecationChecks { XContentType.JSON); Map mappingAsMap = (Map) tuple.v2().get("_doc"); List messages = mappingAsMap == null ? Collections.emptyList() : - IndexDeprecationChecks.findInPropertiesRecursively(LegacyGeoShapeFieldMapper.CONTENT_TYPE, + IndexDeprecationChecks.findInPropertiesRecursively(GeoShapeFieldMapper.CONTENT_TYPE, mappingAsMap, IndexDeprecationChecks::isGeoShapeFieldWithDeprecatedParam, IndexDeprecationChecks::formatDeprecatedGeoShapeParamMessage); diff --git a/x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/IndexDeprecationChecks.java b/x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/IndexDeprecationChecks.java index dd43fd740b16..0b39e005dc31 100644 --- a/x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/IndexDeprecationChecks.java +++ b/x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/IndexDeprecationChecks.java @@ -16,10 +16,10 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexingSlowLog; +import org.elasticsearch.index.mapper.GeoShapeFieldMapper; import org.elasticsearch.xpack.core.deprecation.DeprecationIssue; import org.elasticsearch.index.SearchSlowLog; import org.elasticsearch.index.SlowLogLevel; -import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper; import org.elasticsearch.search.SearchModule; import java.util.ArrayList; @@ -384,8 +384,8 @@ public class IndexDeprecationChecks { } protected static boolean isGeoShapeFieldWithDeprecatedParam(Map property) { - return LegacyGeoShapeFieldMapper.CONTENT_TYPE.equals(property.get("type")) && - LegacyGeoShapeFieldMapper.DEPRECATED_PARAMETERS.stream().anyMatch(deprecatedParameter -> + return GeoShapeFieldMapper.CONTENT_TYPE.equals(property.get("type")) && + GeoShapeFieldMapper.DEPRECATED_PARAMETERS.stream().anyMatch(deprecatedParameter -> property.containsKey(deprecatedParameter) ); } @@ -393,7 +393,7 @@ public class IndexDeprecationChecks { protected static String formatDeprecatedGeoShapeParamMessage(String type, Map.Entry entry) { String fieldName = entry.getKey().toString(); Map value = (Map) entry.getValue(); - return LegacyGeoShapeFieldMapper.DEPRECATED_PARAMETERS.stream() + return GeoShapeFieldMapper.DEPRECATED_PARAMETERS.stream() .filter(deprecatedParameter -> value.containsKey(deprecatedParameter)) .map(deprecatedParameter -> String.format(Locale.ROOT, "parameter [%s] in field [%s]", deprecatedParameter, fieldName)) .collect(Collectors.joining("; ")); @@ -405,7 +405,7 @@ public class IndexDeprecationChecks { return null; } Map sourceAsMap = indexMetadata.mapping().getSourceAsMap(); - List messages = findInPropertiesRecursively(LegacyGeoShapeFieldMapper.CONTENT_TYPE, sourceAsMap, + List messages = findInPropertiesRecursively(GeoShapeFieldMapper.CONTENT_TYPE, sourceAsMap, IndexDeprecationChecks::isGeoShapeFieldWithDeprecatedParam, IndexDeprecationChecks::formatDeprecatedGeoShapeParamMessage); if (messages.isEmpty()) { diff --git a/x-pack/plugin/security/build.gradle b/x-pack/plugin/security/build.gradle index 60932715475a..89ce1a322187 100644 --- a/x-pack/plugin/security/build.gradle +++ b/x-pack/plugin/security/build.gradle @@ -22,6 +22,7 @@ dependencies { testImplementation project(path: xpackModule('monitoring')) testImplementation project(path: xpackModule('spatial')) + testImplementation project(path: ':modules:legacy-geo') testImplementation project(path: ':modules:percolator') testImplementation project(path: xpackModule('sql:sql-action')) testImplementation project(path: ':modules:analysis-common') diff --git a/x-pack/plugin/spatial/build.gradle b/x-pack/plugin/spatial/build.gradle index 3ce2c9858d38..a82a6489f97d 100644 --- a/x-pack/plugin/spatial/build.gradle +++ b/x-pack/plugin/spatial/build.gradle @@ -6,13 +6,15 @@ esplugin { name 'spatial' description 'A plugin for Basic Spatial features' classname 'org.elasticsearch.xpack.spatial.SpatialPlugin' - extendedPlugins = ['x-pack-core'] + extendedPlugins = ['x-pack-core', 'legacy-geo'] } dependencies { + compileOnly project(path: ':modules:legacy-geo') compileOnly project(path: xpackModule('core')) testImplementation(testArtifact(project(xpackModule('core')))) testImplementation project(path: xpackModule('vector-tile')) + testImplementation project(path: ':modules:legacy-geo') yamlRestTestImplementation(testArtifact(project(xpackModule('core')))) } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java index 9ee6fafd5104..d9b234e6c0c7 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java @@ -31,7 +31,6 @@ import org.elasticsearch.index.mapper.GeoShapeFieldMapper; import org.elasticsearch.index.mapper.GeoShapeIndexer; import org.elasticsearch.index.mapper.GeoShapeParser; import org.elasticsearch.index.mapper.GeoShapeQueryable; -import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.Mapper; import org.elasticsearch.index.mapper.MapperBuilderContext; @@ -39,6 +38,7 @@ import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.mapper.MappingParserContext; import org.elasticsearch.index.query.QueryShardException; import org.elasticsearch.index.query.SearchExecutionContext; +import org.elasticsearch.legacygeo.mapper.LegacyGeoShapeFieldMapper; import org.elasticsearch.search.lookup.SearchLookup; import org.elasticsearch.xpack.spatial.index.fielddata.plain.AbstractLatLonShapeIndexFieldData; import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType; @@ -280,7 +280,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField @Override protected void checkIncomingMergeType(FieldMapper mergeWith) { - if (mergeWith instanceof LegacyGeoShapeFieldMapper) { + if (mergeWith instanceof GeoShapeWithDocValuesFieldMapper == false && CONTENT_TYPE.equals(mergeWith.typeName())) { throw new IllegalArgumentException("mapper [" + name() + "] of type [geo_shape] cannot change strategy from [BKD] to [recursive]"); } diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapperTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapperTests.java index 39b91ba07b2d..602c0f00ec34 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapperTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapperTests.java @@ -24,7 +24,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.mapper.DocumentMapper; -import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.Mapper; import org.elasticsearch.index.mapper.MapperParsingException; @@ -32,6 +31,7 @@ import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.MapperTestCase; import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.SourceToParse; +import org.elasticsearch.legacygeo.mapper.LegacyGeoShapeFieldMapper; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.VersionUtils; import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin; diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/LegacyGeoShapeWithDocValuesQueryTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/LegacyGeoShapeWithDocValuesQueryTests.java index 76d80cd3376c..6e067a3824d1 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/LegacyGeoShapeWithDocValuesQueryTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/query/LegacyGeoShapeWithDocValuesQueryTests.java @@ -18,8 +18,8 @@ import org.elasticsearch.geo.GeometryTestUtils; import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.MultiPoint; import org.elasticsearch.geometry.Point; -import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper; import org.elasticsearch.index.mapper.MapperParsingException; +import org.elasticsearch.legacygeo.mapper.LegacyGeoShapeFieldMapper; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.geo.GeoShapeQueryTestCase; import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin; diff --git a/x-pack/plugin/sql/qa/server/build.gradle b/x-pack/plugin/sql/qa/server/build.gradle index 99f576f9e595..4662e76c343f 100644 --- a/x-pack/plugin/sql/qa/server/build.gradle +++ b/x-pack/plugin/sql/qa/server/build.gradle @@ -100,6 +100,7 @@ subprojects { // spatial dependency testRuntimeOnly project(path: xpackModule('spatial')) + testRuntimeOnly project(path: ':modules:legacy-geo') testRuntimeOnly "org.slf4j:slf4j-api:1.7.25" } diff --git a/x-pack/plugin/vector-tile/build.gradle b/x-pack/plugin/vector-tile/build.gradle index b596480f71d0..a8d937dc5fe0 100644 --- a/x-pack/plugin/vector-tile/build.gradle +++ b/x-pack/plugin/vector-tile/build.gradle @@ -30,6 +30,7 @@ dependencies { compileOnly project(path: xpackModule('core')) compileOnly project(path: xpackModule('spatial')) testImplementation(testArtifact(project(xpackModule('core')))) + compileOnly "org.locationtech.jts:jts-core:${versions.jts}" api "com.wdtinc:mapbox-vector-tile:3.1.0" api "com.google.protobuf:protobuf-java:3.14.0" runtimeOnly("org.slf4j:slf4j-api:${versions.slf4j}")