LUCENE-8227: Redevelop path iterator implementations to make them robust against edges on paths.

This commit is contained in:
Karl Wright 2018-03-30 06:43:42 -04:00
parent 358e59596d
commit bc40f6c7e2
3 changed files with 231 additions and 497 deletions

View File

@ -19,6 +19,8 @@ package org.apache.lucene.spatial3d.geom;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
@ -157,6 +159,7 @@ class GeoComplexPolygon extends GeoBasePolygon {
@Override
public boolean isWithin(final double x, final double y, final double z) {
//System.out.println("\nIswithin called for ["+x+","+y+","+z+"]");
// If we're right on top of the point, we know the answer.
if (testPoint.isNumericallyIdentical(x, y, z)) {
return testPointInSet;
@ -366,11 +369,12 @@ class GeoComplexPolygon extends GeoBasePolygon {
if (!firstLegTree.traverse(edgeIterator, firstLegValue)) {
return true;
}
edgeIterator.setSecondLeg();
//edgeIterator.setSecondLeg();
if (!secondLegTree.traverse(edgeIterator, secondLegValue)) {
return true;
}
return ((edgeIterator.crossingCount & 1) == 0)?testPointInSet:!testPointInSet;
//System.out.println("Polarity vs. test point: "+(((edgeIterator.getCrossingCount() & 1) == 0)?"same":"different")+"; testPointInSet: "+testPointInSet);
return ((edgeIterator.getCrossingCount() & 1) == 0)?testPointInSet:!testPointInSet;
}
}
@ -502,6 +506,8 @@ class GeoComplexPolygon extends GeoBasePolygon {
this.planeBounds.addPlane(pm, this.plane, this.startPlane, this.endPlane);
//System.err.println("Recording edge "+this+" from "+startPoint+" to "+endPoint+"; bounds = "+planeBounds);
}
// Hashcode and equals are system default!!
}
/**
@ -798,7 +804,8 @@ class GeoComplexPolygon extends GeoBasePolygon {
private final double thePointY;
private final double thePointZ;
private int crossingCount = 0;
private int aboveCrossingCount = 0;
private int belowCrossingCount = 0;
public FullLinearCrossingEdgeIterator(final Plane plane, final Plane abovePlane, final Plane belowPlane, final double thePointX, final double thePointY, final double thePointZ) {
this.plane = plane;
@ -813,7 +820,11 @@ class GeoComplexPolygon extends GeoBasePolygon {
@Override
public int getCrossingCount() {
return crossingCount;
if (aboveCrossingCount < belowCrossingCount) {
return aboveCrossingCount;
} else {
return belowCrossingCount;
}
}
@Override
@ -822,131 +833,29 @@ class GeoComplexPolygon extends GeoBasePolygon {
if (edge.plane.evaluateIsZero(thePointX, thePointY, thePointZ) && edge.startPlane.isWithin(thePointX, thePointY, thePointZ) && edge.endPlane.isWithin(thePointX, thePointY, thePointZ)) {
return false;
}
final GeoPoint[] crossingPoints = plane.findCrossings(planetModel, edge.plane, bound, edge.startPlane, edge.endPlane);
if (crossingPoints != null) {
// We need to handle the endpoint case, which is quite tricky.
for (final GeoPoint crossingPoint : crossingPoints) {
countCrossingPoint(crossingPoint, edge);
}
// This should precisely mirror what is in DualCrossingIterator, but without the dual crossings.
// Some edges are going to be given to us even when there's no real intersection, so do that as a sanity check, first.
final GeoPoint[] planeCrossings = plane.findIntersections(planetModel, edge.plane, bound, edge.startPlane, edge.endPlane);
if (planeCrossings != null && planeCrossings.length == 0) {
// No actual crossing
return true;
}
// Determine crossings of this edge against all inside/outside planes. There's no further need to look at the actual travel plane itself.
final GeoPoint[] aboveCrossings = abovePlane.findCrossings(planetModel, edge.plane, bound, edge.startPlane, edge.endPlane);
final GeoPoint[] belowCrossings = belowPlane.findCrossings(planetModel, edge.plane, bound, edge.startPlane, edge.endPlane);
if (aboveCrossings != null) {
aboveCrossingCount += aboveCrossings.length;
}
if (belowCrossings != null) {
belowCrossingCount += belowCrossings.length;
}
return true;
}
private void countCrossingPoint(final GeoPoint crossingPoint, final Edge edge) {
if (crossingPoint.isNumericallyIdentical(edge.startPoint)) {
// We have to figure out if this crossing should be counted.
// Does the crossing for this edge go up, or down? Or can't we tell?
final GeoPoint[] aboveIntersections = abovePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
final GeoPoint[] belowIntersections = belowPlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
assert !(aboveIntersections.length > 0 && belowIntersections.length > 0) : "edge that ends in a crossing can't both up and down";
if (aboveIntersections.length == 0 && belowIntersections.length == 0) {
return;
}
final boolean edgeCrossesAbove = aboveIntersections.length > 0;
// This depends on the previous edge that first departs from identicalness.
Edge assessEdge = edge;
GeoPoint[] assessAboveIntersections;
GeoPoint[] assessBelowIntersections;
while (true) {
assessEdge = assessEdge.previous;
assessAboveIntersections = abovePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assessBelowIntersections = belowPlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assert !(assessAboveIntersections.length > 0 && assessBelowIntersections.length > 0) : "assess edge that ends in a crossing can't both up and down";
if (assessAboveIntersections.length == 0 && assessBelowIntersections.length == 0) {
continue;
}
break;
}
// Basically, we now want to assess whether both edges that come together at this endpoint leave the plane in opposite
// directions. If they do, then we should count it as a crossing; if not, we should not. We also have to remember that
// each edge we look at can also be looked at again if it, too, seems to cross the plane.
// To handle the latter situation, we need to know if the other edge will be looked at also, and then we can make
// a decision whether to count or not based on that.
// Compute the crossing points of this other edge.
final GeoPoint[] otherCrossingPoints = plane.findCrossings(planetModel, assessEdge.plane, bound, assessEdge.startPlane, assessEdge.endPlane);
// Look for a matching endpoint. If the other endpoint doesn't show up, it is either out of bounds (in which case the
// transition won't be counted for that edge), or it is not a crossing for that edge (so, same conclusion).
for (final GeoPoint otherCrossingPoint : otherCrossingPoints) {
if (otherCrossingPoint.isNumericallyIdentical(assessEdge.endPoint)) {
// Found it!
// Both edges will try to contribute to the crossing count. By convention, we'll only include the earlier one.
// Since we're the latter point, we exit here in that case.
return;
}
}
// Both edges will not count the same point, so we can proceed. We need to determine the direction of both edges at the
// point where they hit the plane. This may be complicated by the 3D geometry; it may not be safe just to look at the endpoints of the edges
// and make an assessment that way, since a single edge can intersect the plane at more than one point.
final boolean assessEdgeAbove = assessAboveIntersections.length > 0;
if (assessEdgeAbove != edgeCrossesAbove) {
crossingCount++;
}
} else if (crossingPoint.isNumericallyIdentical(edge.endPoint)) {
// Figure out if the crossing should be counted.
// Does the crossing for this edge go up, or down? Or can't we tell?
final GeoPoint[] aboveIntersections = abovePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
final GeoPoint[] belowIntersections = belowPlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
assert !(aboveIntersections.length > 0 && belowIntersections.length > 0) : "edge that ends in a crossing can't both up and down";
if (aboveIntersections.length == 0 && belowIntersections.length == 0) {
return;
}
final boolean edgeCrossesAbove = aboveIntersections.length > 0;
// This depends on the previous edge that first departs from identicalness.
Edge assessEdge = edge;
GeoPoint[] assessAboveIntersections;
GeoPoint[] assessBelowIntersections;
while (true) {
assessEdge = assessEdge.next;
assessAboveIntersections = abovePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assessBelowIntersections = belowPlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assert !(assessAboveIntersections.length > 0 && assessBelowIntersections.length > 0) : "assess edge that ends in a crossing can't both up and down";
if (assessAboveIntersections.length == 0 && assessBelowIntersections.length == 0) {
continue;
}
break;
}
// Basically, we now want to assess whether both edges that come together at this endpoint leave the plane in opposite
// directions. If they do, then we should count it as a crossing; if not, we should not. We also have to remember that
// each edge we look at can also be looked at again if it, too, seems to cross the plane.
// By definition, we're the earlier plane in this case, so any crossing we detect we must count, by convention. It is unnecessary
// to consider what the other edge does, because when we get to it, it will look back and figure out what we did for this one.
// We need to determine the direction of both edges at the
// point where they hit the plane. This may be complicated by the 3D geometry; it may not be safe just to look at the endpoints of the edges
// and make an assessment that way, since a single edge can intersect the plane at more than one point.
final boolean assessEdgeAbove = assessAboveIntersections.length > 0;
if (assessEdgeAbove != edgeCrossesAbove) {
crossingCount++;
}
} else {
crossingCount++;
}
}
}
/** Create a linear crossing edge iterator with the appropriate cutoff planes given the geometry.
@ -980,7 +889,8 @@ class GeoComplexPolygon extends GeoBasePolygon {
private final double thePointY;
private final double thePointZ;
private int crossingCount = 0;
private int aboveCrossingCount = 0;
private int belowCrossingCount = 0;
public SectorLinearCrossingEdgeIterator(final Plane plane, final Plane abovePlane, final Plane belowPlane, final double thePointX, final double thePointY, final double thePointZ) {
this.plane = plane;
@ -996,7 +906,11 @@ class GeoComplexPolygon extends GeoBasePolygon {
@Override
public int getCrossingCount() {
return crossingCount;
if (aboveCrossingCount < belowCrossingCount) {
return aboveCrossingCount;
} else {
return belowCrossingCount;
}
}
@Override
@ -1005,139 +919,38 @@ class GeoComplexPolygon extends GeoBasePolygon {
if (edge.plane.evaluateIsZero(thePointX, thePointY, thePointZ) && edge.startPlane.isWithin(thePointX, thePointY, thePointZ) && edge.endPlane.isWithin(thePointX, thePointY, thePointZ)) {
return false;
}
final GeoPoint[] crossingPoints = plane.findCrossings(planetModel, edge.plane, bound1, bound2, edge.startPlane, edge.endPlane);
if (crossingPoints != null) {
// We need to handle the endpoint case, which is quite tricky.
for (final GeoPoint crossingPoint : crossingPoints) {
countCrossingPoint(crossingPoint, edge);
}
// This should precisely mirror what is in DualCrossingIterator, but without the dual crossings.
// Some edges are going to be given to us even when there's no real intersection, so do that as a sanity check, first.
final GeoPoint[] planeCrossings = plane.findIntersections(planetModel, edge.plane, bound1, bound2, edge.startPlane, edge.endPlane);
if (planeCrossings != null && planeCrossings.length == 0) {
// No actual crossing
return true;
}
// Determine crossings of this edge against all inside/outside planes. There's no further need to look at the actual travel plane itself.
final GeoPoint[] aboveCrossings = abovePlane.findCrossings(planetModel, edge.plane, bound1, bound2, edge.startPlane, edge.endPlane);
final GeoPoint[] belowCrossings = belowPlane.findCrossings(planetModel, edge.plane, bound1, bound2, edge.startPlane, edge.endPlane);
if (aboveCrossings != null) {
aboveCrossingCount += aboveCrossings.length;
}
if (belowCrossings != null) {
belowCrossingCount += belowCrossings.length;
}
return true;
}
private void countCrossingPoint(final GeoPoint crossingPoint, final Edge edge) {
if (crossingPoint.isNumericallyIdentical(edge.startPoint)) {
// The crossing point is shared by two edges. If we are going to count it, this is the edge we'll count it on.
// We have to figure out if this crossing should be counted.
// We look at the above plane and the below plane and see if we cross either of them.
// If we cross NEITHER of them: we're in the "zone" between the planes, and this edge doesn't count.
// Does the crossing for this edge go up, or down? Or can't we tell?
final GeoPoint[] aboveIntersections = abovePlane.findCrossings(planetModel, edge.plane, edge.startPlane, edge.endPlane);
final GeoPoint[] belowIntersections = belowPlane.findCrossings(planetModel, edge.plane, edge.startPlane, edge.endPlane);
if ((aboveIntersections == null || aboveIntersections.length == 0) && (belowIntersections == null || belowIntersections.length == 0)) {
return;
}
// A null value means we have a situation where the edge is numerically identical. That's not counted as a "crossing".
assert !(aboveIntersections != null && aboveIntersections.length > 0 && belowIntersections != null && belowIntersections.length > 0) : "edge that ends in a crossing can't be both up and down!";
final boolean edgeCrossesAbove = aboveIntersections != null && aboveIntersections.length > 0;
// This depends on the previous edge that first departs from identicalness.
Edge assessEdge = edge;
GeoPoint[] assessAboveIntersections;
GeoPoint[] assessBelowIntersections;
while (true) {
assessEdge = assessEdge.previous;
assessAboveIntersections = abovePlane.findCrossings(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assessBelowIntersections = belowPlane.findCrossings(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
if ((assessAboveIntersections == null || assessAboveIntersections.length == 0) && (assessBelowIntersections == null || assessBelowIntersections.length == 0)) {
continue;
}
break;
}
// Basically, we now want to assess whether both edges that come together at this endpoint leave the plane in opposite
// directions. If they do, then we should count it as a crossing; if not, we should not. We also have to remember that
// each edge we look at can also be looked at again if it, too, seems to cross the plane.
// To handle the latter situation, we need to know if the other edge will be looked at also, and then we can make
// a decision whether to count or not based on that.
// Compute the crossing points of this other edge.
final GeoPoint[] otherCrossingPoints = plane.findCrossings(planetModel, assessEdge.plane, bound1, bound2, assessEdge.startPlane, assessEdge.endPlane);
// Look for a matching endpoint. If the other endpoint doesn't show up, it is either out of bounds (in which case the
// transition won't be counted for that edge), or it is not a crossing for that edge (so, same conclusion).
for (final GeoPoint otherCrossingPoint : otherCrossingPoints) {
if (otherCrossingPoint.isNumericallyIdentical(assessEdge.endPoint)) {
// Found it!
// Both edges will try to contribute to the crossing count. By convention, we'll only include the earlier one.
// Since we're the latter point, we exit here in that case.
return;
}
}
// Both edges will not count the same point, so we can proceed. We need to determine the direction of both edges at the
// point where they hit the plane. This may be complicated by the 3D geometry; it may not be safe just to look at the endpoints of the edges
// and make an assessment that way, since a single edge can intersect the plane at more than one point.
final boolean assessEdgeAbove = assessAboveIntersections != null && assessAboveIntersections.length > 0;
if (assessEdgeAbove != edgeCrossesAbove) {
crossingCount++;
}
} else if (crossingPoint.isNumericallyIdentical(edge.endPoint)) {
// Figure out if the crossing should be counted.
// Does the crossing for this edge go up, or down? Or can't we tell?
final GeoPoint[] aboveIntersections = abovePlane.findCrossings(planetModel, edge.plane, edge.startPlane, edge.endPlane);
final GeoPoint[] belowIntersections = belowPlane.findCrossings(planetModel, edge.plane, edge.startPlane, edge.endPlane);
if ((aboveIntersections == null || aboveIntersections.length == 0) && (belowIntersections == null || belowIntersections.length == 0)) {
return;
}
final boolean edgeCrossesAbove = aboveIntersections != null && aboveIntersections.length > 0;
// This depends on the previous edge that first departs from identicalness.
Edge assessEdge = edge;
GeoPoint[] assessAboveIntersections;
GeoPoint[] assessBelowIntersections;
while (true) {
assessEdge = assessEdge.next;
assessAboveIntersections = abovePlane.findCrossings(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assessBelowIntersections = belowPlane.findCrossings(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
if (assessAboveIntersections != null && assessAboveIntersections.length == 0 && assessBelowIntersections != null && assessBelowIntersections.length == 0) {
continue;
}
break;
}
// Basically, we now want to assess whether both edges that come together at this endpoint leave the plane in opposite
// directions. If they do, then we should count it as a crossing; if not, we should not. We also have to remember that
// each edge we look at can also be looked at again if it, too, seems to cross the plane.
// By definition, we're the earlier plane in this case, so any crossing we detect we must count, by convention. It is unnecessary
// to consider what the other edge does, because when we get to it, it will look back and figure out what we did for this one.
// We need to determine the direction of both edges at the
// point where they hit the plane. This may be complicated by the 3D geometry; it may not be safe just to look at the endpoints of the edges
// and make an assessment that way, since a single edge can intersect the plane at more than one point.
final boolean assessEdgeAbove = assessAboveIntersections != null && assessAboveIntersections.length > 0;
if (assessEdgeAbove != edgeCrossesAbove) {
crossingCount++;
}
} else {
crossingCount++;
}
}
}
/** Count the number of verifiable edge crossings for a dual-leg journey.
*/
private class DualCrossingEdgeIterator implements EdgeIterator {
private boolean isSecondLeg = false;
// This is a hash of which edges we've already looked at and tallied, so we don't repeat ourselves.
// It is lazily initialized since most transitions cross no edges at all.
private Set<Edge> seenEdges = null;
private final Plane testPointPlane;
private final Plane testPointAbovePlane;
@ -1163,10 +976,12 @@ class GeoComplexPolygon extends GeoBasePolygon {
private Plane travelOutsidePlane;
private SidedPlane insideTestPointCutoffPlane;
private SidedPlane insideTravelCutoffPlane;
private SidedPlane outsideTestPointCutoffPlane;
private SidedPlane outsideTravelCutoffPlane;
// The counter
public int crossingCount = 0;
// The counters
public int innerCrossingCount = 0;
public int outerCrossingCount = 0;
public DualCrossingEdgeIterator(final Plane testPointPlane, final Plane testPointAbovePlane, final Plane testPointBelowPlane,
final Plane travelPlane, final double thePointX, final double thePointY, final double thePointZ, final GeoPoint intersectionPoint) {
@ -1179,7 +994,7 @@ class GeoComplexPolygon extends GeoBasePolygon {
this.thePointZ = thePointZ;
this.intersectionPoint = intersectionPoint;
//System.err.println("Intersection point = "+intersectionPoint);
//System.out.println("Intersection point = "+intersectionPoint);
assert travelPlane.evaluateIsZero(intersectionPoint) : "intersection point must be on travel plane";
assert testPointPlane.evaluateIsZero(intersectionPoint) : "intersection point must be on test point plane";
@ -1216,6 +1031,9 @@ class GeoComplexPolygon extends GeoBasePolygon {
final Plane travelAbovePlane = new Plane(travelPlane, true);
final Plane travelBelowPlane = new Plane(travelPlane, false);
// Each of these can generate two solutions. We need to refine them to generate only one somehow -- the one in the same area of the world as intersectionPoint.
// Since the travel/testpoint planes have one fixed coordinate, and that is represented by the plane's D value, it should be possible to choose based on the
// point's coordinates.
final GeoPoint[] aboveAbove = travelAbovePlane.findIntersections(planetModel, testPointAbovePlane, intersectionBound1, intersectionBound2);
assert aboveAbove != null : "Above + above should not be coplanar";
final GeoPoint[] aboveBelow = travelAbovePlane.findIntersections(planetModel, testPointBelowPlane, intersectionBound1, intersectionBound2);
@ -1227,265 +1045,197 @@ class GeoComplexPolygon extends GeoBasePolygon {
assert ((aboveAbove.length > 0)?1:0) + ((aboveBelow.length > 0)?1:0) + ((belowBelow.length > 0)?1:0) + ((belowAbove.length > 0)?1:0) == 1 : "Can be exactly one inside point, instead was: aa="+aboveAbove.length+" ab=" + aboveBelow.length+" bb="+ belowBelow.length+" ba=" + belowAbove.length;
final GeoPoint[] insideInsidePoints;
if (aboveAbove.length > 0) {
travelInsidePlane = travelAbovePlane;
testPointInsidePlane = testPointAbovePlane;
travelOutsidePlane = travelBelowPlane;
testPointOutsidePlane = testPointBelowPlane;
insideInsidePoints = aboveAbove;
} else if (aboveBelow.length > 0) {
travelInsidePlane = travelAbovePlane;
testPointInsidePlane = testPointBelowPlane;
travelOutsidePlane = travelBelowPlane;
testPointOutsidePlane = testPointAbovePlane;
insideInsidePoints = aboveBelow;
} else if (belowBelow.length > 0) {
travelInsidePlane = travelBelowPlane;
testPointInsidePlane = testPointBelowPlane;
travelOutsidePlane = travelAbovePlane;
testPointOutsidePlane = testPointAbovePlane;
insideInsidePoints = belowBelow;
} else {
travelInsidePlane = travelBelowPlane;
testPointInsidePlane = testPointAbovePlane;
travelOutsidePlane = travelAbovePlane;
testPointOutsidePlane = testPointBelowPlane;
insideInsidePoints = belowAbove;
}
insideTravelCutoffPlane = new SidedPlane(thePointX, thePointY, thePointZ, testPointInsidePlane, testPointInsidePlane.D);
insideTestPointCutoffPlane = new SidedPlane(testPoint, travelInsidePlane, travelInsidePlane.D);
// Get the inside-inside intersection point
// Picking which point, out of two, that corresponds to the already-selected intersectionPoint, is tricky, but it must be done.
// We expect the choice to be within a small delta of the intersection point in 2 of the dimensions, but not the third
final GeoPoint insideInsidePoint = pickProximate(insideInsidePoints);
// Get the outside-outside intersection point
final GeoPoint[] outsideOutsidePoints = testPointOutsidePlane.findIntersections(planetModel, travelOutsidePlane); //these don't add anything: , checkPointCutoffPlane, testPointCutoffPlane);
final GeoPoint outsideOutsidePoint = pickProximate(outsideOutsidePoints);
insideTravelCutoffPlane = new SidedPlane(thePointX, thePointY, thePointZ, travelInsidePlane, insideInsidePoint);
outsideTravelCutoffPlane = new SidedPlane(thePointX, thePointY, thePointZ, travelInsidePlane, outsideOutsidePoint);
insideTestPointCutoffPlane = new SidedPlane(testPoint, testPointInsidePlane, insideInsidePoint);
outsideTestPointCutoffPlane = new SidedPlane(testPoint, testPointOutsidePlane, outsideOutsidePoint);
/*
System.out.println("insideTravelCutoffPlane = "+insideTravelCutoffPlane);
System.out.println("outsideTravelCutoffPlane = "+outsideTravelCutoffPlane);
System.out.println("insideTestPointCutoffPlane = "+insideTestPointCutoffPlane);
System.out.println("outsideTestPointCutoffPlane = "+outsideTestPointCutoffPlane);
*/
computedInsideOutside = true;
}
}
public void setSecondLeg() {
isSecondLeg = true;
private GeoPoint pickProximate(final GeoPoint[] points) {
if (points.length == 0) {
throw new IllegalArgumentException("No off-plane intersection points were found; can't compute traversal");
} else if (points.length == 1) {
return points[0];
} else {
final double p1dist = computeSquaredDistance(points[0], intersectionPoint);
final double p2dist = computeSquaredDistance(points[1], intersectionPoint);
if (p1dist < p2dist) {
return points[0];
} else if (p2dist < p1dist) {
return points[1];
} else {
throw new IllegalArgumentException("Neither off-plane intersection point matched intersection point; intersection = "+intersectionPoint+"; offplane choice 0: "+points[0]+"; offplane choice 1: "+points[1]);
}
}
}
public int getCrossingCount() {
// Doesn't return the actual crossing count -- just gets the even/odd part right
if (innerCrossingCount < outerCrossingCount) {
return innerCrossingCount;
} else {
return outerCrossingCount;
}
}
@Override
public boolean matches(final Edge edge) {
//System.err.println("Processing edge "+edge+", startpoint="+edge.startPoint+" endpoint="+edge.endPoint);
// Early exit if the point is on the edge.
// Early exit if the point is on the edge, in which case we accidentally discovered the answer.
if (edge.plane.evaluateIsZero(thePointX, thePointY, thePointZ) && edge.startPlane.isWithin(thePointX, thePointY, thePointZ) && edge.endPlane.isWithin(thePointX, thePointY, thePointZ)) {
//System.err.println(" Check point is on edge: isWithin = true");
return false;
}
// If the intersection point lies on this edge, we should still be able to consider crossing points only.
// Even if an intersection point is eliminated because it's not a crossing of one plane, it will have to be a crossing
// for at least one of the two planes in order to be a legitimate crossing of the combined path.
final GeoPoint[] crossingPoints;
if (isSecondLeg) {
//System.err.println(" check point plane = "+travelPlane);
crossingPoints = travelPlane.findCrossings(planetModel, edge.plane, checkPointCutoffPlane, checkPointOtherCutoffPlane, edge.startPlane, edge.endPlane);
} else {
//System.err.println(" test point plane = "+testPointPlane);
crossingPoints = testPointPlane.findCrossings(planetModel, edge.plane, testPointCutoffPlane, testPointOtherCutoffPlane, edge.startPlane, edge.endPlane);
// All edges that touch the travel planes get assessed the same. So, for each intersecting edge on both legs:
// (1) If the edge contains the intersection point, we analyze it on only one leg. For the other leg, we do nothing.
// (2) We compute the crossings of the edge with ALL FOUR inner and outer bounding planes.
// (3) We add the numbers of each kind of crossing to the total for that class of crossing (innerTotal and outerTotal).
// (4) When done all edges tallied in this way, we take min(innerTotal, outerTotal) and assume that is the number of crossings.
//
// Q: What if we see the same edge in both traversals?
// A: We should really evaluate it only in one. Keep a hash of the edges we've looked at already and don't process edges twice.
// Every edge should be looked at only once.
if (seenEdges != null && seenEdges.contains(edge)) {
return true;
}
if (crossingPoints != null) {
// We need to handle the endpoint case, which is quite tricky.
for (final GeoPoint crossingPoint : crossingPoints) {
countCrossingPoint(crossingPoint, edge);
if (seenEdges == null) {
seenEdges = new HashSet<>();
}
seenEdges.add(edge);
//System.out.println("Considering edge "+(edge.startPoint)+" -> "+(edge.endPoint));
// We've never seen this edge before. Evaluate it in the context of inner and outer planes.
computeInsideOutside();
/*
System.out.println("\nThe following edges should intersect the travel/testpoint planes:");
Edge thisEdge = edge;
while (true) {
final GeoPoint[] travelCrossings = travelPlane.findIntersections(planetModel, thisEdge.plane, checkPointCutoffPlane, checkPointOtherCutoffPlane, thisEdge.startPlane, thisEdge.endPlane);
if (travelCrossings == null || travelCrossings.length > 0) {
System.out.println("Travel plane: "+thisEdge.startPoint+" -> "+thisEdge.endPoint);
}
final GeoPoint[] testPointCrossings = testPointPlane.findIntersections(planetModel, thisEdge.plane, testPointCutoffPlane, testPointOtherCutoffPlane, thisEdge.startPlane, thisEdge.endPlane);
if (testPointCrossings == null || testPointCrossings.length > 0) {
System.out.println("Test point plane: "+thisEdge.startPoint+" -> "+thisEdge.endPoint);
}
thisEdge = thisEdge.next;
if (thisEdge == edge) {
break;
}
//System.err.println(" All crossing points processed");
} else {
//System.err.println(" No crossing points!");
}
System.out.println("");
*/
// Some edges are going to be given to us even when there's no real intersection, so do that as a sanity check, first.
final GeoPoint[] travelCrossings = travelPlane.findIntersections(planetModel, edge.plane, checkPointCutoffPlane, checkPointOtherCutoffPlane, edge.startPlane, edge.endPlane);
if (travelCrossings != null && travelCrossings.length == 0) {
final GeoPoint[] testPointCrossings = testPointPlane.findIntersections(planetModel, edge.plane, testPointCutoffPlane, testPointOtherCutoffPlane, edge.startPlane, edge.endPlane);
if (testPointCrossings != null && testPointCrossings.length == 0) {
return true;
}
}
// Determine crossings of this edge against all inside/outside planes. There's no further need to look at the actual travel plane itself.
final GeoPoint[] travelInnerCrossings = travelInsidePlane.findCrossings(planetModel, edge.plane, checkPointCutoffPlane, insideTravelCutoffPlane, edge.startPlane, edge.endPlane);
final GeoPoint[] travelOuterCrossings = travelOutsidePlane.findCrossings(planetModel, edge.plane, checkPointCutoffPlane, outsideTravelCutoffPlane, edge.startPlane, edge.endPlane);
final GeoPoint[] testPointInnerCrossings = testPointInsidePlane.findCrossings(planetModel, edge.plane, testPointCutoffPlane, insideTestPointCutoffPlane, edge.startPlane, edge.endPlane);
final GeoPoint[] testPointOuterCrossings = testPointOutsidePlane.findCrossings(planetModel, edge.plane, testPointCutoffPlane, outsideTestPointCutoffPlane, edge.startPlane, edge.endPlane);
// If the edge goes through the inner-inner intersection point, or the outer-outer intersection point, we need to be sure we count that only once.
// It may appear in both lists. Use a hash for this right now.
final Set<GeoPoint> countingHash = new HashSet<>(2);
if (travelInnerCrossings != null) {
for (final GeoPoint crossing : travelInnerCrossings) {
//System.out.println(" Travel inner point "+crossing);
countingHash.add(crossing);
}
}
if (testPointInnerCrossings != null) {
for (final GeoPoint crossing : testPointInnerCrossings) {
//System.out.println(" Test point inner point "+crossing);
countingHash.add(crossing);
}
}
//System.out.println(" Edge added "+countingHash.size()+" to innerCrossingCount");
innerCrossingCount += countingHash.size();
countingHash.clear();
if (travelOuterCrossings != null) {
for (final GeoPoint crossing : travelOuterCrossings) {
//System.out.println(" Travel outer point "+crossing);
countingHash.add(crossing);
}
}
if (testPointOuterCrossings != null) {
for (final GeoPoint crossing : testPointOuterCrossings) {
//System.out.println(" Test point outer point "+crossing);
countingHash.add(crossing);
}
}
//System.out.println(" Edge added "+countingHash.size()+" to outerCrossingCount");
outerCrossingCount += countingHash.size();
return true;
}
private void countCrossingPoint(final GeoPoint crossingPoint, final Edge edge) {
//System.err.println(" Crossing point "+crossingPoint);
// We consider crossing points only in this method.
// Unlike the linear case, there are additional cases when:
// (1) The crossing point and the intersection point are the same, but are not the endpoint of an edge;
// (2) The crossing point and the intersection point are the same, and they *are* the endpoint of an edge.
// The other logical difference is that crossings of all kinds have to be considered so that:
// (a) both inside edges are considered together at all times;
// (b) both outside edges are considered together at all times;
// (c) inside edge crossings that are between the other leg's inside and outside edge are ignored.
// Intersection point crossings are either simple, or a crossing on an endpoint.
// In either case, we have to be sure to count each edge only once, since it might appear in both the
// first leg and the second. If the first leg can process it, it should, and the second should skip it.
if (crossingPoint.isNumericallyIdentical(intersectionPoint)) {
//System.err.println(" Crosses intersection point.");
if (isSecondLeg) {
// See whether this edge would have been processed in the first leg; if so, we skip it.
final GeoPoint[] firstLegCrossings = testPointPlane.findCrossings(planetModel, edge.plane, testPointCutoffPlane, testPointOtherCutoffPlane, edge.startPlane, edge.endPlane);
for (final GeoPoint firstLegCrossing : firstLegCrossings) {
if (firstLegCrossing.isNumericallyIdentical(intersectionPoint)) {
// We already processed it, so we're done here.
//System.err.println(" Already processed on previous leg: exit");
return;
}
}
}
}
// Plane crossing, either first leg or second leg
if (crossingPoint.isNumericallyIdentical(edge.startPoint)) {
//System.err.println(" Crossing point = edge.startPoint");
// We have to figure out if this crossing should be counted.
computeInsideOutside();
// Does the crossing for this edge go up, or down? Or can't we tell?
final GeoPoint[] insideTestPointPlaneIntersections = testPointInsidePlane.findCrossings(planetModel, edge.plane, edge.startPlane, edge.endPlane, insideTestPointCutoffPlane);
final GeoPoint[] insideTravelPlaneIntersections = travelInsidePlane.findCrossings(planetModel, edge.plane, edge.startPlane, edge.endPlane, insideTravelCutoffPlane);
final GeoPoint[] outsideTestPointPlaneIntersections = testPointOutsidePlane.findCrossings(planetModel, edge.plane, edge.startPlane, edge.endPlane);
final GeoPoint[] outsideTravelPlaneIntersections = travelOutsidePlane.findCrossings(planetModel, edge.plane, edge.startPlane, edge.endPlane);
if ((insideTestPointPlaneIntersections == null || insideTestPointPlaneIntersections.length == 0) &&
(insideTravelPlaneIntersections == null || insideTravelPlaneIntersections.length == 0) &&
(outsideTestPointPlaneIntersections == null || outsideTestPointPlaneIntersections.length == 0) &&
(outsideTravelPlaneIntersections == null || outsideTravelPlaneIntersections.length == 0)) {
//System.err.println(" No inside or outside crossings found");
return;
}
final boolean edgeCrossesInside = insideTestPointPlaneIntersections.length + insideTravelPlaneIntersections.length > 0;
// This depends on the previous edge that first departs from identicalness.
Edge assessEdge = edge;
GeoPoint[] assessInsideTestPointIntersections;
GeoPoint[] assessInsideTravelIntersections;
GeoPoint[] assessOutsideTestPointIntersections;
GeoPoint[] assessOutsideTravelIntersections;
while (true) {
assessEdge = assessEdge.previous;
assessInsideTestPointIntersections = testPointInsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane, insideTestPointCutoffPlane);
assessInsideTravelIntersections = travelInsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane, insideTravelCutoffPlane);
assessOutsideTestPointIntersections = testPointOutsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assessOutsideTravelIntersections = travelOutsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
// If the assess edge is numerically identical to the edge we're trying to find the intersections with, there's not really a crossing, so count it as zero.
if ((assessInsideTestPointIntersections == null || assessInsideTestPointIntersections.length == 0) &&
(assessInsideTravelIntersections == null || assessInsideTravelIntersections.length == 0) &&
(assessOutsideTestPointIntersections == null || assessOutsideTestPointIntersections.length == 0) &&
(assessOutsideTravelIntersections == null || assessOutsideTravelIntersections.length == 0)) {
continue;
}
break;
}
// Basically, we now want to assess whether both edges that come together at this endpoint leave the plane in opposite
// directions. If they do, then we should count it as a crossing; if not, we should not. We also have to remember that
// each edge we look at can also be looked at again if it, too, seems to cross the plane.
// To handle the latter situation, we need to know if the other edge will be looked at also, and then we can make
// a decision whether to count or not based on that.
// Compute the crossing points of this other edge.
final GeoPoint[] otherCrossingPoints;
if (isSecondLeg) {
otherCrossingPoints = travelPlane.findCrossings(planetModel, assessEdge.plane, checkPointCutoffPlane, checkPointOtherCutoffPlane, assessEdge.startPlane, assessEdge.endPlane);
} else {
otherCrossingPoints = testPointPlane.findCrossings(planetModel, assessEdge.plane, testPointCutoffPlane, testPointOtherCutoffPlane, assessEdge.startPlane, assessEdge.endPlane);
}
if (otherCrossingPoints == null) {
// The assessEdge plane is the same as the travel plane. We consider this the same as "no crossing".
return;
}
// Look for a matching endpoint. If the other endpoint doesn't show up, it is either out of bounds (in which case the
// transition won't be counted for that edge), or it is not a crossing for that edge (so, same conclusion).
for (final GeoPoint otherCrossingPoint : otherCrossingPoints) {
if (otherCrossingPoint.isNumericallyIdentical(assessEdge.endPoint)) {
// Found it!
// Both edges will try to contribute to the crossing count. By convention, we'll only include the earlier one.
// Since we're the latter point, we exit here in that case.
//System.err.println(" Earlier point fired, so this one shouldn't");
return;
}
}
// Both edges will not count the same point, so we can proceed. We need to determine the direction of both edges at the
// point where they hit the plane. This may be complicated by the 3D geometry; it may not be safe just to look at the endpoints of the edges
// and make an assessment that way, since a single edge can intersect the plane at more than one point.
final boolean assessEdgeInside = (assessInsideTestPointIntersections != null && assessInsideTestPointIntersections.length > 0) ||
(assessInsideTravelIntersections != null && assessInsideTravelIntersections.length > 0);
if (assessEdgeInside != edgeCrossesInside) {
//System.err.println(" Incrementing crossing count");
crossingCount++;
} else {
//System.err.println(" Entered and exited on same side");
}
} else if (crossingPoint.isNumericallyIdentical(edge.endPoint)) {
//System.err.println(" Crossing point = edge.endPoint");
// Figure out if the crossing should be counted.
computeInsideOutside();
// If the assess edge is numerically identical to the edge we're trying to find the intersections with, there's not really a crossing, so count it as zero.
// Does the crossing for this edge go up, or down? Or can't we tell?
final GeoPoint[] insideTestPointPlaneIntersections = testPointInsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane, insideTestPointCutoffPlane);
final GeoPoint[] insideTravelPlaneIntersections = travelInsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane, insideTravelCutoffPlane);
final GeoPoint[] outsideTestPointPlaneIntersections = testPointOutsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
final GeoPoint[] outsideTravelPlaneIntersections = travelOutsidePlane.findIntersections(planetModel, edge.plane, edge.startPlane, edge.endPlane);
// An edge can cross both outside and inside, because of the corner. But it can be considered to cross the inside ONLY if it crosses either of the inside edges.
if ((insideTestPointPlaneIntersections == null || insideTestPointPlaneIntersections.length == 0) &&
(insideTravelPlaneIntersections == null || insideTravelPlaneIntersections.length == 0) &&
(outsideTestPointPlaneIntersections == null || outsideTestPointPlaneIntersections.length == 0) &&
(outsideTravelPlaneIntersections == null || outsideTravelPlaneIntersections.length == 0)) {
//System.err.println(" No inside or outside crossings found");
return;
}
final boolean edgeCrossesInside = (insideTestPointPlaneIntersections !=null && insideTestPointPlaneIntersections.length > 0) ||
(insideTravelPlaneIntersections != null && insideTravelPlaneIntersections.length > 0);
// This depends on the previous edge that first departs from identicalness.
Edge assessEdge = edge;
GeoPoint[] assessInsideTestPointIntersections;
GeoPoint[] assessInsideTravelIntersections;
GeoPoint[] assessOutsideTestPointIntersections;
GeoPoint[] assessOutsideTravelIntersections;
while (true) {
assessEdge = assessEdge.next;
assessInsideTestPointIntersections = testPointInsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane, insideTestPointCutoffPlane);
assessInsideTravelIntersections = travelInsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane, insideTravelCutoffPlane);
assessOutsideTestPointIntersections = testPointOutsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
assessOutsideTravelIntersections = travelOutsidePlane.findIntersections(planetModel, assessEdge.plane, assessEdge.startPlane, assessEdge.endPlane);
if ((assessInsideTestPointIntersections == null || assessInsideTestPointIntersections.length == 0) &&
(assessInsideTravelIntersections == null || assessInsideTravelIntersections.length == 0) &&
(assessOutsideTestPointIntersections == null || assessOutsideTestPointIntersections.length == 0) &&
(assessOutsideTravelIntersections == null || assessOutsideTravelIntersections.length == 0)) {
continue;
}
break;
}
// Basically, we now want to assess whether both edges that come together at this endpoint leave the plane in opposite
// directions. If they do, then we should count it as a crossing; if not, we should not. We also have to remember that
// each edge we look at can also be looked at again if it, too, seems to cross the plane.
// By definition, we're the earlier plane in this case, so any crossing we detect we must count, by convention. It is unnecessary
// to consider what the other edge does, because when we get to it, it will look back and figure out what we did for this one.
// We need to determine the direction of both edges at the
// point where they hit the plane. This may be complicated by the 3D geometry; it may not be safe just to look at the endpoints of the edges
// and make an assessment that way, since a single edge can intersect the plane at more than one point.
final boolean assessEdgeInside = (assessInsideTestPointIntersections !=null && assessInsideTestPointIntersections.length > 0) ||
(assessInsideTravelIntersections != null && assessInsideTravelIntersections.length > 0);
if (assessEdgeInside != edgeCrossesInside) {
//System.err.println(" Incrementing crossing count");
crossingCount++;
} else {
//System.err.println(" Entered and exited on same side");
}
} else {
//System.err.println(" Not a special case: incrementing crossing count");
// Not a special case, so we can safely count a crossing.
crossingCount++;
}
}
}
private static double computeSquaredDistance(final GeoPoint checkPoint, final GeoPoint intersectionPoint) {
final double distanceX = checkPoint.x - intersectionPoint.x;
final double distanceY = checkPoint.y - intersectionPoint.y;
final double distanceZ = checkPoint.z - intersectionPoint.z;
return distanceX * distanceX + distanceY * distanceY + distanceZ * distanceZ;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoComplexPolygon))

View File

@ -81,8 +81,6 @@ import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.StringHelper;
import org.apache.lucene.util.TestUtil;
import org.junit.Ignore;
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
public class TestGeo3DPoint extends LuceneTestCase {
@ -190,8 +188,7 @@ public class TestGeo3DPoint extends LuceneTestCase {
}
/** Tests consistency of GeoArea.getRelationship vs GeoShape.isWithin */
//@AwaitsFix("https://issues.apache.org/jira/browse/LUCENE-8227")
@Ignore
@AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/LUCENE-8227")
public void testGeo3DRelations() throws Exception {
int numDocs = atLeast(1000);
@ -471,22 +468,16 @@ public class TestGeo3DPoint extends LuceneTestCase {
}
}
//@AwaitsFix("https://issues.apache.org/jira/browse/LUCENE-8227")
@Ignore
public void testRandomTiny() throws Exception {
// Make sure single-leaf-node case is OK:
doTestRandom(10);
}
//@AwaitsFix("https://issues.apache.org/jira/browse/LUCENE-8227")
@Ignore
public void testRandomMedium() throws Exception {
doTestRandom(10000);
}
@Nightly
//@AwaitsFix("https://issues.apache.org/jira/browse/LUCENE-8227")
@Ignore
public void testRandomBig() throws Exception {
doTestRandom(50000);
}

View File

@ -253,6 +253,7 @@ public class GeoPolygonTest {
shapes.add(pd);
c = GeoPolygonFactory.makeLargeGeoPolygon(PlanetModel.SPHERE, shapes);
//System.out.println("Large polygon = "+c);
// Sample some points within
gp = new GeoPoint(PlanetModel.SPHERE, 0.0, -0.45);
@ -1217,7 +1218,6 @@ shape:
[junit4] 1> quantized=[X=-0.9951793580415914, Y=-0.10888987641797832, Z=-2.3309121299774915E-10]
*/
@Test
@Ignore
public void testLUCENE8227() throws Exception {
List<GeoPoint> points = new ArrayList<>();
points.add(new GeoPoint(PlanetModel.WGS84, -0.63542308910253, 0.9853722928232957));
@ -1227,9 +1227,11 @@ shape:
points.add(new GeoPoint(PlanetModel.WGS84, -1.2205765069413237, 3.141592653589793));
GeoPolygonFactory.PolygonDescription pd = new GeoPolygonFactory.PolygonDescription(points);
/*
for (int i = 0; i < points.size(); i++) {
System.out.println("Point "+i+": "+points.get(i));
}
*/
final GeoPoint unquantized = new GeoPoint(PlanetModel.WGS84, -3.1780051348770987E-74, -3.032608859187692);
final GeoPoint quantized = new GeoPoint(-0.9951793580415914, -0.10888987641797832, -2.3309121299774915E-10);
@ -1237,31 +1239,22 @@ shape:
final GeoPoint negativeX = new GeoPoint(PlanetModel.WGS84, 0.0, Math.PI);
final GeoPoint negativeY = new GeoPoint(PlanetModel.WGS84, 0.0, -Math.PI * 0.5);
// Construct a standard polygon first to see what that does
// Construct a standard polygon first to see what that does. This winds up being a large polygon under the covers.
GeoPolygon standard = GeoPolygonFactory.makeGeoPolygon(PlanetModel.WGS84, pd);
System.out.println("Standard polygon: "+standard);
// This shows y < 0 hemisphere is all in-set
//assertTrue(standard.isWithin(negativeY));
// This should be in-set too, but isn't!!
assertTrue(standard.isWithin(negativeX));
/*
final XYZBounds standardBounds = new XYZBounds();
standard.getBounds(standardBounds);
final XYZSolid standardSolid = XYZSolidFactory.makeXYZSolid(PlanetModel.WGS84, standardBounds);
System.out.println("Standard bounds: "+standardBounds);
// If within shape, should be within bounds
assertTrue(standard.isWithin(quantized)?standardSolid.isWithin(quantized):true);
assertTrue(standard.isWithin(unquantized)?standardSolid.isWithin(unquantized):true);
assertFalse(standardSolid.isWithin(quantized));
assertFalse(standardSolid.isWithin(unquantized));
*/
// Now, both points should also not be in the poly
assertFalse(standard.isWithin(unquantized));
assertFalse(standard.isWithin(quantized));
}
}