mirror of https://github.com/apache/lucene.git
LUCENE-7226: Clean polygon data, where feasible.
This commit is contained in:
parent
c08f644841
commit
35e0e92bb3
|
@ -139,24 +139,26 @@ public class GeoPolygonFactory {
|
||||||
final List<GeoPolygon> holes,
|
final List<GeoPolygon> holes,
|
||||||
final GeoPoint testPoint,
|
final GeoPoint testPoint,
|
||||||
final boolean testPointInside) {
|
final boolean testPointInside) {
|
||||||
|
// First, exercise a sanity filter on the provided pointList, and remove identical points, linear points, and backtracks
|
||||||
|
final List<GeoPoint> filteredPointList = filterPoints(pointList);
|
||||||
// We will be trying twice to find the right GeoPolygon, using alternate siding choices for the first polygon
|
// We will be trying twice to find the right GeoPolygon, using alternate siding choices for the first polygon
|
||||||
// side. While this looks like it might be 2x as expensive as it could be, there's really no other choice I can
|
// side. While this looks like it might be 2x as expensive as it could be, there's really no other choice I can
|
||||||
// find.
|
// find.
|
||||||
final SidedPlane initialPlane = new SidedPlane(testPoint, pointList.get(0), pointList.get(1));
|
final SidedPlane initialPlane = new SidedPlane(testPoint, filteredPointList.get(0), filteredPointList.get(1));
|
||||||
// We don't know if this is the correct siding choice. We will only know as we build the complex polygon.
|
// We don't know if this is the correct siding choice. We will only know as we build the complex polygon.
|
||||||
// So we need to be prepared to try both possibilities.
|
// So we need to be prepared to try both possibilities.
|
||||||
GeoCompositePolygon rval = new GeoCompositePolygon();
|
GeoCompositePolygon rval = new GeoCompositePolygon();
|
||||||
if (buildPolygonShape(rval, planetModel, pointList, new BitSet(), 0, 1, initialPlane, holes, testPoint) == false) {
|
if (buildPolygonShape(rval, planetModel, filteredPointList, new BitSet(), 0, 1, initialPlane, holes, testPoint) == false) {
|
||||||
// The testPoint was within the shape. Was that intended?
|
// The testPoint was within the shape. Was that intended?
|
||||||
if (testPointInside) {
|
if (testPointInside) {
|
||||||
// Yes: build it for real
|
// Yes: build it for real
|
||||||
rval = new GeoCompositePolygon();
|
rval = new GeoCompositePolygon();
|
||||||
buildPolygonShape(rval, planetModel, pointList, new BitSet(), 0, 1, initialPlane, holes, null);
|
buildPolygonShape(rval, planetModel, filteredPointList, new BitSet(), 0, 1, initialPlane, holes, null);
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
// No: do the complement and return that.
|
// No: do the complement and return that.
|
||||||
rval = new GeoCompositePolygon();
|
rval = new GeoCompositePolygon();
|
||||||
buildPolygonShape(rval, planetModel, pointList, new BitSet(), 0, 1, new SidedPlane(initialPlane), holes, null);
|
buildPolygonShape(rval, planetModel, filteredPointList, new BitSet(), 0, 1, new SidedPlane(initialPlane), holes, null);
|
||||||
return rval;
|
return rval;
|
||||||
} else {
|
} else {
|
||||||
// The testPoint was outside the shape. Was that intended?
|
// The testPoint was outside the shape. Was that intended?
|
||||||
|
@ -166,11 +168,104 @@ public class GeoPolygonFactory {
|
||||||
}
|
}
|
||||||
// No: return the complement
|
// No: return the complement
|
||||||
rval = new GeoCompositePolygon();
|
rval = new GeoCompositePolygon();
|
||||||
buildPolygonShape(rval, planetModel, pointList, new BitSet(), 0, 1, new SidedPlane(initialPlane), holes, null);
|
buildPolygonShape(rval, planetModel, filteredPointList, new BitSet(), 0, 1, new SidedPlane(initialPlane), holes, null);
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Filter duplicate points and coplanar points.
|
||||||
|
*/
|
||||||
|
static List<GeoPoint> filterPoints(final List<GeoPoint> input) {
|
||||||
|
|
||||||
|
final List<GeoPoint> noIdenticalPoints = new ArrayList<>(input.size());
|
||||||
|
|
||||||
|
// Backtrack to find something different from the first point
|
||||||
|
int startIndex = -1;
|
||||||
|
final GeoPoint comparePoint = input.get(0);
|
||||||
|
for (int i = 0; i < input.size()-1; i++) {
|
||||||
|
final GeoPoint thePoint = input.get(getLegalIndex(- i - 1, input.size()));
|
||||||
|
if (!thePoint.isNumericallyIdentical(comparePoint)) {
|
||||||
|
startIndex = getLegalIndex(-i, input.size());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (startIndex == -1) {
|
||||||
|
throw new IllegalArgumentException("polygon is degenerate: all points are identical");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can start the process of walking around, removing duplicate points.
|
||||||
|
int currentIndex = startIndex;
|
||||||
|
while (true) {
|
||||||
|
final GeoPoint currentPoint = input.get(currentIndex);
|
||||||
|
noIdenticalPoints.add(currentPoint);
|
||||||
|
while (true) {
|
||||||
|
currentIndex = getLegalIndex(currentIndex + 1, input.size());
|
||||||
|
if (currentIndex == startIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final GeoPoint nextNonIdenticalPoint = input.get(currentIndex);
|
||||||
|
if (!nextNonIdenticalPoint.isNumericallyIdentical(currentPoint)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentIndex == startIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noIdenticalPoints.size() < 3) {
|
||||||
|
throw new IllegalArgumentException("polygon has fewer than three non-identical points");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next step: remove coplanar points and backtracks. For this, we use a strategy that is similar but we assess whether the points
|
||||||
|
// are on the same plane, taking the first and last points on the same plane only.
|
||||||
|
|
||||||
|
final List<GeoPoint> nonCoplanarPoints = new ArrayList<>(noIdenticalPoints.size());
|
||||||
|
|
||||||
|
int startPlaneIndex = -1;
|
||||||
|
final Plane comparePlane = new Plane(noIdenticalPoints.get(0), noIdenticalPoints.get(1));
|
||||||
|
for (int i = 0; i < noIdenticalPoints.size()-1; i++) {
|
||||||
|
final GeoPoint thePoint = noIdenticalPoints.get(getLegalIndex(- i - 1, noIdenticalPoints.size()));
|
||||||
|
if (!comparePlane.evaluateIsZero(thePoint)) {
|
||||||
|
startPlaneIndex = getLegalIndex(-i, noIdenticalPoints.size());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (startPlaneIndex == -1) {
|
||||||
|
throw new IllegalArgumentException("polygon is degenerate: all points are coplanar");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can start the process of walking around, removing duplicate points.
|
||||||
|
int currentPlaneIndex = startPlaneIndex;
|
||||||
|
while (true) {
|
||||||
|
final GeoPoint currentPoint = noIdenticalPoints.get(currentPlaneIndex);
|
||||||
|
nonCoplanarPoints.add(currentPoint);
|
||||||
|
int nextPlaneIndex = getLegalIndex(currentPlaneIndex + 1, noIdenticalPoints.size());
|
||||||
|
if (nextPlaneIndex == startPlaneIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final Plane testPlane = new Plane(currentPoint, noIdenticalPoints.get(nextPlaneIndex));
|
||||||
|
while (true) {
|
||||||
|
currentPlaneIndex = nextPlaneIndex;
|
||||||
|
if (currentPlaneIndex == startPlaneIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Check if the next point is off plane
|
||||||
|
nextPlaneIndex = getLegalIndex(currentPlaneIndex + 1, noIdenticalPoints.size());
|
||||||
|
final GeoPoint nextNonCoplanarPoint = noIdenticalPoints.get(nextPlaneIndex);
|
||||||
|
if (!testPlane.evaluateIsZero(nextNonCoplanarPoint)) {
|
||||||
|
// We will want to add the point at currentPlaneIndex to the list (last on of the series)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentPlaneIndex == startPlaneIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nonCoplanarPoints;
|
||||||
|
}
|
||||||
|
|
||||||
/** The maximum distance from the close point to the trial pole: 2 degrees */
|
/** The maximum distance from the close point to the trial pole: 2 degrees */
|
||||||
private final static double MAX_POLE_DISTANCE = Math.PI * 2.0 / 180.0;
|
private final static double MAX_POLE_DISTANCE = Math.PI * 2.0 / 180.0;
|
||||||
|
|
||||||
|
|
|
@ -328,6 +328,18 @@ public class Vector {
|
||||||
return magnitude(x,y,z);
|
return magnitude(x,y,z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute whether two vectors are numerically identical.
|
||||||
|
* @param other is the other vector.
|
||||||
|
* @return true if they are numerically identical.
|
||||||
|
*/
|
||||||
|
public boolean isNumericallyIdentical(final Vector other) {
|
||||||
|
final double thisX = y * other.z - z * other.y;
|
||||||
|
final double thisY = z * other.x - x * other.z;
|
||||||
|
final double thisZ = x * other.y - y * other.x;
|
||||||
|
return thisX * thisX + thisY * thisY + thisZ * thisZ < MINIMUM_RESOLUTION_SQUARED;
|
||||||
|
}
|
||||||
|
|
||||||
/** Compute the desired magnitude of a unit vector projected to a given
|
/** Compute the desired magnitude of a unit vector projected to a given
|
||||||
* planet model.
|
* planet model.
|
||||||
* @param planetModel is the planet model.
|
* @param planetModel is the planet model.
|
||||||
|
@ -336,7 +348,7 @@ public class Vector {
|
||||||
* @param z is the unit vector z value.
|
* @param z is the unit vector z value.
|
||||||
* @return a magnitude value for that (x,y,z) that projects the vector onto the specified ellipsoid.
|
* @return a magnitude value for that (x,y,z) that projects the vector onto the specified ellipsoid.
|
||||||
*/
|
*/
|
||||||
protected static double computeDesiredEllipsoidMagnitude(final PlanetModel planetModel, final double x, final double y, final double z) {
|
static double computeDesiredEllipsoidMagnitude(final PlanetModel planetModel, final double x, final double y, final double z) {
|
||||||
return 1.0 / Math.sqrt(x*x*planetModel.inverseAbSquared + y*y*planetModel.inverseAbSquared + z*z*planetModel.inverseCSquared);
|
return 1.0 / Math.sqrt(x*x*planetModel.inverseAbSquared + y*y*planetModel.inverseAbSquared + z*z*planetModel.inverseCSquared);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,7 +358,7 @@ public class Vector {
|
||||||
* @param z is the unit vector z value.
|
* @param z is the unit vector z value.
|
||||||
* @return a magnitude value for that z value that projects the vector onto the specified ellipsoid.
|
* @return a magnitude value for that z value that projects the vector onto the specified ellipsoid.
|
||||||
*/
|
*/
|
||||||
protected static double computeDesiredEllipsoidMagnitude(final PlanetModel planetModel, final double z) {
|
static double computeDesiredEllipsoidMagnitude(final PlanetModel planetModel, final double z) {
|
||||||
return 1.0 / Math.sqrt((1.0-z*z)*planetModel.inverseAbSquared + z*z*planetModel.inverseCSquared);
|
return 1.0 / Math.sqrt((1.0-z*z)*planetModel.inverseAbSquared + z*z*planetModel.inverseCSquared);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,70 @@ import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class GeoPolygonTest {
|
public class GeoPolygonTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPolygonPointFiltering() {
|
||||||
|
final GeoPoint point1 = new GeoPoint(PlanetModel.WGS84, 1.0, 2.0);
|
||||||
|
final GeoPoint point2 = new GeoPoint(PlanetModel.WGS84, 0.5, 2.5);
|
||||||
|
final GeoPoint point3 = new GeoPoint(PlanetModel.WGS84, 0.0, 0.0);
|
||||||
|
final GeoPoint point4 = new GeoPoint(PlanetModel.WGS84, Math.PI * 0.5, 0.0);
|
||||||
|
final GeoPoint point5 = new GeoPoint(PlanetModel.WGS84, 1.0, 0.0);
|
||||||
|
|
||||||
|
// First: duplicate points in the middle
|
||||||
|
{
|
||||||
|
final List<GeoPoint> originalPoints = new ArrayList<>();
|
||||||
|
originalPoints.add(point1);
|
||||||
|
originalPoints.add(point2);
|
||||||
|
originalPoints.add(point2);
|
||||||
|
originalPoints.add(point3);
|
||||||
|
final List<GeoPoint> filteredPoints =GeoPolygonFactory.filterPoints(originalPoints);
|
||||||
|
assertEquals(3, filteredPoints.size());
|
||||||
|
assertEquals(point1, filteredPoints.get(0));
|
||||||
|
assertEquals(point2, filteredPoints.get(1));
|
||||||
|
assertEquals(point3, filteredPoints.get(2));
|
||||||
|
}
|
||||||
|
// Next, duplicate points at the beginning
|
||||||
|
{
|
||||||
|
final List<GeoPoint> originalPoints = new ArrayList<>();
|
||||||
|
originalPoints.add(point2);
|
||||||
|
originalPoints.add(point1);
|
||||||
|
originalPoints.add(point3);
|
||||||
|
originalPoints.add(point2);
|
||||||
|
final List<GeoPoint> filteredPoints =GeoPolygonFactory.filterPoints(originalPoints);
|
||||||
|
assertEquals(3, filteredPoints.size());
|
||||||
|
assertEquals(point2, filteredPoints.get(0));
|
||||||
|
assertEquals(point1, filteredPoints.get(1));
|
||||||
|
assertEquals(point3, filteredPoints.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coplanar point removal
|
||||||
|
{
|
||||||
|
final List<GeoPoint> originalPoints = new ArrayList<>();
|
||||||
|
originalPoints.add(point1);
|
||||||
|
originalPoints.add(point3);
|
||||||
|
originalPoints.add(point4);
|
||||||
|
originalPoints.add(point5);
|
||||||
|
final List<GeoPoint> filteredPoints =GeoPolygonFactory.filterPoints(originalPoints);
|
||||||
|
assertEquals(3, filteredPoints.size());
|
||||||
|
assertEquals(point1, filteredPoints.get(0));
|
||||||
|
assertEquals(point3, filteredPoints.get(1));
|
||||||
|
assertEquals(point5, filteredPoints.get(2));
|
||||||
|
}
|
||||||
|
// Over the boundary
|
||||||
|
{
|
||||||
|
final List<GeoPoint> originalPoints = new ArrayList<>();
|
||||||
|
originalPoints.add(point5);
|
||||||
|
originalPoints.add(point1);
|
||||||
|
originalPoints.add(point3);
|
||||||
|
originalPoints.add(point4);
|
||||||
|
final List<GeoPoint> filteredPoints =GeoPolygonFactory.filterPoints(originalPoints);
|
||||||
|
assertEquals(3, filteredPoints.size());
|
||||||
|
assertEquals(point5, filteredPoints.get(0));
|
||||||
|
assertEquals(point1, filteredPoints.get(1));
|
||||||
|
assertEquals(point3, filteredPoints.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPolygonClockwise() {
|
public void testPolygonClockwise() {
|
||||||
GeoPolygon c;
|
GeoPolygon c;
|
||||||
|
|
Loading…
Reference in New Issue