diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index a55a5e35857..0376172cd3a 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -150,6 +150,10 @@ Bug Fixes handling a tragic exception but another is still committing (Mike McCandless) +* LUCENE-6810: Upgrade to Spatial4j 0.5 -- fixes some edge-case bugs in the + spatial module. See https://github.com/locationtech/spatial4j/blob/master/CHANGES.md + (David Smiley) + Other * LUCENE-6812: Upgrade RandomizedTesting to 2.1.17. (Dawid Weiss) diff --git a/lucene/ivy-versions.properties b/lucene/ivy-versions.properties index cb586eb3468..186eb781c71 100644 --- a/lucene/ivy-versions.properties +++ b/lucene/ivy-versions.properties @@ -41,7 +41,7 @@ com.google.inject.guice.version = 3.0 /com.googlecode.mp4parser/isoparser = 1.0.2 /com.ibm.icu/icu4j = 54.1 /com.pff/java-libpst = 0.8.1 -/com.spatial4j/spatial4j = 0.4.1 +/com.spatial4j/spatial4j = 0.5 com.sun.jersey.version = 1.9 /com.sun.jersey.contribs/jersey-guice = ${com.sun.jersey.version} diff --git a/lucene/licenses/slf4j-api-1.7.7.jar.sha1 b/lucene/licenses/slf4j-api-1.7.7.jar.sha1 new file mode 100644 index 00000000000..1a822184da3 --- /dev/null +++ b/lucene/licenses/slf4j-api-1.7.7.jar.sha1 @@ -0,0 +1 @@ +2b8019b6249bb05d81d3a3094e468753e2b21311 diff --git a/lucene/licenses/spatial4j-0.4.1.jar.sha1 b/lucene/licenses/spatial4j-0.4.1.jar.sha1 deleted file mode 100644 index 1c2883bd830..00000000000 --- a/lucene/licenses/spatial4j-0.4.1.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -4234d12b1ba4d4b539fb3e29edd948a99539d9eb diff --git a/lucene/licenses/spatial4j-0.5-tests.jar.sha1 b/lucene/licenses/spatial4j-0.5-tests.jar.sha1 new file mode 100644 index 00000000000..0c514f8835e --- /dev/null +++ b/lucene/licenses/spatial4j-0.5-tests.jar.sha1 @@ -0,0 +1 @@ +bdcdf20a723516a233b5bcc0ca7d4decaa88b6ed diff --git a/lucene/licenses/spatial4j-0.5.jar.sha1 b/lucene/licenses/spatial4j-0.5.jar.sha1 new file mode 100644 index 00000000000..c81a76cbe91 --- /dev/null +++ b/lucene/licenses/spatial4j-0.5.jar.sha1 @@ -0,0 +1 @@ +6e16edaf6b1ba76db7f08c2f3723fce3b358ecc3 diff --git a/lucene/spatial/ivy.xml b/lucene/spatial/ivy.xml index f98a85fb2ae..45a0f3a5f23 100644 --- a/lucene/spatial/ivy.xml +++ b/lucene/spatial/ivy.xml @@ -16,13 +16,20 @@ specific language governing permissions and limitations under the License. --> - + - + + - - + + + + + + + + diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/NumberRangePrefixTree.java b/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/NumberRangePrefixTree.java index f308e8c7a4f..d8efe3cd424 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/NumberRangePrefixTree.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/NumberRangePrefixTree.java @@ -266,6 +266,11 @@ public abstract class NumberRangePrefixTree extends SpatialPrefixTree { lastLevelInCommon = level - 1; } + @Override + public SpatialContext getContext() { + return DUMMY_CTX; + } + public UnitNRShape getMinUnit() { return minLV; } public UnitNRShape getMaxUnit() { return maxLV; } @@ -953,6 +958,11 @@ public abstract class NumberRangePrefixTree extends SpatialPrefixTree { return answer; } + @Override + public SpatialContext getContext() { + return DUMMY_CTX; + } + @Override public int hashCode() { //trick to re-use bytesref; provided that we re-instate it diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java b/lucene/spatial/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java index 7edd5d89c65..19118a5de6b 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/spatial4j/Geo3dShape.java @@ -61,6 +61,11 @@ public class Geo3dShape implements Shape { this.shape = shape; } + @Override + public SpatialContext getContext() { + return ctx; + } + @Override public SpatialRelation relate(Shape other) { if (other instanceof Rectangle) diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/bbox/TestBBoxStrategy.java b/lucene/spatial/src/test/org/apache/lucene/spatial/bbox/TestBBoxStrategy.java index 665b3586bf4..f24554dd6d3 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/bbox/TestBBoxStrategy.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/bbox/TestBBoxStrategy.java @@ -57,27 +57,18 @@ public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase { int worldHeight = (int) Math.round(world.getHeight()); int deltaTop = nextIntInclusive(worldHeight); int deltaBottom = nextIntInclusive(worldHeight - deltaTop); - - double rectMinX = world.getMinX() + deltaLeft; - double rectMaxX = world.getMaxX() - deltaRight; - if (ctx.isGeo()) { - int shift = 0; - if ((deltaLeft != 0 || deltaRight != 0)) { - //if geo & doesn't world-wrap, we shift randomly to potentially cross dateline - shift = nextIntInclusive(360); - } - rectMinX = DistanceUtils.normLonDEG(rectMinX + shift); - rectMaxX = DistanceUtils.normLonDEG(rectMaxX + shift); - if (rectMinX == 180 && rectMaxX == 180) { - // Work-around for https://github.com/spatial4j/spatial4j/issues/85 - rectMinX = -180; - rectMaxX = -180; - } + if (ctx.isGeo() && (deltaLeft != 0 || deltaRight != 0)) { + //if geo & doesn't world-wrap, we shift randomly to potentially cross dateline + int shift = nextIntInclusive(360); + return ctx.makeRectangle( + DistanceUtils.normLonDEG(world.getMinX() + deltaLeft + shift), + DistanceUtils.normLonDEG(world.getMaxX() - deltaRight + shift), + world.getMinY() + deltaBottom, world.getMaxY() - deltaTop); + } else { + return ctx.makeRectangle( + world.getMinX() + deltaLeft, world.getMaxX() - deltaRight, + world.getMinY() + deltaBottom, world.getMaxY() - deltaTop); } - return ctx.makeRectangle( - rectMinX, - rectMaxX, - world.getMinY() + deltaBottom, world.getMaxY() - deltaTop); } /** next int, inclusive, rounds to multiple of 10 if given evenly divisible. */ diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/RandomSpatialOpFuzzyPrefixTreeTest.java b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/RandomSpatialOpFuzzyPrefixTreeTest.java index 16ff56f2700..8750c6472f4 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/RandomSpatialOpFuzzyPrefixTreeTest.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/RandomSpatialOpFuzzyPrefixTreeTest.java @@ -447,7 +447,7 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase { final boolean biasContainsThenWithin; public ShapePair(Shape shape1, Shape shape2, boolean containsThenWithin) { - super(Arrays.asList(shape1, shape2), ctx); + super(Arrays.asList(shape1, shape2), RandomSpatialOpFuzzyPrefixTreeTest.this.ctx); this.shape1 = shape1; this.shape2 = shape2; this.shape1_2D = toNonGeo(shape1); diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java b/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java index aee459adeae..42bc2e2a5f2 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java @@ -22,10 +22,12 @@ import java.util.List; import java.util.Random; import com.carrotsearch.randomizedtesting.RandomizedContext; +import com.spatial4j.core.TestLog; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; +import com.spatial4j.core.shape.RectIntersectionTestHelper; import org.apache.lucene.geo3d.LatLonBounds; import org.apache.lucene.geo3d.GeoBBox; import org.apache.lucene.geo3d.GeoBBoxFactory; @@ -44,7 +46,7 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest protected final static double RADIANS_PER_DEGREE = Math.PI/180.0; @Rule - public final LogRule testLog = LogRule.instance; + public final TestLog testLog = TestLog.instance; protected static Random random() { return RandomizedContext.current().getRandom(); @@ -91,15 +93,26 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest super(ctx); } - @Override - protected int getMaxLaps() { - //sometimes, getWithinMinimum needs some more attempts then normal; 20k is suggested max. - return 200_000;//200k + //20 times each -- should be plenty + + protected int getContainsMinimum(int laps) { + return 20; } - @Override - protected int getDefaultMinimumPredicateFrequency(int maxLaps) { - return 20;//20 times each -- should be plenty in 200k + protected int getIntersectsMinimum(int laps) { + return 20; + } + + protected int getWithinMinimum(int laps) { + return 20; + } + + protected int getDisjointMinimum(int laps) { + return 20; + } + + protected int getBoundingMinimum(int laps) { + return 20; } } diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/LogRule.java b/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/LogRule.java deleted file mode 100644 index 36681256c98..00000000000 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/LogRule.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.apache.lucene.spatial.spatial4j; - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter; - -/** - * A utility logger for tests in which log statements are logged following - * test failure only. Add this to a JUnit based test class with a {@link org.junit.Rule} - * annotation. - */ -public class LogRule extends TestRuleAdapter { - - //TODO does this need to be threadsafe (such as via thread-local state)? - private static ArrayList logStack = new ArrayList(); - private static final int MAX_LOGS = 1000; - - public static final LogRule instance = new LogRule(); - - private LogRule() {} - - @Override - protected void before() throws Throwable { - logStack.clear(); - } - - @Override - protected void afterAlways(List errors) throws Throwable { - if (!errors.isEmpty()) - logThenClear(); - } - - private void logThenClear() { - for (LogEntry entry : logStack) { - //no SLF4J in Lucene... fallback to this - if (entry.args != null && entry.args.length > 0) { - System.out.println(entry.msg + " " + Arrays.asList(entry.args) + "(no slf4j subst; sorry)"); - } else { - System.out.println(entry.msg); - } - } - logStack.clear(); - } - - public static void clear() { - logStack.clear(); - } - - /** - * Enqueues a log message with substitution arguments ala SLF4J (i.e. {} syntax). - * If the test fails then it'll be logged then, otherwise it'll be forgotten. - */ - public static void log(String msg, Object... args) { - if (logStack.size() > MAX_LOGS) { - throw new RuntimeException("Too many log statements: "+logStack.size() + " > "+MAX_LOGS); - } - LogEntry entry = new LogEntry(); - entry.msg = msg; - entry.args = args; - logStack.add(entry); - } - - private static class LogEntry { String msg; Object[] args; } -} diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RectIntersectionTestHelper.java b/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RectIntersectionTestHelper.java deleted file mode 100644 index 7fcda14cb72..00000000000 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RectIntersectionTestHelper.java +++ /dev/null @@ -1,242 +0,0 @@ -package org.apache.lucene.spatial.spatial4j; - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.spatial4j.core.context.SpatialContext; -import com.spatial4j.core.shape.Point; -import com.spatial4j.core.shape.Rectangle; -import com.spatial4j.core.shape.Shape; -import com.spatial4j.core.shape.SpatialRelation; -import com.spatial4j.core.shape.impl.InfBufLine; -import com.spatial4j.core.shape.impl.PointImpl; - -import static com.spatial4j.core.shape.SpatialRelation.CONTAINS; -import static com.spatial4j.core.shape.SpatialRelation.DISJOINT; - -public abstract class RectIntersectionTestHelper extends RandomizedShapeTestCase { - - public RectIntersectionTestHelper(SpatialContext ctx) { - super(ctx); - } - - /** Override to return true if generateRandomShape is essentially a Rectangle. */ - protected boolean isRandomShapeRectangular() { - return false; - } - - protected abstract S generateRandomShape(Point nearP); - - /** shape has no area; return a point in it */ - protected abstract Point randomPointInEmptyShape(S shape); - - // Minimum distribution of relationships - - // Each shape has different characteristics, so we don't expect (for instance) shapes that - // are likely to be long and thin to contain as many rectangles as those that - // short and fat. - - /** Called once by {@link #testRelateWithRectangle()} to determine the max laps to try before failing. */ - protected int getMaxLaps() { - return scaledRandomIntBetween(20_000, 200_000); - } - - /** The minimum number of times we need to see each predicate in {@code laps} iterations. */ - protected int getDefaultMinimumPredicateFrequency(int maxLaps) { - return maxLaps / 1000; - } - - protected int getContainsMinimum(int maxLaps) { - return getDefaultMinimumPredicateFrequency(maxLaps); - } - - protected int getIntersectsMinimum(int maxLaps) { - return getDefaultMinimumPredicateFrequency(maxLaps); - } - - protected int getWithinMinimum(int maxLaps) { - return getDefaultMinimumPredicateFrequency(maxLaps); - } - - protected int getDisjointMinimum(int maxLaps) { - return getDefaultMinimumPredicateFrequency(maxLaps); - } - - protected int getBoundingMinimum(int maxLaps) { - return getDefaultMinimumPredicateFrequency(maxLaps); - } - - @SuppressWarnings("unchecked") - @Override - protected Point randomPointInOrNull(Shape shape) { - if (!shape.hasArea()) { - final Point pt = randomPointInEmptyShape((S) shape); - assert shape.relate(pt).intersects() : "faulty randomPointInEmptyShape"; - return pt; - } - return super.randomPointInOrNull(shape); - } - - public void testRelateWithRectangle() { - //counters for the different intersection cases - int i_C = 0, i_I = 0, i_W = 0, i_D = 0, i_bboxD = 0; - int lap = 0; - final int MAXLAPS = getMaxLaps(); - while(i_C < getContainsMinimum(MAXLAPS) || i_I < getIntersectsMinimum(MAXLAPS) || i_W < getWithinMinimum(MAXLAPS) - || (!isRandomShapeRectangular() && i_D < getDisjointMinimum(MAXLAPS)) || i_bboxD < getBoundingMinimum(MAXLAPS)) { - lap++; - - LogRule.clear(); - - if (lap > MAXLAPS) { - fail("Did not find enough contains/within/intersection/disjoint/bounds cases in a reasonable number" + - " of attempts. CWIDbD: " + - i_C + "("+getContainsMinimum(MAXLAPS)+")," + - i_W + "("+getWithinMinimum(MAXLAPS)+")," + - i_I + "("+getIntersectsMinimum(MAXLAPS)+")," + - i_D + "("+getDisjointMinimum(MAXLAPS)+")," + - i_bboxD + "("+getBoundingMinimum(MAXLAPS)+")" - + " Laps exceeded " + MAXLAPS); - } - - Point nearP = randomPointIn(ctx.getWorldBounds()); - - S s = generateRandomShape(nearP); - - Rectangle r = randomRectangle(s.getBoundingBox().getCenter()); - - SpatialRelation ic = s.relate(r); - - LogRule.log("S-R Rel: {}, Shape {}, Rectangle {} lap# {}", ic, s, r, lap); - - if (ic != DISJOINT) { - assertTrue("if not disjoint then the shape's bbox shouldn't be disjoint", - s.getBoundingBox().relate(r).intersects()); - } - - try { - int MAX_TRIES = scaledRandomIntBetween(10, 100); - switch (ic) { - case CONTAINS: - i_C++; - for (int j = 0; j < MAX_TRIES; j++) { - Point p = randomPointIn(r); - assertRelation(null, CONTAINS, s, p); - } - break; - - case WITHIN: - i_W++; - for (int j = 0; j < MAX_TRIES; j++) { - Point p = randomPointInOrNull(s); - if (p == null) {//couldn't find a random point in shape - break; - } - assertRelation(null, CONTAINS, r, p); - } - break; - - case DISJOINT: - if (!s.getBoundingBox().relate(r).intersects()) {//bboxes are disjoint - i_bboxD++; - if (i_bboxD >= getBoundingMinimum(MAXLAPS)) - break; - } else { - i_D++; - } - for (int j = 0; j < MAX_TRIES; j++) { - Point p = randomPointIn(r); - assertRelation(null, DISJOINT, s, p); - } - break; - - case INTERSECTS: - i_I++; - SpatialRelation pointR = null;//set once - Rectangle randomPointSpace = null; - MAX_TRIES = 1000;//give many attempts - for (int j = 0; j < MAX_TRIES; j++) { - Point p; - if (j < 4) { - p = new PointImpl(0, 0, ctx); - InfBufLine.cornerByQuadrant(r, j + 1, p); - } else { - if (randomPointSpace == null) { - if (pointR == DISJOINT) { - randomPointSpace = intersectRects(r,s.getBoundingBox()); - } else {//CONTAINS - randomPointSpace = r; - } - } - p = randomPointIn(randomPointSpace); - } - SpatialRelation pointRNew = s.relate(p); - if (pointR == null) { - pointR = pointRNew; - } else if (pointR != pointRNew) { - break; - } else if (j >= MAX_TRIES) { - //TODO consider logging instead of failing - fail("Tried intersection brute-force too many times without success"); - } - } - - break; - - default: fail(""+ic); - } // switch - } catch (AssertionError e) { - onAssertFail(e, s, r, ic); - } - - } // while loop - - System.out.println("Laps: "+lap + " CWIDbD: "+i_C+","+i_W+","+i_I+","+i_D+","+i_bboxD); - } - - protected void onAssertFail(AssertionError e, S s, Rectangle r, SpatialRelation ic) { - throw e; - } - - private Rectangle intersectRects(Rectangle r1, Rectangle r2) { - assert r1.relate(r2).intersects(); - final double minX, maxX; - if (r1.relateXRange(r2.getMinX(),r2.getMinX()).intersects()) { - minX = r2.getMinX(); - } else { - minX = r1.getMinX(); - } - if (r1.relateXRange(r2.getMaxX(),r2.getMaxX()).intersects()) { - maxX = r2.getMaxX(); - } else { - maxX = r1.getMaxX(); - } - final double minY, maxY; - if (r1.relateYRange(r2.getMinY(),r2.getMinY()).intersects()) { - minY = r2.getMinY(); - } else { - minY = r1.getMinY(); - } - if (r1.relateYRange(r2.getMaxY(),r2.getMaxY()).intersects()) { - maxY = r2.getMaxY(); - } else { - maxY = r1.getMaxY(); - } - return ctx.makeRectangle(minX, maxX, minY, maxY); - } - -} diff --git a/solr/licenses/spatial4j-0.5.jar.sha1 b/solr/licenses/spatial4j-0.5.jar.sha1 new file mode 100644 index 00000000000..c81a76cbe91 --- /dev/null +++ b/solr/licenses/spatial4j-0.5.jar.sha1 @@ -0,0 +1 @@ +6e16edaf6b1ba76db7f08c2f3723fce3b358ecc3