LUCENE-6196: committing Karl's latest patch

https://reviews.apache.org/r/33353/ (diff #9) https://reviews.apache.org/r/33353/diff/raw/


git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/branches/lucene6196@1675374 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
David Wayne Smiley 2015-04-22 14:47:00 +00:00
parent bf74c72eb2
commit 1242bf1519
31 changed files with 1726 additions and 203 deletions

View File

@ -22,6 +22,9 @@ package org.apache.lucene.spatial.spatial4j.geo3d;
*/ */
public abstract class GeoBBoxBase implements GeoBBox { public abstract class GeoBBoxBase implements GeoBBox {
protected final static GeoPoint NORTH_POLE = new GeoPoint(0.0,0.0,1.0);
protected final static GeoPoint SOUTH_POLE = new GeoPoint(0.0,0.0,-1.0);
@Override @Override
public abstract boolean isWithin(final Vector point); public abstract boolean isWithin(final Vector point);

View File

@ -30,6 +30,7 @@ public class GeoBBoxFactory
*@return a GeoBBox corresponding to what was specified. *@return a GeoBBox corresponding to what was specified.
*/ */
public static GeoBBox makeGeoBBox(double topLat, double bottomLat, double leftLon, double rightLon) { public static GeoBBox makeGeoBBox(double topLat, double bottomLat, double leftLon, double rightLon) {
//System.err.println("Making rectangle for topLat="+topLat*180.0/Math.PI+", bottomLat="+bottomLat*180.0/Math.PI+", leftLon="+leftLon*180.0/Math.PI+", rightlon="+rightLon*180.0/Math.PI);
if (topLat > Math.PI * 0.5) if (topLat > Math.PI * 0.5)
topLat = Math.PI * 0.5; topLat = Math.PI * 0.5;
if (bottomLat < -Math.PI * 0.5) if (bottomLat < -Math.PI * 0.5)
@ -41,10 +42,18 @@ public class GeoBBoxFactory
if (leftLon == -Math.PI && rightLon == Math.PI) { if (leftLon == -Math.PI && rightLon == Math.PI) {
if (topLat == Math.PI * 0.5 && bottomLat == -Math.PI * 0.5) if (topLat == Math.PI * 0.5 && bottomLat == -Math.PI * 0.5)
return new GeoWorld(); return new GeoWorld();
if (topLat == bottomLat) if (topLat == bottomLat) {
if (topLat == Math.PI * 0.5 || topLat == -Math.PI * 0.5)
return new GeoDegeneratePoint(topLat,0.0);
return new GeoDegenerateLatitudeZone(topLat); return new GeoDegenerateLatitudeZone(topLat);
}
if (topLat == Math.PI * 0.5)
return new GeoNorthLatitudeZone(bottomLat);
else if (bottomLat == -Math.PI * 0.5)
return new GeoSouthLatitudeZone(topLat);
return new GeoLatitudeZone(topLat, bottomLat); return new GeoLatitudeZone(topLat, bottomLat);
} }
//System.err.println(" not latitude zone");
double extent = rightLon - leftLon; double extent = rightLon - leftLon;
if (extent < 0.0) if (extent < 0.0)
extent += Math.PI * 2.0; extent += Math.PI * 2.0;
@ -57,19 +66,36 @@ public class GeoBBoxFactory
return new GeoLongitudeSlice(leftLon, rightLon); return new GeoLongitudeSlice(leftLon, rightLon);
} }
//System.err.println(" not longitude slice");
if (leftLon == rightLon) { if (leftLon == rightLon) {
if (topLat == bottomLat) if (topLat == bottomLat)
return new GeoDegeneratePoint(topLat, leftLon); return new GeoDegeneratePoint(topLat, leftLon);
return new GeoDegenerateVerticalLine(topLat, bottomLat, leftLon); return new GeoDegenerateVerticalLine(topLat, bottomLat, leftLon);
} }
//System.err.println(" not vertical line");
if (extent >= Math.PI) { if (extent >= Math.PI) {
if (topLat == bottomLat) { if (topLat == bottomLat) {
//System.err.println(" wide degenerate line");
return new GeoWideDegenerateHorizontalLine(topLat, leftLon, rightLon); return new GeoWideDegenerateHorizontalLine(topLat, leftLon, rightLon);
} }
if (topLat == Math.PI * 0.5) {
return new GeoWideNorthRectangle(bottomLat, leftLon, rightLon);
} else if (bottomLat == -Math.PI * 0.5) {
return new GeoWideSouthRectangle(topLat, leftLon, rightLon);
}
//System.err.println(" wide rect");
return new GeoWideRectangle(topLat, bottomLat, leftLon, rightLon); return new GeoWideRectangle(topLat, bottomLat, leftLon, rightLon);
} }
if (topLat == bottomLat) if (topLat == bottomLat) {
//System.err.println(" horizontal line");
return new GeoDegenerateHorizontalLine(topLat, leftLon, rightLon); return new GeoDegenerateHorizontalLine(topLat, leftLon, rightLon);
}
if (topLat == Math.PI * 0.5) {
return new GeoNorthRectangle(bottomLat, leftLon, rightLon);
} else if (bottomLat == -Math.PI * 0.5) {
return new GeoSouthRectangle(topLat, leftLon, rightLon);
}
//System.err.println(" rectangle");
return new GeoRectangle(topLat, bottomLat, leftLon, rightLon); return new GeoRectangle(topLat, bottomLat, leftLon, rightLon);
} }

View File

@ -59,7 +59,7 @@ public abstract class GeoBaseExtendedShape implements GeoShape
*@return true if there's such an intersection, false if not. *@return true if there's such an intersection, false if not.
*/ */
@Override @Override
public abstract boolean intersects(final Plane plane, final Membership... bounds); public abstract boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds);
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
*@param bounds is the optional input bounds object. If this is null, *@param bounds is the optional input bounds object. If this is null,

View File

@ -27,6 +27,7 @@ public class GeoCircle extends GeoBaseExtendedShape implements GeoDistanceShape,
public final double cutoffLinearDistance; public final double cutoffLinearDistance;
public final SidedPlane circlePlane; public final SidedPlane circlePlane;
public final GeoPoint[] edgePoints; public final GeoPoint[] edgePoints;
public static final GeoPoint[] circlePoints = new GeoPoint[0];
public GeoCircle(final double lat, final double lon, final double cutoffAngle) public GeoCircle(final double lat, final double lon, final double cutoffAngle)
{ {
@ -35,7 +36,7 @@ public class GeoCircle extends GeoBaseExtendedShape implements GeoDistanceShape,
throw new IllegalArgumentException("Latitude out of bounds"); throw new IllegalArgumentException("Latitude out of bounds");
if (lon < -Math.PI || lon > Math.PI) if (lon < -Math.PI || lon > Math.PI)
throw new IllegalArgumentException("Longitude out of bounds"); throw new IllegalArgumentException("Longitude out of bounds");
if (cutoffAngle < 0.0 || cutoffAngle > Math.PI) if (cutoffAngle <= 0.0 || cutoffAngle > Math.PI)
throw new IllegalArgumentException("Cutoff angle out of bounds"); throw new IllegalArgumentException("Cutoff angle out of bounds");
final double sinAngle = Math.sin(cutoffAngle); final double sinAngle = Math.sin(cutoffAngle);
final double cosAngle = Math.cos(cutoffAngle); final double cosAngle = Math.cos(cutoffAngle);
@ -47,9 +48,25 @@ public class GeoCircle extends GeoBaseExtendedShape implements GeoDistanceShape,
this.cutoffAngle = cutoffAngle; this.cutoffAngle = cutoffAngle;
this.circlePlane = new SidedPlane(center, center, -cosAngle); this.circlePlane = new SidedPlane(center, center, -cosAngle);
// Compute a point on the circle boundary. This can be any point that is easy to compute. // Compute a point on the circle boundary.
// This requires some math, so I've implemented it in Plane. if (cutoffAngle == Math.PI)
this.edgePoints = new GeoPoint[]{center.getSamplePoint(sinAngle,cosAngle)}; this.edgePoints = new GeoPoint[0];
else {
// Move from center only in latitude. Then, if we go past the north pole, adjust the longitude also.
double newLat = lat + cutoffAngle;
double newLon = lon;
if (newLat > Math.PI * 0.5) {
newLat = Math.PI - newLat;
newLon += Math.PI;
}
while (newLon > Math.PI) {
newLon -= Math.PI * 2.0;
}
final GeoPoint edgePoint = new GeoPoint(newLat,newLon);
//if (Math.abs(circlePlane.evaluate(edgePoint)) > 1e-10)
// throw new RuntimeException("Computed an edge point that does not satisfy circlePlane equation! "+circlePlane.evaluate(edgePoint));
this.edgePoints = new GeoPoint[]{edgePoint};
}
} }
@Override @Override
@ -170,8 +187,6 @@ public class GeoCircle extends GeoBaseExtendedShape implements GeoDistanceShape,
@Override @Override
public boolean isWithin(final Vector point) public boolean isWithin(final Vector point)
{ {
if (point == null)
return false;
// Fastest way of determining membership // Fastest way of determining membership
return circlePlane.isWithin(point); return circlePlane.isWithin(point);
} }
@ -190,9 +205,9 @@ public class GeoCircle extends GeoBaseExtendedShape implements GeoDistanceShape,
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
return circlePlane.intersects(p, bounds); return circlePlane.intersects(p, notablePoints, circlePoints, bounds);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.

View File

@ -63,10 +63,10 @@ public class GeoCompositeMembershipShape implements GeoMembershipShape
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
for (GeoMembershipShape shape : shapes) { for (GeoMembershipShape shape : shapes) {
if (shape.intersects(p,bounds)) if (shape.intersects(p,notablePoints,bounds))
return true; return true;
} }
return false; return false;

View File

@ -33,6 +33,7 @@ public class GeoConvexPolygon extends GeoBaseExtendedShape implements GeoMembers
protected SidedPlane[] edges = null; protected SidedPlane[] edges = null;
protected boolean[] internalEdges = null; protected boolean[] internalEdges = null;
protected GeoPoint[][] notableEdgePoints = null;
protected GeoPoint[] edgePoints = null; protected GeoPoint[] edgePoints = null;
@ -98,6 +99,7 @@ public class GeoConvexPolygon extends GeoBaseExtendedShape implements GeoMembers
throw new IllegalArgumentException("Polygon needs at least three points."); throw new IllegalArgumentException("Polygon needs at least three points.");
// Time to construct the planes. If the polygon is truly convex, then any adjacent point // Time to construct the planes. If the polygon is truly convex, then any adjacent point
edges = new SidedPlane[points.size()]; edges = new SidedPlane[points.size()];
notableEdgePoints = new GeoPoint[points.size()][];
internalEdges = new boolean[points.size()]; internalEdges = new boolean[points.size()];
// to a segment can provide an interior measurement. // to a segment can provide an interior measurement.
for (int i = 0; i < points.size(); i++) { for (int i = 0; i < points.size(); i++) {
@ -108,6 +110,7 @@ public class GeoConvexPolygon extends GeoBaseExtendedShape implements GeoMembers
final SidedPlane sp = new SidedPlane(check,start,end); final SidedPlane sp = new SidedPlane(check,start,end);
//System.out.println("Created edge "+sp+" using start="+start+" end="+end+" check="+check); //System.out.println("Created edge "+sp+" using start="+start+" end="+end+" check="+check);
edges[i] = sp; edges[i] = sp;
notableEdgePoints[i] = new GeoPoint[]{start,end};
internalEdges[i] = isInternalEdge; internalEdges[i] = isInternalEdge;
} }
createCenterPoint(); createCenterPoint();
@ -163,11 +166,14 @@ public class GeoConvexPolygon extends GeoBaseExtendedShape implements GeoMembers
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
//System.err.println("Checking for polygon intersection with plane "+p+"...");
for (int edgeIndex = 0; edgeIndex < edges.length; edgeIndex++) { for (int edgeIndex = 0; edgeIndex < edges.length; edgeIndex++) {
final SidedPlane edge = edges[edgeIndex]; final SidedPlane edge = edges[edgeIndex];
final GeoPoint[] points = this.notableEdgePoints[edgeIndex];
if (!internalEdges[edgeIndex]) { if (!internalEdges[edgeIndex]) {
//System.err.println(" non-internal edge "+edge);
// Edges flagged as 'internal only' are excluded from the matching // Edges flagged as 'internal only' are excluded from the matching
// Construct boundaries // Construct boundaries
final Membership[] membershipBounds = new Membership[edges.length-1]; final Membership[] membershipBounds = new Membership[edges.length-1];
@ -177,10 +183,13 @@ public class GeoConvexPolygon extends GeoBaseExtendedShape implements GeoMembers
membershipBounds[count++] = edges[otherIndex]; membershipBounds[count++] = edges[otherIndex];
} }
} }
if (edge.intersects(p,bounds,membershipBounds)) if (edge.intersects(p,notablePoints, points, bounds,membershipBounds)) {
//System.err.println(" intersects!");
return true; return true;
}
} }
} }
//System.err.println(" no intersection");
return false; return false;
} }

View File

@ -33,7 +33,9 @@ public class GeoDegenerateHorizontalLine extends GeoBBoxBase
public final Plane plane; public final Plane plane;
public final SidedPlane leftPlane; public final SidedPlane leftPlane;
public final SidedPlane rightPlane; public final SidedPlane rightPlane;
public final GeoPoint[] planePoints;
public final GeoPoint centerPoint; public final GeoPoint centerPoint;
public final GeoPoint[] edgePoints; public final GeoPoint[] edgePoints;
@ -83,6 +85,8 @@ public class GeoDegenerateHorizontalLine extends GeoBBoxBase
this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon); this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon); this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon);
this.planePoints = new GeoPoint[]{LHC,RHC};
this.edgePoints = new GeoPoint[]{centerPoint}; this.edgePoints = new GeoPoint[]{centerPoint};
} }
@ -107,7 +111,7 @@ public class GeoDegenerateHorizontalLine extends GeoBBoxBase
@Override @Override
public boolean isWithin(final Vector point) public boolean isWithin(final Vector point)
{ {
return plane.evaluate(point) == 0.0 && return plane.evaluateIsZero(point) &&
leftPlane.isWithin(point) && leftPlane.isWithin(point) &&
rightPlane.isWithin(point); rightPlane.isWithin(point);
} }
@ -115,7 +119,7 @@ public class GeoDegenerateHorizontalLine extends GeoBBoxBase
@Override @Override
public boolean isWithin(final double x, final double y, final double z) public boolean isWithin(final double x, final double y, final double z)
{ {
return plane.evaluate(x,y,z) == 0.0 && return plane.evaluateIsZero(x,y,z) &&
leftPlane.isWithin(x,y,z) && leftPlane.isWithin(x,y,z) &&
rightPlane.isWithin(x,y,z); rightPlane.isWithin(x,y,z);
} }
@ -135,9 +139,9 @@ public class GeoDegenerateHorizontalLine extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
return p.intersects(plane,bounds,leftPlane,rightPlane); return p.intersects(plane,notablePoints,planePoints,bounds,leftPlane,rightPlane);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
@ -158,7 +162,7 @@ public class GeoDegenerateHorizontalLine extends GeoBBoxBase
@Override @Override
public int getRelationship(final GeoShape path) { public int getRelationship(final GeoShape path) {
if (path.intersects(plane,leftPlane,rightPlane)) if (path.intersects(plane,planePoints,leftPlane,rightPlane))
return OVERLAPS; return OVERLAPS;
if (path.isWithin(centerPoint)) if (path.isWithin(centerPoint))

View File

@ -28,6 +28,7 @@ public class GeoDegenerateLatitudeZone extends GeoBBoxBase
public final Plane plane; public final Plane plane;
public final GeoPoint interiorPoint; public final GeoPoint interiorPoint;
public final GeoPoint[] edgePoints; public final GeoPoint[] edgePoints;
public final static GeoPoint[] planePoints = new GeoPoint[0];
public GeoDegenerateLatitudeZone(final double latitude) public GeoDegenerateLatitudeZone(final double latitude)
{ {
@ -36,7 +37,6 @@ public class GeoDegenerateLatitudeZone extends GeoBBoxBase
this.sinLatitude = Math.sin(latitude); this.sinLatitude = Math.sin(latitude);
double cosLatitude = Math.cos(latitude); double cosLatitude = Math.cos(latitude);
this.plane = new Plane(sinLatitude); this.plane = new Plane(sinLatitude);
// Compute an interior point. // Compute an interior point.
interiorPoint = new GeoPoint(cosLatitude,0.0,sinLatitude); interiorPoint = new GeoPoint(cosLatitude,0.0,sinLatitude);
edgePoints = new GeoPoint[]{interiorPoint}; edgePoints = new GeoPoint[]{interiorPoint};
@ -53,13 +53,13 @@ public class GeoDegenerateLatitudeZone extends GeoBBoxBase
@Override @Override
public boolean isWithin(final Vector point) public boolean isWithin(final Vector point)
{ {
return point.z == this.sinLatitude; return Math.abs(point.z - this.sinLatitude) < 1e-10;
} }
@Override @Override
public boolean isWithin(final double x, final double y, final double z) public boolean isWithin(final double x, final double y, final double z)
{ {
return z == this.sinLatitude; return Math.abs(z - this.sinLatitude) < 1e-10;
} }
@Override @Override
@ -75,9 +75,9 @@ public class GeoDegenerateLatitudeZone extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
return p.intersects(plane,bounds); return p.intersects(plane,notablePoints,planePoints,bounds);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
@ -102,7 +102,7 @@ public class GeoDegenerateLatitudeZone extends GeoBBoxBase
// work with no area endpoints. So we rely entirely on intersections. // work with no area endpoints. So we rely entirely on intersections.
//System.out.println("Got here! latitude="+latitude+" path="+path); //System.out.println("Got here! latitude="+latitude+" path="+path);
if (path.intersects(plane)) { if (path.intersects(plane,planePoints)) {
return OVERLAPS; return OVERLAPS;
} }

View File

@ -30,6 +30,8 @@ public class GeoDegenerateLongitudeSlice extends GeoBBoxBase
public final GeoPoint interiorPoint; public final GeoPoint interiorPoint;
public final GeoPoint[] edgePoints; public final GeoPoint[] edgePoints;
public final static GeoPoint[] planePoints = new GeoPoint[]{NORTH_POLE,SOUTH_POLE};
/** Accepts only values in the following ranges: lon: {@code -PI -> PI} */ /** Accepts only values in the following ranges: lon: {@code -PI -> PI} */
public GeoDegenerateLongitudeSlice(final double longitude) public GeoDegenerateLongitudeSlice(final double longitude)
{ {
@ -65,14 +67,14 @@ public class GeoDegenerateLongitudeSlice extends GeoBBoxBase
@Override @Override
public boolean isWithin(final Vector point) public boolean isWithin(final Vector point)
{ {
return plane.evaluate(point) == 0.0 && return plane.evaluateIsZero(point) &&
boundingPlane.isWithin(point); boundingPlane.isWithin(point);
} }
@Override @Override
public boolean isWithin(final double x, final double y, final double z) public boolean isWithin(final double x, final double y, final double z)
{ {
return plane.evaluate(x,y,z) == 0.0 && return plane.evaluateIsZero(x,y,z) &&
boundingPlane.isWithin(x,y,z); boundingPlane.isWithin(x,y,z);
} }
@ -89,9 +91,9 @@ public class GeoDegenerateLongitudeSlice extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
return p.intersects(plane,bounds,boundingPlane); return p.intersects(plane,notablePoints,planePoints,bounds,boundingPlane);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
@ -114,7 +116,7 @@ public class GeoDegenerateLongitudeSlice extends GeoBBoxBase
@Override @Override
public int getRelationship(final GeoShape path) { public int getRelationship(final GeoShape path) {
// Look for intersections. // Look for intersections.
if (path.intersects(plane,boundingPlane)) if (path.intersects(plane,planePoints,boundingPlane))
return OVERLAPS; return OVERLAPS;
if (path.isWithin(interiorPoint)) if (path.isWithin(interiorPoint))

View File

@ -64,7 +64,7 @@ public class GeoDegeneratePoint extends GeoPoint implements GeoBBox
*@return true if there's such an intersection, false if not. *@return true if there's such an intersection, false if not.
*/ */
@Override @Override
public boolean intersects(final Plane plane, final Membership... bounds) { public boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds) {
if (plane.evaluate(this) == 0.0) if (plane.evaluate(this) == 0.0)
return false; return false;

View File

@ -32,7 +32,9 @@ public class GeoDegenerateVerticalLine extends GeoBBoxBase
public final SidedPlane bottomPlane; public final SidedPlane bottomPlane;
public final SidedPlane boundingPlane; public final SidedPlane boundingPlane;
public final Plane plane; public final Plane plane;
public final GeoPoint[] planePoints;
public final GeoPoint centerPoint; public final GeoPoint centerPoint;
public final GeoPoint[] edgePoints; public final GeoPoint[] edgePoints;
@ -77,6 +79,8 @@ public class GeoDegenerateVerticalLine extends GeoBBoxBase
this.boundingPlane = new SidedPlane(centerPoint,-sinLongitude,cosLongitude); this.boundingPlane = new SidedPlane(centerPoint,-sinLongitude,cosLongitude);
this.planePoints = new GeoPoint[]{UHC,LHC};
this.edgePoints = new GeoPoint[]{centerPoint}; this.edgePoints = new GeoPoint[]{centerPoint};
} }
@ -98,7 +102,7 @@ public class GeoDegenerateVerticalLine extends GeoBBoxBase
@Override @Override
public boolean isWithin(final Vector point) public boolean isWithin(final Vector point)
{ {
return plane.evaluate(point) == 0.0 && return plane.evaluateIsZero(point) &&
boundingPlane.isWithin(point) && boundingPlane.isWithin(point) &&
topPlane.isWithin(point) && topPlane.isWithin(point) &&
bottomPlane.isWithin(point); bottomPlane.isWithin(point);
@ -107,7 +111,7 @@ public class GeoDegenerateVerticalLine extends GeoBBoxBase
@Override @Override
public boolean isWithin(final double x, final double y, final double z) public boolean isWithin(final double x, final double y, final double z)
{ {
return plane.evaluate(x,y,z) == 0.0 && return plane.evaluateIsZero(x,y,z) &&
boundingPlane.isWithin(x,y,z) && boundingPlane.isWithin(x,y,z) &&
topPlane.isWithin(x,y,z) && topPlane.isWithin(x,y,z) &&
bottomPlane.isWithin(x,y,z); bottomPlane.isWithin(x,y,z);
@ -131,9 +135,9 @@ public class GeoDegenerateVerticalLine extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
return p.intersects(plane,bounds,boundingPlane,topPlane,bottomPlane); return p.intersects(plane,notablePoints,planePoints,bounds,boundingPlane,topPlane,bottomPlane);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
@ -155,12 +159,18 @@ public class GeoDegenerateVerticalLine extends GeoBBoxBase
@Override @Override
public int getRelationship(final GeoShape path) { public int getRelationship(final GeoShape path) {
if (path.intersects(plane,boundingPlane,topPlane,bottomPlane)) //System.err.println(this+" relationship to "+path);
if (path.intersects(plane,planePoints,boundingPlane,topPlane,bottomPlane)) {
//System.err.println(" overlaps");
return OVERLAPS; return OVERLAPS;
}
if (path.isWithin(centerPoint)) if (path.isWithin(centerPoint)) {
//System.err.println(" contains");
return CONTAINS; return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT; return DISJOINT;
} }

View File

@ -28,6 +28,7 @@ public class GeoLatitudeZone extends GeoBBoxBase
public final SidedPlane topPlane; public final SidedPlane topPlane;
public final SidedPlane bottomPlane; public final SidedPlane bottomPlane;
public final GeoPoint interiorPoint; public final GeoPoint interiorPoint;
public final static GeoPoint[] planePoints = new GeoPoint[0];
// We need two additional points because a latitude zone's boundaries don't intersect. This is a very // We need two additional points because a latitude zone's boundaries don't intersect. This is a very
// special case that most GeoBBox's do not have. // special case that most GeoBBox's do not have.
@ -106,10 +107,10 @@ public class GeoLatitudeZone extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
return p.intersects(topPlane,bounds,bottomPlane) || return p.intersects(topPlane,notablePoints,planePoints,bounds,bottomPlane) ||
p.intersects(bottomPlane,bounds,topPlane); p.intersects(bottomPlane,notablePoints,planePoints,bounds,topPlane);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
@ -149,8 +150,8 @@ public class GeoLatitudeZone extends GeoBBoxBase
// Second, the shortcut of seeing whether endpoints are in/out is not going to // Second, the shortcut of seeing whether endpoints are in/out is not going to
// work with no area endpoints. So we rely entirely on intersections. // work with no area endpoints. So we rely entirely on intersections.
if (path.intersects(topPlane,bottomPlane) || if (path.intersects(topPlane,planePoints,bottomPlane) ||
path.intersects(bottomPlane,topPlane)) path.intersects(bottomPlane,planePoints,topPlane))
return OVERLAPS; return OVERLAPS;
// There is another case for latitude zones only. This is when the boundaries of the shape all fit // There is another case for latitude zones only. This is when the boundaries of the shape all fit

View File

@ -29,9 +29,11 @@ public class GeoLongitudeSlice extends GeoBBoxBase
public final SidedPlane leftPlane; public final SidedPlane leftPlane;
public final SidedPlane rightPlane; public final SidedPlane rightPlane;
public final static GeoPoint[] planePoints = new GeoPoint[]{NORTH_POLE,SOUTH_POLE};
public final GeoPoint centerPoint; public final GeoPoint centerPoint;
public final GeoPoint northPole = new GeoPoint(0.0,0.0,1.0);
public final GeoPoint[] edgePoints = new GeoPoint[]{northPole}; public final static GeoPoint[] edgePoints = new GeoPoint[]{NORTH_POLE};
/** Accepts only values in the following ranges: lon: {@code -PI -> PI} */ /** Accepts only values in the following ranges: lon: {@code -PI -> PI} */
public GeoLongitudeSlice(final double leftLon, double rightLon) public GeoLongitudeSlice(final double leftLon, double rightLon)
@ -115,10 +117,10 @@ public class GeoLongitudeSlice extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
return p.intersects(leftPlane,bounds,rightPlane) || return p.intersects(leftPlane,notablePoints,planePoints,bounds,rightPlane) ||
p.intersects(rightPlane,bounds,leftPlane); p.intersects(rightPlane,notablePoints,planePoints,bounds,leftPlane);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
@ -144,13 +146,13 @@ public class GeoLongitudeSlice extends GeoBBoxBase
if (insideRectangle == SOME_INSIDE) if (insideRectangle == SOME_INSIDE)
return OVERLAPS; return OVERLAPS;
final boolean insideShape = path.isWithin(northPole); final boolean insideShape = path.isWithin(NORTH_POLE);
if (insideRectangle == ALL_INSIDE && insideShape) if (insideRectangle == ALL_INSIDE && insideShape)
return OVERLAPS; return OVERLAPS;
if (path.intersects(leftPlane,rightPlane) || if (path.intersects(leftPlane,planePoints,rightPlane) ||
path.intersects(rightPlane,leftPlane)) { path.intersects(rightPlane,planePoints,leftPlane)) {
return OVERLAPS; return OVERLAPS;
} }

View File

@ -0,0 +1,171 @@
package org.apache.lucene.spatial.spatial4j.geo3d;
/*
* 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.
*/
/** This GeoBBox represents an area rectangle limited only in south latitude.
*/
public class GeoNorthLatitudeZone extends GeoBBoxBase
{
public final double bottomLat;
public final double cosBottomLat;
public final SidedPlane bottomPlane;
public final GeoPoint interiorPoint;
public final static GeoPoint[] planePoints = new GeoPoint[0];
public final GeoPoint bottomBoundaryPoint;
// Edge points
public final GeoPoint[] edgePoints;
public GeoNorthLatitudeZone(final double bottomLat)
{
this.bottomLat = bottomLat;
final double sinBottomLat = Math.sin(bottomLat);
this.cosBottomLat = Math.cos(bottomLat);
// Construct sample points, so we get our sidedness right
final Vector bottomPoint = new Vector(0.0,0.0,sinBottomLat);
// Compute an interior point. Pick one whose lat is between top and bottom.
final double middleLat = (Math.PI * 0.5 + bottomLat) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
this.interiorPoint = new GeoPoint(Math.sqrt(1.0 - sinMiddleLat * sinMiddleLat),0.0,sinMiddleLat);
this.bottomBoundaryPoint = new GeoPoint(Math.sqrt(1.0 - sinBottomLat * sinBottomLat),0.0,sinBottomLat);
this.bottomPlane = new SidedPlane(interiorPoint,sinBottomLat);
this.edgePoints = new GeoPoint[]{bottomBoundaryPoint};
}
@Override
public GeoBBox expand(final double angle)
{
final double newTopLat = Math.PI * 0.5;
final double newBottomLat = bottomLat - angle;
return GeoBBoxFactory.makeGeoBBox(newTopLat, newBottomLat, -Math.PI, Math.PI);
}
@Override
public boolean isWithin(final Vector point)
{
return
bottomPlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z)
{
return
bottomPlane.isWithin(x,y,z);
}
@Override
public double getRadius()
{
// This is a bit tricky. I guess we should interpret this as meaning the angle of a circle that
// would contain all the bounding box points, when starting in the "center".
if (bottomLat < 0.0)
return Math.PI;
double maxCosLat = cosBottomLat;
return maxCosLat * Math.PI;
}
@Override
public GeoPoint[] getEdgePoints()
{
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{
return
p.intersects(bottomPlane,notablePoints,planePoints,bounds);
}
/** Compute longitude/latitude bounds for the shape.
*@param bounds is the optional input bounds object. If this is null,
* a bounds object will be created. Otherwise, the input object will be modified.
*@return a Bounds object describing the shape's bounds. If the bounds cannot
* be computed, then return a Bounds object with noLongitudeBound,
* noTopLatitudeBound, and noBottomLatitudeBound.
*/
@Override
public Bounds getBounds(Bounds bounds)
{
if (bounds == null)
bounds = new Bounds();
bounds.noLongitudeBound().noTopLatitudeBound().addLatitudeZone(bottomLat);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
final int insideRectangle = isShapeInsideBBox(path);
if (insideRectangle == SOME_INSIDE)
return OVERLAPS;
final boolean insideShape = path.isWithin(bottomBoundaryPoint);
if (insideRectangle == ALL_INSIDE && insideShape)
return OVERLAPS;
// Second, the shortcut of seeing whether endpoints are in/out is not going to
// work with no area endpoints. So we rely entirely on intersections.
if (
path.intersects(bottomPlane,planePoints))
return OVERLAPS;
// There is another case for latitude zones only. This is when the boundaries of the shape all fit
// within the zone, but the shape includes areas outside the zone crossing a pole.
// In this case, the above "overlaps" check is insufficient. We also need to check a point on either boundary
// whether it is within the shape. If both such points are within, then CONTAINS is the right answer. If
// one such point is within, then OVERLAPS is the right answer.
if (insideShape)
return CONTAINS;
if (insideRectangle == ALL_INSIDE)
return WITHIN;
return DISJOINT;
}
@Override
public boolean equals(Object o)
{
if (!(o instanceof GeoNorthLatitudeZone))
return false;
GeoNorthLatitudeZone other = (GeoNorthLatitudeZone)o;
return other.bottomPlane.equals(bottomPlane);
}
@Override
public int hashCode() {
int result = bottomPlane.hashCode();
return result;
}
@Override
public String toString() {
return "GeoNorthLatitudeZone: {bottomlat="+bottomLat+"("+bottomLat*180.0/Math.PI+")}";
}
}

View File

@ -0,0 +1,243 @@
package org.apache.lucene.spatial.spatial4j.geo3d;
/*
* 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.
*/
/** Bounding box limited on three sides (bottom lat, left lon, right lon), including
* the north pole.
* The left-right maximum extent for this shape is PI; for anything larger, use
* GeoWideNorthRectangle.
*/
public class GeoNorthRectangle extends GeoBBoxBase
{
public final double bottomLat;
public final double leftLon;
public final double rightLon;
public final double cosMiddleLat;
public final GeoPoint LRHC;
public final GeoPoint LLHC;
public final SidedPlane bottomPlane;
public final SidedPlane leftPlane;
public final SidedPlane rightPlane;
public final GeoPoint[] bottomPlanePoints;
public final GeoPoint[] leftPlanePoints;
public final GeoPoint[] rightPlanePoints;
public final GeoPoint centerPoint;
public final GeoPoint[] edgePoints = new GeoPoint[]{NORTH_POLE};
/** Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI} */
public GeoNorthRectangle(final double bottomLat, final double leftLon, double rightLon)
{
// Argument checking
if (bottomLat > Math.PI * 0.5 || bottomLat < -Math.PI * 0.5)
throw new IllegalArgumentException("Bottom latitude out of range");
if (leftLon < -Math.PI || leftLon > Math.PI)
throw new IllegalArgumentException("Left longitude out of range");
if (rightLon < -Math.PI || rightLon > Math.PI)
throw new IllegalArgumentException("Right longitude out of range");
double extent = rightLon - leftLon;
if (extent < 0.0) {
extent += 2.0 * Math.PI;
}
if (extent > Math.PI)
throw new IllegalArgumentException("Width of rectangle too great");
this.bottomLat = bottomLat;
this.leftLon = leftLon;
this.rightLon = rightLon;
final double sinBottomLat = Math.sin(bottomLat);
final double cosBottomLat = Math.cos(bottomLat);
final double sinLeftLon = Math.sin(leftLon);
final double cosLeftLon = Math.cos(leftLon);
final double sinRightLon = Math.sin(rightLon);
final double cosRightLon = Math.cos(rightLon);
// Now build the points
this.LRHC = new GeoPoint(sinBottomLat,sinRightLon,cosBottomLat,cosRightLon);
this.LLHC = new GeoPoint(sinBottomLat,sinLeftLon,cosBottomLat,cosLeftLon);
final double middleLat = (Math.PI * 0.5 + bottomLat) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
this.cosMiddleLat = Math.cos(middleLat);
// Normalize
while (leftLon > rightLon) {
rightLon += Math.PI * 2.0;
}
final double middleLon = (leftLon + rightLon) * 0.5;
final double sinMiddleLon = Math.sin(middleLon);
final double cosMiddleLon = Math.cos(middleLon);
this.centerPoint = new GeoPoint(sinMiddleLat,sinMiddleLon,cosMiddleLat,cosMiddleLon);
this.bottomPlane = new SidedPlane(centerPoint,sinBottomLat);
this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon);
this.bottomPlanePoints = new GeoPoint[]{LLHC,LRHC};
this.leftPlanePoints = new GeoPoint[]{NORTH_POLE,LLHC};
this.rightPlanePoints = new GeoPoint[]{NORTH_POLE,LRHC};
}
@Override
public GeoBBox expand(final double angle)
{
final double newTopLat = Math.PI * 0.5;
final double newBottomLat = bottomLat - angle;
// Figuring out when we escalate to a special case requires some prefiguring
double currentLonSpan = rightLon - leftLon;
if (currentLonSpan < 0.0)
currentLonSpan += Math.PI * 2.0;
double newLeftLon = leftLon - angle;
double newRightLon = rightLon + angle;
if (currentLonSpan + 2.0 * angle >= Math.PI * 2.0) {
newLeftLon = -Math.PI;
newRightLon = Math.PI;
}
return GeoBBoxFactory.makeGeoBBox(newTopLat,newBottomLat,newLeftLon,newRightLon);
}
@Override
public boolean isWithin(final Vector point)
{
return
bottomPlane.isWithin(point) &&
leftPlane.isWithin(point) &&
rightPlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z)
{
return
bottomPlane.isWithin(x,y,z) &&
leftPlane.isWithin(x,y,z) &&
rightPlane.isWithin(x,y,z);
}
@Override
public double getRadius()
{
// Here we compute the distance from the middle point to one of the corners. However, we need to be careful
// to use the longest of three distances: the distance to a corner on the top; the distnace to a corner on the bottom, and
// the distance to the right or left edge from the center.
final double centerAngle = (rightLon - (rightLon + leftLon) * 0.5) * cosMiddleLat;
final double bottomAngle = centerPoint.arcDistance(LLHC);
return Math.max(centerAngle,bottomAngle);
}
@Override
public GeoPoint[] getEdgePoints()
{
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{
return
p.intersects(bottomPlane,notablePoints,bottomPlanePoints,bounds,leftPlane,rightPlane) ||
p.intersects(leftPlane,notablePoints,leftPlanePoints,bounds,rightPlane,bottomPlane) ||
p.intersects(rightPlane,notablePoints,rightPlanePoints,bounds,leftPlane,bottomPlane);
}
/** Compute longitude/latitude bounds for the shape.
*@param bounds is the optional input bounds object. If this is null,
* a bounds object will be created. Otherwise, the input object will be modified.
*@return a Bounds object describing the shape's bounds. If the bounds cannot
* be computed, then return a Bounds object with noLongitudeBound,
* noTopLatitudeBound, and noBottomLatitudeBound.
*/
@Override
public Bounds getBounds(Bounds bounds)
{
if (bounds == null)
bounds = new Bounds();
bounds.noTopLatitudeBound().addLatitudeZone(bottomLat)
.addLongitudeSlice(leftLon,rightLon);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideBBox(path);
if (insideRectangle == SOME_INSIDE)
{
//System.err.println(" some inside");
return OVERLAPS;
}
final boolean insideShape = path.isWithin(NORTH_POLE);
if (insideRectangle == ALL_INSIDE && insideShape) {
//System.err.println(" inside of each other");
return OVERLAPS;
}
if (
path.intersects(bottomPlane,bottomPlanePoints,leftPlane,rightPlane) ||
path.intersects(leftPlane,leftPlanePoints,bottomPlane,rightPlane) ||
path.intersects(rightPlane,rightPlanePoints,leftPlane,bottomPlane)) {
//System.err.println(" edges intersect");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE)
{
//System.err.println(" shape inside rectangle");
return WITHIN;
}
if (insideShape) {
//System.err.println(" shape contains rectangle");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o)
{
if (!(o instanceof GeoNorthRectangle))
return false;
GeoNorthRectangle other = (GeoNorthRectangle)o;
return other.LLHC.equals(LLHC) && other.LRHC.equals(LRHC);
}
@Override
public int hashCode() {
int result = LLHC.hashCode();
result = 31 * result + LRHC.hashCode();
return result;
}
@Override
public String toString() {
return "GeoNorthRectangle: {bottomlat="+bottomLat+"("+bottomLat*180.0/Math.PI+"), leftlon="+leftLon+"("+leftLon*180.0/Math.PI+"), rightlon="+rightLon+"("+rightLon*180.0/Math.PI+")}";
}
}

View File

@ -67,6 +67,20 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
if (ps.isDegenerate()) if (ps.isDegenerate())
return; return;
segments.add(ps); segments.add(ps);
} else {
// First point. We compute the basic set of edgepoints here because we've got the lat and lon available.
// Move from center only in latitude. Then, if we go past the north pole, adjust the longitude also.
double newLat = lat + cutoffAngle;
double newLon = lon;
if (newLat > Math.PI * 0.5) {
newLat = Math.PI - newLat;
newLon += Math.PI;
}
while (newLon > Math.PI) {
newLon -= Math.PI * 2.0;
}
final GeoPoint edgePoint = new GeoPoint(newLat,newLon);
this.edgePoints = new GeoPoint[]{edgePoint};
} }
final SegmentEndpoint se = new SegmentEndpoint(end, originDistance, cutoffOffset, cutoffAngle, chordDistance); final SegmentEndpoint se = new SegmentEndpoint(end, originDistance, cutoffOffset, cutoffAngle, chordDistance);
points.add(se); points.add(se);
@ -77,8 +91,24 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
throw new IllegalArgumentException("Path must have at least one point"); throw new IllegalArgumentException("Path must have at least one point");
if (segments.size() > 0) { if (segments.size() > 0) {
edgePoints = new GeoPoint[]{points.get(0).circlePlane.getSampleIntersectionPoint(segments.get(0).invertedStartCutoffPlane)}; edgePoints = new GeoPoint[]{points.get(0).circlePlane.getSampleIntersectionPoint(segments.get(0).invertedStartCutoffPlane)};
} else { }
edgePoints = new GeoPoint[]{points.get(0).point.getSamplePoint(cutoffOffset,originDistance)}; for (int i = 0; i < points.size(); i++) {
final SegmentEndpoint pathPoint = points.get(i);
Membership previousEndBound = null;
GeoPoint[] previousEndNotablePoints = null;
Membership nextStartBound = null;
GeoPoint[] nextStartNotablePoints = null;
if (i > 0) {
final PathSegment previousSegment = segments.get(i-1);
previousEndBound = previousSegment.invertedEndCutoffPlane;
previousEndNotablePoints = previousSegment.endCutoffPlanePoints;
}
if (i < segments.size()) {
final PathSegment nextSegment = segments.get(i);
nextStartBound = nextSegment.invertedStartCutoffPlane;
nextStartNotablePoints = nextSegment.startCutoffPlanePoints;
}
pathPoint.setCutoffPlanes(previousEndNotablePoints,previousEndBound,nextStartNotablePoints,nextStartBound);
} }
} }
@ -267,7 +297,7 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
} }
@Override @Override
public boolean intersects(final Plane plane, final Membership... bounds) public boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
// We look for an intersection with any of the exterior edges of the path. // We look for an intersection with any of the exterior edges of the path.
// We also have to look for intersections with the cones described by the endpoints. // We also have to look for intersections with the cones described by the endpoints.
@ -279,21 +309,14 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
// Well, sort of. We can detect intersections also due to overlap of segments with each other. // Well, sort of. We can detect intersections also due to overlap of segments with each other.
// But that's an edge case and we won't be optimizing for it. // But that's an edge case and we won't be optimizing for it.
for (int i = 0; i < points.size(); i++) { for (final SegmentEndpoint pathPoint : points) {
final SegmentEndpoint pathPoint = points.get(i); if (pathPoint.intersects(plane, notablePoints, bounds)) {
Membership previousEndBound = null;
Membership nextStartBound = null;
if (i > 0)
previousEndBound = segments.get(i-1).invertedEndCutoffPlane;
if (i < segments.size())
nextStartBound = segments.get(i).invertedStartCutoffPlane;
if (pathPoint.intersects(plane, bounds, previousEndBound, nextStartBound)) {
return true; return true;
} }
} }
for (PathSegment pathSegment : segments) { for (final PathSegment pathSegment : segments) {
if (pathSegment.intersects(plane, bounds)) { if (pathSegment.intersects(plane, notablePoints, bounds)) {
return true; return true;
} }
} }
@ -363,7 +386,11 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
public final double cutoffNormalDistance; public final double cutoffNormalDistance;
public final double cutoffAngle; public final double cutoffAngle;
public final double chordDistance; public final double chordDistance;
public Membership[] cutoffPlanes = null;
public GeoPoint[] notablePoints = null;
public final static GeoPoint[] circlePoints = new GeoPoint[0];
public SegmentEndpoint(final GeoPoint point, final double originDistance, final double cutoffOffset, final double cutoffAngle, final double chordDistance) public SegmentEndpoint(final GeoPoint point, final double originDistance, final double cutoffOffset, final double cutoffAngle, final double chordDistance)
{ {
this.point = point; this.point = point;
@ -373,6 +400,30 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
this.circlePlane = new SidedPlane(point, point, -originDistance); this.circlePlane = new SidedPlane(point, point, -originDistance);
} }
public void setCutoffPlanes(final GeoPoint[] previousEndNotablePoints, final Membership previousEndPlane,
final GeoPoint[] nextStartNotablePoints, final Membership nextStartPlane) {
if (previousEndNotablePoints == null && nextStartNotablePoints == null) {
cutoffPlanes = new Membership[0];
notablePoints = new GeoPoint[0];
} else if (previousEndNotablePoints != null && nextStartNotablePoints == null) {
cutoffPlanes = new Membership[]{previousEndPlane};
notablePoints = previousEndNotablePoints;
} else if (previousEndNotablePoints == null && nextStartNotablePoints != null) {
cutoffPlanes = new Membership[]{nextStartPlane};
notablePoints = nextStartNotablePoints;
} else {
cutoffPlanes = new Membership[]{previousEndPlane,nextStartPlane};
notablePoints = new GeoPoint[previousEndNotablePoints.length + nextStartNotablePoints.length];
int i = 0;
for (GeoPoint p : previousEndNotablePoints) {
notablePoints[i++] = p;
}
for (GeoPoint p : nextStartNotablePoints) {
notablePoints[i++] = p;
}
}
}
public boolean isWithin(final Vector point) public boolean isWithin(final Vector point)
{ {
return circlePlane.isWithin(point); return circlePlane.isWithin(point);
@ -407,9 +458,9 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
return dist; return dist;
} }
public boolean intersects(final Plane p, final Membership[] bounds, final Membership previousEndCutoff, final Membership nextStartCutoff) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds)
{ {
return circlePlane.intersects(p, bounds, previousEndCutoff, nextStartCutoff); return circlePlane.intersects(p, notablePoints, this.notablePoints, bounds, this.cutoffPlanes);
} }
public void getBounds(Bounds bounds) public void getBounds(Bounds bounds)
@ -450,6 +501,10 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
public final SidedPlane lowerConnectingPlane; public final SidedPlane lowerConnectingPlane;
public final SidedPlane startCutoffPlane; public final SidedPlane startCutoffPlane;
public final SidedPlane endCutoffPlane; public final SidedPlane endCutoffPlane;
public final GeoPoint[] upperConnectingPlanePoints;
public final GeoPoint[] lowerConnectingPlanePoints;
public final GeoPoint[] startCutoffPlanePoints;
public final GeoPoint[] endCutoffPlanePoints;
public final double planeBoundingOffset; public final double planeBoundingOffset;
public final double arcWidth; public final double arcWidth;
public final double chordDistance; public final double chordDistance;
@ -475,6 +530,10 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
lowerConnectingPlane = null; lowerConnectingPlane = null;
startCutoffPlane = null; startCutoffPlane = null;
endCutoffPlane = null; endCutoffPlane = null;
upperConnectingPlanePoints = null;
lowerConnectingPlanePoints = null;
startCutoffPlanePoints = null;
endCutoffPlanePoints = null;
invertedStartCutoffPlane = null; invertedStartCutoffPlane = null;
invertedEndCutoffPlane = null; invertedEndCutoffPlane = null;
} else { } else {
@ -484,6 +543,18 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
// Cutoff planes use opposite endpoints as correct side examples // Cutoff planes use opposite endpoints as correct side examples
startCutoffPlane = new SidedPlane(end,normalizedConnectingPlane,start); startCutoffPlane = new SidedPlane(end,normalizedConnectingPlane,start);
endCutoffPlane = new SidedPlane(start,normalizedConnectingPlane,end); endCutoffPlane = new SidedPlane(start,normalizedConnectingPlane,end);
final Membership[] upperSide = new Membership[]{upperConnectingPlane};
final Membership[] lowerSide = new Membership[]{lowerConnectingPlane};
final Membership[] startSide = new Membership[]{startCutoffPlane};
final Membership[] endSide = new Membership[]{endCutoffPlane};
final GeoPoint ULHC = upperConnectingPlane.findIntersections(startCutoffPlane,lowerSide,endSide)[0];
final GeoPoint URHC = upperConnectingPlane.findIntersections(endCutoffPlane,lowerSide,startSide)[0];
final GeoPoint LLHC = lowerConnectingPlane.findIntersections(startCutoffPlane,upperSide,endSide)[0];
final GeoPoint LRHC = lowerConnectingPlane.findIntersections(endCutoffPlane,upperSide,startSide)[0];
upperConnectingPlanePoints = new GeoPoint[]{ULHC,URHC};
lowerConnectingPlanePoints = new GeoPoint[]{LLHC,LRHC};
startCutoffPlanePoints = new GeoPoint[]{ULHC,LLHC};
endCutoffPlanePoints = new GeoPoint[]{URHC,LRHC};
invertedStartCutoffPlane = new SidedPlane(startCutoffPlane); invertedStartCutoffPlane = new SidedPlane(startCutoffPlane);
invertedEndCutoffPlane = new SidedPlane(endCutoffPlane); invertedEndCutoffPlane = new SidedPlane(endCutoffPlane);
} }
@ -535,7 +606,7 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
final double perpZ = normalizedConnectingPlane.x * point.y - normalizedConnectingPlane.y * point.x; final double perpZ = normalizedConnectingPlane.x * point.y - normalizedConnectingPlane.y * point.x;
// If we have a degenerate line, then just compute the normal distance from point x to the start // If we have a degenerate line, then just compute the normal distance from point x to the start
if (perpX < 1e-10 && perpY < 1e-10 && perpZ < 1e-10) if (Math.abs(perpX) < Vector.MINIMUM_RESOLUTION && Math.abs(perpY) < Vector.MINIMUM_RESOLUTION && Math.abs(perpZ) < Vector.MINIMUM_RESOLUTION)
return point.normalDistance(start); return point.normalDistance(start);
final double normFactor = 1.0 / Math.sqrt(perpX * perpX + perpY * perpY + perpZ * perpZ); final double normFactor = 1.0 / Math.sqrt(perpX * perpX + perpY * perpY + perpZ * perpZ);
@ -556,7 +627,7 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
final double perpZ = normalizedConnectingPlane.x * point.y - normalizedConnectingPlane.y * point.x; final double perpZ = normalizedConnectingPlane.x * point.y - normalizedConnectingPlane.y * point.x;
// If we have a degenerate line, then just compute the normal distance from point x to the start // If we have a degenerate line, then just compute the normal distance from point x to the start
if (Math.abs(perpX) < 1e-10 && Math.abs(perpY) < 1e-10 && Math.abs(perpZ) < 1e-10) if (Math.abs(perpX) < Vector.MINIMUM_RESOLUTION && Math.abs(perpY) < Vector.MINIMUM_RESOLUTION && Math.abs(perpZ) < Vector.MINIMUM_RESOLUTION)
return point.linearDistance(start); return point.linearDistance(start);
// Next, we need the vector of the line, which is the cross product of the normalized connecting plane // Next, we need the vector of the line, which is the cross product of the normalized connecting plane
@ -584,10 +655,10 @@ public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape
return point.linearDistance(normLineX,normLineY,normLineZ) + start.linearDistance(normLineX,normLineY,normLineZ); return point.linearDistance(normLineX,normLineY,normLineZ) + start.linearDistance(normLineX,normLineY,normLineZ);
} }
public boolean intersects(final Plane p, final Membership[] bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds)
{ {
return upperConnectingPlane.intersects(p, bounds, lowerConnectingPlane, startCutoffPlane, endCutoffPlane) || return upperConnectingPlane.intersects(p, notablePoints, upperConnectingPlanePoints, bounds, lowerConnectingPlane, startCutoffPlane, endCutoffPlane) ||
lowerConnectingPlane.intersects(p, bounds, upperConnectingPlane, startCutoffPlane, endCutoffPlane); lowerConnectingPlane.intersects(p, notablePoints, lowerConnectingPlanePoints, bounds, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
} }
public void getBounds(Bounds bounds) public void getBounds(Bounds bounds)

View File

@ -41,42 +41,4 @@ public class GeoPoint extends Vector
return Tools.safeAcos(evaluate(v)); return Tools.safeAcos(evaluate(v));
} }
/** Find a single point that is a specified arc distance away from this point.
*/
public GeoPoint getSamplePoint(final double sinRotationAngle, final double cosRotationAngle) {
// Rotate in the best of three possible directions: x-y, x-z, y-z.
final double absX = Math.abs(x);
final double absY = Math.abs(y);
final double absZ = Math.abs(z);
if (absX > absY) {
// x > y
if (absY > absZ) {
// x > y > z
// rotate in x-y
return new GeoPoint(x*cosRotationAngle-y*sinRotationAngle,x*sinRotationAngle+y*cosRotationAngle,z);
} else {
// x > z > y OR z > x > y
// rotate in x-z
return new GeoPoint(x*cosRotationAngle-z*sinRotationAngle,y,x*sinRotationAngle+z*cosRotationAngle);
}
} else {
// y > x
if (absX > absZ) {
// y > x > z
// rotate in x-y
return new GeoPoint(x*cosRotationAngle-y*sinRotationAngle,x*sinRotationAngle+y*cosRotationAngle,z);
} else {
// y > z > x OR z > y > x
// rotate in y-z
return new GeoPoint(x,y*cosRotationAngle-z*sinRotationAngle,y*sinRotationAngle+z*cosRotationAngle);
}
}
}
/** Find a single point that is a specified arc distance away from this point.
*/
public GeoPoint getSamplePoint(final double rotationAngle) {
return getSamplePoint(Math.sin(rotationAngle), Math.cos(rotationAngle));
}
} }

View File

@ -39,7 +39,12 @@ public class GeoRectangle extends GeoBBoxBase
public final SidedPlane bottomPlane; public final SidedPlane bottomPlane;
public final SidedPlane leftPlane; public final SidedPlane leftPlane;
public final SidedPlane rightPlane; public final SidedPlane rightPlane;
public final GeoPoint[] topPlanePoints;
public final GeoPoint[] bottomPlanePoints;
public final GeoPoint[] leftPlanePoints;
public final GeoPoint[] rightPlanePoints;
public final GeoPoint centerPoint; public final GeoPoint centerPoint;
public final GeoPoint[] edgePoints; public final GeoPoint[] edgePoints;
@ -103,6 +108,11 @@ public class GeoRectangle extends GeoBBoxBase
this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon); this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon); this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon);
this.topPlanePoints = new GeoPoint[]{ULHC,URHC};
this.bottomPlanePoints = new GeoPoint[]{LLHC,LRHC};
this.leftPlanePoints = new GeoPoint[]{ULHC,LLHC};
this.rightPlanePoints = new GeoPoint[]{URHC,LRHC};
this.edgePoints = new GeoPoint[]{ULHC}; this.edgePoints = new GeoPoint[]{ULHC};
} }
@ -161,12 +171,12 @@ public class GeoRectangle extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
return p.intersects(topPlane,bounds,bottomPlane,leftPlane,rightPlane) || return p.intersects(topPlane,notablePoints,topPlanePoints,bounds,bottomPlane,leftPlane,rightPlane) ||
p.intersects(bottomPlane,bounds,topPlane,leftPlane,rightPlane) || p.intersects(bottomPlane,notablePoints,bottomPlanePoints,bounds,topPlane,leftPlane,rightPlane) ||
p.intersects(leftPlane,bounds,rightPlane,topPlane,bottomPlane) || p.intersects(leftPlane,notablePoints,leftPlanePoints,bounds,rightPlane,topPlane,bottomPlane) ||
p.intersects(rightPlane,bounds,leftPlane,topPlane,bottomPlane); p.intersects(rightPlane,notablePoints,rightPlanePoints,bounds,leftPlane,topPlane,bottomPlane);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
@ -188,27 +198,40 @@ public class GeoRectangle extends GeoBBoxBase
@Override @Override
public int getRelationship(final GeoShape path) { public int getRelationship(final GeoShape path) {
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideBBox(path); final int insideRectangle = isShapeInsideBBox(path);
if (insideRectangle == SOME_INSIDE) if (insideRectangle == SOME_INSIDE)
{
//System.err.println(" some inside");
return OVERLAPS; return OVERLAPS;
}
final boolean insideShape = path.isWithin(ULHC); final boolean insideShape = path.isWithin(ULHC);
if (insideRectangle == ALL_INSIDE && insideShape)
return OVERLAPS;
if (path.intersects(topPlane,bottomPlane,leftPlane,rightPlane) || if (insideRectangle == ALL_INSIDE && insideShape) {
path.intersects(bottomPlane,topPlane,leftPlane,rightPlane) || //System.err.println(" inside of each other");
path.intersects(leftPlane,topPlane,bottomPlane,rightPlane) ||
path.intersects(rightPlane,leftPlane,topPlane,bottomPlane))
return OVERLAPS; return OVERLAPS;
}
if (path.intersects(topPlane,topPlanePoints,bottomPlane,leftPlane,rightPlane) ||
path.intersects(bottomPlane,bottomPlanePoints,topPlane,leftPlane,rightPlane) ||
path.intersects(leftPlane,leftPlanePoints,topPlane,bottomPlane,rightPlane) ||
path.intersects(rightPlane,rightPlanePoints,leftPlane,topPlane,bottomPlane)) {
//System.err.println(" edges intersect");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) if (insideRectangle == ALL_INSIDE)
{
//System.err.println(" shape inside rectangle");
return WITHIN; return WITHIN;
}
if (insideShape) if (insideShape) {
//System.err.println(" shape contains rectangle");
return CONTAINS; return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT; return DISJOINT;
} }

View File

@ -37,11 +37,14 @@ public interface GeoShape extends Membership {
* helped for some complex shapes that are built out of overlapping parts. * helped for some complex shapes that are built out of overlapping parts.
*@param plane is the plane to assess for intersection with the shape's edges or *@param plane is the plane to assess for intersection with the shape's edges or
* bounding curves. * bounding curves.
*@param notablePoints represents the intersections of the plane with the supplied
* bounds. These are used to disambiguate when two planes are identical and it needs
* to be determined whether any points exist that fulfill all the bounds.
*@param bounds are a set of bounds that define an area that an *@param bounds are a set of bounds that define an area that an
* intersection must be within in order to qualify (provided by a GeoArea). * intersection must be within in order to qualify (provided by a GeoArea).
*@return true if there's such an intersection, false if not. *@return true if there's such an intersection, false if not.
*/ */
public boolean intersects(final Plane plane, final Membership... bounds); public boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds);
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
*@param bounds is the optional input bounds object. If this is null, *@param bounds is the optional input bounds object. If this is null,

View File

@ -0,0 +1,167 @@
package org.apache.lucene.spatial.spatial4j.geo3d;
/*
* 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.
*/
/** This GeoBBox represents an area rectangle limited only in north latitude.
*/
public class GeoSouthLatitudeZone extends GeoBBoxBase
{
public final double topLat;
public final double cosTopLat;
public final SidedPlane topPlane;
public final GeoPoint interiorPoint;
public final static GeoPoint[] planePoints = new GeoPoint[0];
public final GeoPoint topBoundaryPoint;
// Edge points
public final GeoPoint[] edgePoints;
public GeoSouthLatitudeZone(final double topLat)
{
this.topLat = topLat;
final double sinTopLat = Math.sin(topLat);
this.cosTopLat = Math.cos(topLat);
// Construct sample points, so we get our sidedness right
final Vector topPoint = new Vector(0.0,0.0,sinTopLat);
// Compute an interior point. Pick one whose lat is between top and bottom.
final double middleLat = (topLat - Math.PI * 0.5) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
this.interiorPoint = new GeoPoint(Math.sqrt(1.0 - sinMiddleLat * sinMiddleLat),0.0,sinMiddleLat);
this.topBoundaryPoint = new GeoPoint(Math.sqrt(1.0 - sinTopLat * sinTopLat),0.0,sinTopLat);
this.topPlane = new SidedPlane(interiorPoint,sinTopLat);
this.edgePoints = new GeoPoint[]{topBoundaryPoint};
}
@Override
public GeoBBox expand(final double angle)
{
final double newTopLat = topLat + angle;
final double newBottomLat = -Math.PI * 0.5;
return GeoBBoxFactory.makeGeoBBox(newTopLat, newBottomLat, -Math.PI, Math.PI);
}
@Override
public boolean isWithin(final Vector point)
{
return topPlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z)
{
return topPlane.isWithin(x,y,z);
}
@Override
public double getRadius()
{
// This is a bit tricky. I guess we should interpret this as meaning the angle of a circle that
// would contain all the bounding box points, when starting in the "center".
if (topLat > 0.0)
return Math.PI;
double maxCosLat = cosTopLat;
return maxCosLat * Math.PI;
}
@Override
public GeoPoint[] getEdgePoints()
{
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{
return p.intersects(topPlane,notablePoints,planePoints,bounds);
}
/** Compute longitude/latitude bounds for the shape.
*@param bounds is the optional input bounds object. If this is null,
* a bounds object will be created. Otherwise, the input object will be modified.
*@return a Bounds object describing the shape's bounds. If the bounds cannot
* be computed, then return a Bounds object with noLongitudeBound,
* noTopLatitudeBound, and noBottomLatitudeBound.
*/
@Override
public Bounds getBounds(Bounds bounds)
{
if (bounds == null)
bounds = new Bounds();
bounds.noLongitudeBound().addLatitudeZone(topLat).noBottomLatitudeBound();
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
final int insideRectangle = isShapeInsideBBox(path);
if (insideRectangle == SOME_INSIDE)
return OVERLAPS;
final boolean insideShape = path.isWithin(topBoundaryPoint);
if (insideRectangle == ALL_INSIDE && insideShape)
return OVERLAPS;
// Second, the shortcut of seeing whether endpoints are in/out is not going to
// work with no area endpoints. So we rely entirely on intersections.
if (path.intersects(topPlane,planePoints))
return OVERLAPS;
// There is another case for latitude zones only. This is when the boundaries of the shape all fit
// within the zone, but the shape includes areas outside the zone crossing a pole.
// In this case, the above "overlaps" check is insufficient. We also need to check a point on either boundary
// whether it is within the shape. If both such points are within, then CONTAINS is the right answer. If
// one such point is within, then OVERLAPS is the right answer.
if (insideShape)
return CONTAINS;
if (insideRectangle == ALL_INSIDE)
return WITHIN;
return DISJOINT;
}
@Override
public boolean equals(Object o)
{
if (!(o instanceof GeoSouthLatitudeZone))
return false;
GeoSouthLatitudeZone other = (GeoSouthLatitudeZone)o;
return other.topPlane.equals(topPlane);
}
@Override
public int hashCode() {
int result = topPlane.hashCode();
return result;
}
@Override
public String toString() {
return "GeoSouthLatitudeZone: {toplat="+topLat+"("+topLat*180.0/Math.PI+")}";
}
}

View File

@ -0,0 +1,238 @@
package org.apache.lucene.spatial.spatial4j.geo3d;
/*
* 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.
*/
/** Bounding box limited on three sides (top lat, left lon, right lon). The
* other corner is the south pole.
* The left-right maximum extent for this shape is PI; for anything larger, use
* GeoWideSouthRectangle.
*/
public class GeoSouthRectangle extends GeoBBoxBase
{
public final double topLat;
public final double leftLon;
public final double rightLon;
public final double cosMiddleLat;
public final GeoPoint ULHC;
public final GeoPoint URHC;
public final SidedPlane topPlane;
public final SidedPlane leftPlane;
public final SidedPlane rightPlane;
public final GeoPoint[] topPlanePoints;
public final GeoPoint[] leftPlanePoints;
public final GeoPoint[] rightPlanePoints;
public final GeoPoint centerPoint;
public final GeoPoint[] edgePoints = new GeoPoint[]{SOUTH_POLE};
/** Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI} */
public GeoSouthRectangle(final double topLat, final double leftLon, double rightLon)
{
// Argument checking
if (topLat > Math.PI * 0.5 || topLat < -Math.PI * 0.5)
throw new IllegalArgumentException("Top latitude out of range");
if (leftLon < -Math.PI || leftLon > Math.PI)
throw new IllegalArgumentException("Left longitude out of range");
if (rightLon < -Math.PI || rightLon > Math.PI)
throw new IllegalArgumentException("Right longitude out of range");
double extent = rightLon - leftLon;
if (extent < 0.0) {
extent += 2.0 * Math.PI;
}
if (extent > Math.PI)
throw new IllegalArgumentException("Width of rectangle too great");
this.topLat = topLat;
this.leftLon = leftLon;
this.rightLon = rightLon;
final double sinTopLat = Math.sin(topLat);
final double cosTopLat = Math.cos(topLat);
final double sinLeftLon = Math.sin(leftLon);
final double cosLeftLon = Math.cos(leftLon);
final double sinRightLon = Math.sin(rightLon);
final double cosRightLon = Math.cos(rightLon);
// Now build the four points
this.ULHC = new GeoPoint(sinTopLat,sinLeftLon,cosTopLat,cosLeftLon);
this.URHC = new GeoPoint(sinTopLat,sinRightLon,cosTopLat,cosRightLon);
final double middleLat = (topLat - Math.PI * 0.5) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
this.cosMiddleLat = Math.cos(middleLat);
// Normalize
while (leftLon > rightLon) {
rightLon += Math.PI * 2.0;
}
final double middleLon = (leftLon + rightLon) * 0.5;
final double sinMiddleLon = Math.sin(middleLon);
final double cosMiddleLon = Math.cos(middleLon);
this.centerPoint = new GeoPoint(sinMiddleLat,sinMiddleLon,cosMiddleLat,cosMiddleLon);
this.topPlane = new SidedPlane(centerPoint,sinTopLat);
this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon);
this.topPlanePoints = new GeoPoint[]{ULHC,URHC};
this.leftPlanePoints = new GeoPoint[]{ULHC,SOUTH_POLE};
this.rightPlanePoints = new GeoPoint[]{URHC,SOUTH_POLE};
}
@Override
public GeoBBox expand(final double angle)
{
final double newTopLat = topLat + angle;
final double newBottomLat = -Math.PI * 0.5;
// Figuring out when we escalate to a special case requires some prefiguring
double currentLonSpan = rightLon - leftLon;
if (currentLonSpan < 0.0)
currentLonSpan += Math.PI * 2.0;
double newLeftLon = leftLon - angle;
double newRightLon = rightLon + angle;
if (currentLonSpan + 2.0 * angle >= Math.PI * 2.0) {
newLeftLon = -Math.PI;
newRightLon = Math.PI;
}
return GeoBBoxFactory.makeGeoBBox(newTopLat,newBottomLat,newLeftLon,newRightLon);
}
@Override
public boolean isWithin(final Vector point)
{
return topPlane.isWithin(point) &&
leftPlane.isWithin(point) &&
rightPlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z)
{
return topPlane.isWithin(x,y,z) &&
leftPlane.isWithin(x,y,z) &&
rightPlane.isWithin(x,y,z);
}
@Override
public double getRadius()
{
// Here we compute the distance from the middle point to one of the corners. However, we need to be careful
// to use the longest of three distances: the distance to a corner on the top; the distnace to a corner on the bottom, and
// the distance to the right or left edge from the center.
final double centerAngle = (rightLon - (rightLon + leftLon) * 0.5) * cosMiddleLat;
final double topAngle = centerPoint.arcDistance(URHC);
return Math.max(centerAngle,topAngle);
}
@Override
public GeoPoint[] getEdgePoints()
{
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{
return p.intersects(topPlane,notablePoints,topPlanePoints,bounds,leftPlane,rightPlane) ||
p.intersects(leftPlane,notablePoints,leftPlanePoints,bounds,rightPlane,topPlane) ||
p.intersects(rightPlane,notablePoints,rightPlanePoints,bounds,leftPlane,topPlane);
}
/** Compute longitude/latitude bounds for the shape.
*@param bounds is the optional input bounds object. If this is null,
* a bounds object will be created. Otherwise, the input object will be modified.
*@return a Bounds object describing the shape's bounds. If the bounds cannot
* be computed, then return a Bounds object with noLongitudeBound,
* noTopLatitudeBound, and noBottomLatitudeBound.
*/
@Override
public Bounds getBounds(Bounds bounds)
{
if (bounds == null)
bounds = new Bounds();
bounds.addLatitudeZone(topLat).noBottomLatitudeBound()
.addLongitudeSlice(leftLon,rightLon);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideBBox(path);
if (insideRectangle == SOME_INSIDE)
{
//System.err.println(" some inside");
return OVERLAPS;
}
final boolean insideShape = path.isWithin(SOUTH_POLE);
if (insideRectangle == ALL_INSIDE && insideShape) {
//System.err.println(" inside of each other");
return OVERLAPS;
}
if (path.intersects(topPlane,topPlanePoints,leftPlane,rightPlane) ||
path.intersects(leftPlane,leftPlanePoints,topPlane,rightPlane) ||
path.intersects(rightPlane,rightPlanePoints,leftPlane,topPlane)) {
//System.err.println(" edges intersect");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE)
{
//System.err.println(" shape inside rectangle");
return WITHIN;
}
if (insideShape) {
//System.err.println(" shape contains rectangle");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o)
{
if (!(o instanceof GeoSouthRectangle))
return false;
GeoSouthRectangle other = (GeoSouthRectangle)o;
return other.ULHC.equals(ULHC) && other.URHC.equals(URHC);
}
@Override
public int hashCode() {
int result = ULHC.hashCode();
result = 31 * result + URHC.hashCode();
return result;
}
@Override
public String toString() {
return "GeoSouthRectangle: {toplat="+topLat+"("+topLat*180.0/Math.PI+"), leftlon="+leftLon+"("+leftLon*180.0/Math.PI+"), rightlon="+rightLon+"("+rightLon*180.0/Math.PI+")}";
}
}

View File

@ -31,7 +31,9 @@ public class GeoWideDegenerateHorizontalLine extends GeoBBoxBase
public final Plane plane; public final Plane plane;
public final SidedPlane leftPlane; public final SidedPlane leftPlane;
public final SidedPlane rightPlane; public final SidedPlane rightPlane;
public final GeoPoint[] planePoints;
public final GeoPoint centerPoint; public final GeoPoint centerPoint;
public final EitherBound eitherBound; public final EitherBound eitherBound;
@ -87,6 +89,8 @@ public class GeoWideDegenerateHorizontalLine extends GeoBBoxBase
this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon); this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon); this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon);
this.planePoints = new GeoPoint[]{LHC,RHC};
this.eitherBound = new EitherBound(); this.eitherBound = new EitherBound();
this.edgePoints = new GeoPoint[]{centerPoint}; this.edgePoints = new GeoPoint[]{centerPoint};
@ -115,7 +119,7 @@ public class GeoWideDegenerateHorizontalLine extends GeoBBoxBase
{ {
if (point == null) if (point == null)
return false; return false;
return plane.evaluate(point) == 0.0 && return plane.evaluateIsZero(point) &&
(leftPlane.isWithin(point) || (leftPlane.isWithin(point) ||
rightPlane.isWithin(point)); rightPlane.isWithin(point));
} }
@ -123,7 +127,7 @@ public class GeoWideDegenerateHorizontalLine extends GeoBBoxBase
@Override @Override
public boolean isWithin(final double x, final double y, final double z) public boolean isWithin(final double x, final double y, final double z)
{ {
return plane.evaluate(x,y,z) == 0.0 && return plane.evaluateIsZero(x,y,z) &&
(leftPlane.isWithin(x,y,z) || (leftPlane.isWithin(x,y,z) ||
rightPlane.isWithin(x,y,z)); rightPlane.isWithin(x,y,z));
} }
@ -146,11 +150,11 @@ public class GeoWideDegenerateHorizontalLine extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
// Right and left bounds are essentially independent hemispheres; crossing into the wrong part of one // Right and left bounds are essentially independent hemispheres; crossing into the wrong part of one
// requires crossing into the right part of the other. So intersection can ignore the left/right bounds. // requires crossing into the right part of the other. So intersection can ignore the left/right bounds.
return p.intersects(plane,bounds,eitherBound); return p.intersects(plane,notablePoints,planePoints,bounds,eitherBound);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
@ -172,7 +176,7 @@ public class GeoWideDegenerateHorizontalLine extends GeoBBoxBase
@Override @Override
public int getRelationship(final GeoShape path) { public int getRelationship(final GeoShape path) {
if (path.intersects(plane,eitherBound)) { if (path.intersects(plane,planePoints,eitherBound)) {
return OVERLAPS; return OVERLAPS;
} }

View File

@ -27,10 +27,12 @@ public class GeoWideLongitudeSlice extends GeoBBoxBase
public final SidedPlane leftPlane; public final SidedPlane leftPlane;
public final SidedPlane rightPlane; public final SidedPlane rightPlane;
public final static GeoPoint[] planePoints = new GeoPoint[]{NORTH_POLE,SOUTH_POLE};
public final GeoPoint centerPoint; public final GeoPoint centerPoint;
public final GeoPoint northPole = new GeoPoint(0.0,0.0,1.0);
public final GeoPoint[] edgePoints = new GeoPoint[]{northPole}; public final static GeoPoint[] edgePoints = new GeoPoint[]{NORTH_POLE};
/** Accepts only values in the following ranges: lon: {@code -PI -> PI}. /** Accepts only values in the following ranges: lon: {@code -PI -> PI}.
* Horizantal angle must be greater than or equal to PI. * Horizantal angle must be greater than or equal to PI.
@ -115,12 +117,12 @@ public class GeoWideLongitudeSlice extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
// Right and left bounds are essentially independent hemispheres; crossing into the wrong part of one // Right and left bounds are essentially independent hemispheres; crossing into the wrong part of one
// requires crossing into the right part of the other. So intersection can ignore the left/right bounds. // requires crossing into the right part of the other. So intersection can ignore the left/right bounds.
return p.intersects(leftPlane,bounds) || return p.intersects(leftPlane,notablePoints,planePoints,bounds) ||
p.intersects(rightPlane,bounds); p.intersects(rightPlane,notablePoints,planePoints,bounds);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
@ -146,13 +148,13 @@ public class GeoWideLongitudeSlice extends GeoBBoxBase
if (insideRectangle == SOME_INSIDE) if (insideRectangle == SOME_INSIDE)
return OVERLAPS; return OVERLAPS;
final boolean insideShape = path.isWithin(northPole); final boolean insideShape = path.isWithin(NORTH_POLE);
if (insideRectangle == ALL_INSIDE && insideShape) if (insideRectangle == ALL_INSIDE && insideShape)
return OVERLAPS; return OVERLAPS;
if (path.intersects(leftPlane) || if (path.intersects(leftPlane,planePoints) ||
path.intersects(rightPlane)) path.intersects(rightPlane,planePoints))
return OVERLAPS; return OVERLAPS;
if (insideRectangle == ALL_INSIDE) if (insideRectangle == ALL_INSIDE)

View File

@ -0,0 +1,263 @@
package org.apache.lucene.spatial.spatial4j.geo3d;
/*
* 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.
*/
/** Bounding box wider than PI but limited on three sides (
* bottom lat, left lon, right lon).
*/
public class GeoWideNorthRectangle extends GeoBBoxBase
{
public final double bottomLat;
public final double leftLon;
public final double rightLon;
public final double cosMiddleLat;
public final GeoPoint LRHC;
public final GeoPoint LLHC;
public final SidedPlane bottomPlane;
public final SidedPlane leftPlane;
public final SidedPlane rightPlane;
public final GeoPoint[] bottomPlanePoints;
public final GeoPoint[] leftPlanePoints;
public final GeoPoint[] rightPlanePoints;
public final GeoPoint centerPoint;
public final EitherBound eitherBound;
public final GeoPoint[] edgePoints = new GeoPoint[]{NORTH_POLE};
/** Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI}.
* Horizontal angle must be greater than or equal to PI.
*/
public GeoWideNorthRectangle(final double bottomLat, final double leftLon, double rightLon)
{
// Argument checking
if (bottomLat > Math.PI * 0.5 || bottomLat < -Math.PI * 0.5)
throw new IllegalArgumentException("Bottom latitude out of range");
if (leftLon < -Math.PI || leftLon > Math.PI)
throw new IllegalArgumentException("Left longitude out of range");
if (rightLon < -Math.PI || rightLon > Math.PI)
throw new IllegalArgumentException("Right longitude out of range");
double extent = rightLon - leftLon;
if (extent < 0.0) {
extent += 2.0 * Math.PI;
}
if (extent < Math.PI)
throw new IllegalArgumentException("Width of rectangle too small");
this.bottomLat = bottomLat;
this.leftLon = leftLon;
this.rightLon = rightLon;
final double sinBottomLat = Math.sin(bottomLat);
final double cosBottomLat = Math.cos(bottomLat);
final double sinLeftLon = Math.sin(leftLon);
final double cosLeftLon = Math.cos(leftLon);
final double sinRightLon = Math.sin(rightLon);
final double cosRightLon = Math.cos(rightLon);
// Now build the four points
this.LRHC = new GeoPoint(sinBottomLat,sinRightLon,cosBottomLat,cosRightLon);
this.LLHC = new GeoPoint(sinBottomLat,sinLeftLon,cosBottomLat,cosLeftLon);
final double middleLat = (Math.PI * 0.5 + bottomLat) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
this.cosMiddleLat = Math.cos(middleLat);
// Normalize
while (leftLon > rightLon) {
rightLon += Math.PI * 2.0;
}
final double middleLon = (leftLon + rightLon) * 0.5;
final double sinMiddleLon = Math.sin(middleLon);
final double cosMiddleLon = Math.cos(middleLon);
this.centerPoint = new GeoPoint(sinMiddleLat,sinMiddleLon,cosMiddleLat,cosMiddleLon);
this.bottomPlane = new SidedPlane(centerPoint,sinBottomLat);
this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon);
this.bottomPlanePoints = new GeoPoint[]{LLHC,LRHC};
this.leftPlanePoints = new GeoPoint[]{NORTH_POLE,LLHC};
this.rightPlanePoints = new GeoPoint[]{NORTH_POLE,LRHC};
this.eitherBound = new EitherBound();
}
@Override
public GeoBBox expand(final double angle)
{
final double newTopLat = Math.PI * 0.5;
final double newBottomLat = bottomLat - angle;
// Figuring out when we escalate to a special case requires some prefiguring
double currentLonSpan = rightLon - leftLon;
if (currentLonSpan < 0.0)
currentLonSpan += Math.PI * 2.0;
double newLeftLon = leftLon - angle;
double newRightLon = rightLon + angle;
if (currentLonSpan + 2.0 * angle >= Math.PI * 2.0) {
newLeftLon = -Math.PI;
newRightLon = Math.PI;
}
return GeoBBoxFactory.makeGeoBBox(newTopLat,newBottomLat,newLeftLon,newRightLon);
}
@Override
public boolean isWithin(final Vector point)
{
return
bottomPlane.isWithin(point) &&
(leftPlane.isWithin(point) ||
rightPlane.isWithin(point));
}
@Override
public boolean isWithin(final double x, final double y, final double z)
{
return
bottomPlane.isWithin(x,y,z) &&
(leftPlane.isWithin(x,y,z) ||
rightPlane.isWithin(x,y,z));
}
@Override
public double getRadius()
{
// Here we compute the distance from the middle point to one of the corners. However, we need to be careful
// to use the longest of three distances: the distance to a corner on the top; the distnace to a corner on the bottom, and
// the distance to the right or left edge from the center.
final double centerAngle = (rightLon - (rightLon + leftLon) * 0.5) * cosMiddleLat;
final double bottomAngle = centerPoint.arcDistance(LLHC);
return Math.max(centerAngle,bottomAngle);
}
@Override
public GeoPoint[] getEdgePoints()
{
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{
// Right and left bounds are essentially independent hemispheres; crossing into the wrong part of one
// requires crossing into the right part of the other. So intersection can ignore the left/right bounds.
return
p.intersects(bottomPlane,notablePoints,bottomPlanePoints,bounds,eitherBound) ||
p.intersects(leftPlane,notablePoints,leftPlanePoints,bounds,bottomPlane) ||
p.intersects(rightPlane,notablePoints,rightPlanePoints,bounds,bottomPlane);
}
/** Compute longitude/latitude bounds for the shape.
*@param bounds is the optional input bounds object. If this is null,
* a bounds object will be created. Otherwise, the input object will be modified.
*@return a Bounds object describing the shape's bounds. If the bounds cannot
* be computed, then return a Bounds object with noLongitudeBound,
* noTopLatitudeBound, and noBottomLatitudeBound.
*/
@Override
public Bounds getBounds(Bounds bounds)
{
if (bounds == null)
bounds = new Bounds();
bounds.noTopLatitudeBound().addLatitudeZone(bottomLat)
.addLongitudeSlice(leftLon,rightLon);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" comparing to "+path);
final int insideRectangle = isShapeInsideBBox(path);
if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some inside");
return OVERLAPS;
}
final boolean insideShape = path.isWithin(NORTH_POLE);
if (insideRectangle == ALL_INSIDE && insideShape)
{
//System.err.println(" both inside each other");
return OVERLAPS;
}
if (
path.intersects(bottomPlane,bottomPlanePoints,eitherBound) ||
path.intersects(leftPlane,leftPlanePoints,bottomPlane) ||
path.intersects(rightPlane,rightPlanePoints,bottomPlane)) {
//System.err.println(" edges intersect");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
//System.err.println(" shape inside rectangle");
return WITHIN;
}
if (insideShape) {
//System.err.println(" rectangle inside shape");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o)
{
if (!(o instanceof GeoWideNorthRectangle))
return false;
GeoWideNorthRectangle other = (GeoWideNorthRectangle)o;
return other.LLHC.equals(LLHC) && other.LRHC.equals(LRHC);
}
@Override
public int hashCode() {
int result = LLHC.hashCode();
result = 31 * result + LRHC.hashCode();
return result;
}
@Override
public String toString() {
return "GeoWideNorthRectangle: {bottomlat="+bottomLat+"("+bottomLat*180.0/Math.PI+"), leftlon="+leftLon+"("+leftLon*180.0/Math.PI+"), rightlon="+rightLon+"("+rightLon*180.0/Math.PI+")}";
}
protected class EitherBound implements Membership {
public EitherBound() {
}
@Override
public boolean isWithin(final Vector v) {
return leftPlane.isWithin(v) || rightPlane.isWithin(v);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return leftPlane.isWithin(x,y,z) || rightPlane.isWithin(x,y,z);
}
}
}

View File

@ -38,7 +38,12 @@ public class GeoWideRectangle extends GeoBBoxBase
public final SidedPlane bottomPlane; public final SidedPlane bottomPlane;
public final SidedPlane leftPlane; public final SidedPlane leftPlane;
public final SidedPlane rightPlane; public final SidedPlane rightPlane;
public final GeoPoint[] topPlanePoints;
public final GeoPoint[] bottomPlanePoints;
public final GeoPoint[] leftPlanePoints;
public final GeoPoint[] rightPlanePoints;
public final GeoPoint centerPoint; public final GeoPoint centerPoint;
public final EitherBound eitherBound; public final EitherBound eitherBound;
@ -105,7 +110,12 @@ public class GeoWideRectangle extends GeoBBoxBase
this.bottomPlane = new SidedPlane(centerPoint,sinBottomLat); this.bottomPlane = new SidedPlane(centerPoint,sinBottomLat);
this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon); this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon); this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon);
this.topPlanePoints = new GeoPoint[]{ULHC,URHC};
this.bottomPlanePoints = new GeoPoint[]{LLHC,LRHC};
this.leftPlanePoints = new GeoPoint[]{ULHC,LLHC};
this.rightPlanePoints = new GeoPoint[]{URHC,LRHC};
this.eitherBound = new EitherBound(); this.eitherBound = new EitherBound();
this.edgePoints = new GeoPoint[]{ULHC}; this.edgePoints = new GeoPoint[]{ULHC};
@ -166,14 +176,14 @@ public class GeoWideRectangle extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
// Right and left bounds are essentially independent hemispheres; crossing into the wrong part of one // Right and left bounds are essentially independent hemispheres; crossing into the wrong part of one
// requires crossing into the right part of the other. So intersection can ignore the left/right bounds. // requires crossing into the right part of the other. So intersection can ignore the left/right bounds.
return p.intersects(topPlane,bounds,bottomPlane,eitherBound) || return p.intersects(topPlane,notablePoints,topPlanePoints,bounds,bottomPlane,eitherBound) ||
p.intersects(bottomPlane,bounds,topPlane,eitherBound) || p.intersects(bottomPlane,notablePoints,bottomPlanePoints,bounds,topPlane,eitherBound) ||
p.intersects(leftPlane,bounds,topPlane,bottomPlane) || p.intersects(leftPlane,notablePoints,leftPlanePoints,bounds,topPlane,bottomPlane) ||
p.intersects(rightPlane,bounds,topPlane,bottomPlane); p.intersects(rightPlane,notablePoints,rightPlanePoints,bounds,topPlane,bottomPlane);
} }
/** Compute longitude/latitude bounds for the shape. /** Compute longitude/latitude bounds for the shape.
@ -195,30 +205,40 @@ public class GeoWideRectangle extends GeoBBoxBase
@Override @Override
public int getRelationship(final GeoShape path) { public int getRelationship(final GeoShape path) {
//System.err.println(this+" comparing to "+path);
final int insideRectangle = isShapeInsideBBox(path); final int insideRectangle = isShapeInsideBBox(path);
if (insideRectangle == SOME_INSIDE) if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some inside");
return OVERLAPS; return OVERLAPS;
}
final boolean insideShape = path.isWithin(ULHC); final boolean insideShape = path.isWithin(ULHC);
if (insideRectangle == ALL_INSIDE && insideShape) if (insideRectangle == ALL_INSIDE && insideShape)
{
//System.err.println(" both inside each other");
return OVERLAPS; return OVERLAPS;
}
if (path.intersects(topPlane,bottomPlane,eitherBound) || if (path.intersects(topPlane,topPlanePoints,bottomPlane,eitherBound) ||
path.intersects(bottomPlane,topPlane,eitherBound) || path.intersects(bottomPlane,bottomPlanePoints,topPlane,eitherBound) ||
path.intersects(leftPlane,topPlane,bottomPlane) || path.intersects(leftPlane,leftPlanePoints,topPlane,bottomPlane) ||
path.intersects(rightPlane,topPlane,bottomPlane)) { path.intersects(rightPlane,rightPlanePoints,topPlane,bottomPlane)) {
//System.err.println(" edges intersect");
return OVERLAPS; return OVERLAPS;
} }
if (insideRectangle == ALL_INSIDE) { if (insideRectangle == ALL_INSIDE) {
//System.err.println(" shape inside rectangle");
return WITHIN; return WITHIN;
} }
if (insideShape) { if (insideShape) {
//System.err.println(" rectangle inside shape");
return CONTAINS; return CONTAINS;
} }
//System.err.println(" disjoint");
return DISJOINT; return DISJOINT;
} }

View File

@ -0,0 +1,259 @@
package org.apache.lucene.spatial.spatial4j.geo3d;
/*
* 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.
*/
/** Bounding box wider than PI but limited on three sides (top lat,
* left lon, right lon).
*/
public class GeoWideSouthRectangle extends GeoBBoxBase
{
public final double topLat;
public final double leftLon;
public final double rightLon;
public final double cosMiddleLat;
public final GeoPoint ULHC;
public final GeoPoint URHC;
public final SidedPlane topPlane;
public final SidedPlane leftPlane;
public final SidedPlane rightPlane;
public final GeoPoint[] topPlanePoints;
public final GeoPoint[] leftPlanePoints;
public final GeoPoint[] rightPlanePoints;
public final GeoPoint centerPoint;
public final EitherBound eitherBound;
public final GeoPoint[] edgePoints = new GeoPoint[]{SOUTH_POLE};
/** Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI}.
* Horizontal angle must be greater than or equal to PI.
*/
public GeoWideSouthRectangle(final double topLat, final double leftLon, double rightLon)
{
// Argument checking
if (topLat > Math.PI * 0.5 || topLat < -Math.PI * 0.5)
throw new IllegalArgumentException("Top latitude out of range");
if (leftLon < -Math.PI || leftLon > Math.PI)
throw new IllegalArgumentException("Left longitude out of range");
if (rightLon < -Math.PI || rightLon > Math.PI)
throw new IllegalArgumentException("Right longitude out of range");
double extent = rightLon - leftLon;
if (extent < 0.0) {
extent += 2.0 * Math.PI;
}
if (extent < Math.PI)
throw new IllegalArgumentException("Width of rectangle too small");
this.topLat = topLat;
this.leftLon = leftLon;
this.rightLon = rightLon;
final double sinTopLat = Math.sin(topLat);
final double cosTopLat = Math.cos(topLat);
final double sinLeftLon = Math.sin(leftLon);
final double cosLeftLon = Math.cos(leftLon);
final double sinRightLon = Math.sin(rightLon);
final double cosRightLon = Math.cos(rightLon);
// Now build the four points
this.ULHC = new GeoPoint(sinTopLat,sinLeftLon,cosTopLat,cosLeftLon);
this.URHC = new GeoPoint(sinTopLat,sinRightLon,cosTopLat,cosRightLon);
final double middleLat = (topLat - Math.PI * 0.5) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
this.cosMiddleLat = Math.cos(middleLat);
// Normalize
while (leftLon > rightLon) {
rightLon += Math.PI * 2.0;
}
final double middleLon = (leftLon + rightLon) * 0.5;
final double sinMiddleLon = Math.sin(middleLon);
final double cosMiddleLon = Math.cos(middleLon);
this.centerPoint = new GeoPoint(sinMiddleLat,sinMiddleLon,cosMiddleLat,cosMiddleLon);
this.topPlane = new SidedPlane(centerPoint,sinTopLat);
this.leftPlane = new SidedPlane(centerPoint,cosLeftLon,sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint,cosRightLon,sinRightLon);
this.topPlanePoints = new GeoPoint[]{ULHC,URHC};
this.leftPlanePoints = new GeoPoint[]{ULHC,SOUTH_POLE};
this.rightPlanePoints = new GeoPoint[]{URHC,SOUTH_POLE};
this.eitherBound = new EitherBound();
}
@Override
public GeoBBox expand(final double angle)
{
final double newTopLat = topLat + angle;
final double newBottomLat = -Math.PI * 0.5;
// Figuring out when we escalate to a special case requires some prefiguring
double currentLonSpan = rightLon - leftLon;
if (currentLonSpan < 0.0)
currentLonSpan += Math.PI * 2.0;
double newLeftLon = leftLon - angle;
double newRightLon = rightLon + angle;
if (currentLonSpan + 2.0 * angle >= Math.PI * 2.0) {
newLeftLon = -Math.PI;
newRightLon = Math.PI;
}
return GeoBBoxFactory.makeGeoBBox(newTopLat,newBottomLat,newLeftLon,newRightLon);
}
@Override
public boolean isWithin(final Vector point)
{
return topPlane.isWithin(point) &&
(leftPlane.isWithin(point) ||
rightPlane.isWithin(point));
}
@Override
public boolean isWithin(final double x, final double y, final double z)
{
return topPlane.isWithin(x,y,z) &&
(leftPlane.isWithin(x,y,z) ||
rightPlane.isWithin(x,y,z));
}
@Override
public double getRadius()
{
// Here we compute the distance from the middle point to one of the corners. However, we need to be careful
// to use the longest of three distances: the distance to a corner on the top; the distnace to a corner on the bottom, and
// the distance to the right or left edge from the center.
final double centerAngle = (rightLon - (rightLon + leftLon) * 0.5) * cosMiddleLat;
final double topAngle = centerPoint.arcDistance(URHC);
return Math.max(centerAngle,topAngle);
}
@Override
public GeoPoint[] getEdgePoints()
{
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{
// Right and left bounds are essentially independent hemispheres; crossing into the wrong part of one
// requires crossing into the right part of the other. So intersection can ignore the left/right bounds.
return p.intersects(topPlane,notablePoints,topPlanePoints,bounds,eitherBound) ||
p.intersects(leftPlane,notablePoints,leftPlanePoints,bounds,topPlane) ||
p.intersects(rightPlane,notablePoints,rightPlanePoints,bounds,topPlane);
}
/** Compute longitude/latitude bounds for the shape.
*@param bounds is the optional input bounds object. If this is null,
* a bounds object will be created. Otherwise, the input object will be modified.
*@return a Bounds object describing the shape's bounds. If the bounds cannot
* be computed, then return a Bounds object with noLongitudeBound,
* noTopLatitudeBound, and noBottomLatitudeBound.
*/
@Override
public Bounds getBounds(Bounds bounds)
{
if (bounds == null)
bounds = new Bounds();
bounds.addLatitudeZone(topLat).noBottomLatitudeBound()
.addLongitudeSlice(leftLon,rightLon);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" comparing to "+path);
final int insideRectangle = isShapeInsideBBox(path);
if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some inside");
return OVERLAPS;
}
final boolean insideShape = path.isWithin(SOUTH_POLE);
if (insideRectangle == ALL_INSIDE && insideShape)
{
//System.err.println(" both inside each other");
return OVERLAPS;
}
if (path.intersects(topPlane,topPlanePoints,eitherBound) ||
path.intersects(leftPlane,leftPlanePoints,topPlane) ||
path.intersects(rightPlane,rightPlanePoints,topPlane)) {
//System.err.println(" edges intersect");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
//System.err.println(" shape inside rectangle");
return WITHIN;
}
if (insideShape) {
//System.err.println(" rectangle inside shape");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o)
{
if (!(o instanceof GeoWideSouthRectangle))
return false;
GeoWideSouthRectangle other = (GeoWideSouthRectangle)o;
return other.ULHC.equals(ULHC) && other.URHC.equals(URHC);
}
@Override
public int hashCode() {
int result = ULHC.hashCode();
result = 31 * result + URHC.hashCode();
return result;
}
@Override
public String toString() {
return "GeoWideSouthRectangle: {toplat="+topLat+"("+topLat*180.0/Math.PI+"), leftlon="+leftLon+"("+leftLon*180.0/Math.PI+"), rightlon="+rightLon+"("+rightLon*180.0/Math.PI+")}";
}
protected class EitherBound implements Membership {
public EitherBound() {
}
@Override
public boolean isWithin(final Vector v) {
return leftPlane.isWithin(v) || rightPlane.isWithin(v);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return leftPlane.isWithin(x,y,z) || rightPlane.isWithin(x,y,z);
}
}
}

View File

@ -59,7 +59,7 @@ public class GeoWorld extends GeoBBoxBase
} }
@Override @Override
public boolean intersects(final Plane p, final Membership... bounds) public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds)
{ {
return false; return false;
} }

View File

@ -216,7 +216,7 @@ public class Plane extends Vector
// Now compute latitude min/max points // Now compute latitude min/max points
if (!boundsInfo.checkNoTopLatitudeBound() || !boundsInfo.checkNoBottomLatitudeBound()) { if (!boundsInfo.checkNoTopLatitudeBound() || !boundsInfo.checkNoBottomLatitudeBound()) {
if ((Math.abs(A) >= 1e-10 || Math.abs(B) >= 1e-10)) { if ((Math.abs(A) >= MINIMUM_RESOLUTION || Math.abs(B) >= MINIMUM_RESOLUTION)) {
//System.out.println("A = "+A+" B = "+B+" C = "+C+" D = "+D); //System.out.println("A = "+A+" B = "+B+" C = "+C+" D = "+D);
// sin (phi) = z // sin (phi) = z
// cos (theta - phi) = D // cos (theta - phi) = D
@ -359,11 +359,11 @@ public class Plane extends Vector
double b; double b;
double c; double c;
if (Math.abs(C) < 1e-10) { if (Math.abs(C) < MINIMUM_RESOLUTION) {
// Degenerate; the equation describes a line // Degenerate; the equation describes a line
//System.out.println("It's a zero-width ellipse"); //System.out.println("It's a zero-width ellipse");
// Ax + By + D = 0 // Ax + By + D = 0
if (Math.abs(D) >= 1e-10) { if (Math.abs(D) >= MINIMUM_RESOLUTION) {
if (Math.abs(A) > Math.abs(B)) { if (Math.abs(A) > Math.abs(B)) {
// Use equation suitable for A != 0 // Use equation suitable for A != 0
// We need to find the endpoints of the zero-width ellipse. // We need to find the endpoints of the zero-width ellipse.
@ -432,7 +432,6 @@ public class Plane extends Vector
double sqrtClause = b * b - 4.0 * a * c; double sqrtClause = b * b - 4.0 * a * c;
if (sqrtClause >= 0.0) { if (sqrtClause >= 0.0) {
if (sqrtClause == 0.0) { if (sqrtClause == 0.0) {
double x0 = -b / (2.0 * a); double x0 = -b / (2.0 * a);
double y0 = (- D - A * x0) / B; double y0 = (- D - A * x0) / B;
@ -560,7 +559,7 @@ public class Plane extends Vector
addPoint(boundsInfo, bounds, x0a, y0a, z0a); addPoint(boundsInfo, bounds, x0a, y0a, z0a);
addPoint(boundsInfo, bounds, x0b, y0b, z0b); addPoint(boundsInfo, bounds, x0b, y0b, z0b);
} }
} }
} else { } else {
//System.out.println("Using the x quadratic"); //System.out.println("Using the x quadratic");
@ -588,7 +587,6 @@ public class Plane extends Vector
//System.out.println("sqrtClause="+sqrtClause); //System.out.println("sqrtClause="+sqrtClause);
if (sqrtClause >= 0.0) { if (sqrtClause >= 0.0) {
if (sqrtClause == 0.0) { if (sqrtClause == 0.0) {
//System.out.println("One solution"); //System.out.println("One solution");
double x0 = -b / (2.0 * a); double x0 = -b / (2.0 * a);
@ -634,13 +632,44 @@ public class Plane extends Vector
/** Determine whether the plane intersects another plane within the /** Determine whether the plane intersects another plane within the
* bounds provided. * bounds provided.
*@param q is the other plane. *@param q is the other plane.
*@param notablePoints are points to look at to disambiguate cases when the two planes are identical.
*@param moreNotablePoints are additional points to look at to disambiguate cases when the two planes are identical.
*@param bounds is one part of the bounds. *@param bounds is one part of the bounds.
*@param moreBounds are more bounds. *@param moreBounds are more bounds.
*@return true if there's an intersection. *@return true if there's an intersection.
*/ */
public boolean intersects(final Plane q, final Membership[] bounds, final Membership... moreBounds) { public boolean intersects(final Plane q, final GeoPoint[] notablePoints, final GeoPoint[] moreNotablePoints, final Membership[] bounds, final Membership... moreBounds) {
// If the two planes are identical, then the math will find no points of intersection.
// So a special case of this is to check for plane equality. But that is not enough, because
// what we really need at that point is to determine whether overlap occurs between the two parts of the intersection
// of plane and circle. That is, are there *any* points on the plane that are within the bounds described?
if (equals(q)) {
// The only way to efficiently figure this out will be to have a list of trial points available to evaluate.
// We look for any point that fulfills all the bounds.
for (GeoPoint p : notablePoints) {
if (meetsAllBounds(p,bounds,moreBounds))
return true;
}
for (GeoPoint p : moreNotablePoints) {
if (meetsAllBounds(p,bounds,moreBounds))
return true;
}
return false;
}
return findIntersections(q,bounds,moreBounds).length > 0; return findIntersections(q,bounds,moreBounds).length > 0;
} }
protected static boolean meetsAllBounds(final GeoPoint p, final Membership[] bounds, final Membership[] moreBounds) {
for (final Membership bound : bounds) {
if (!bound.isWithin(p))
return false;
}
for (final Membership bound : moreBounds) {
if (!bound.isWithin(p))
return false;
}
return true;
}
/** Find a sample point on the intersection between two planes and the unit sphere. /** Find a sample point on the intersection between two planes and the unit sphere.
*/ */

View File

@ -79,9 +79,10 @@ public class SidedPlane extends Plane implements Membership
@Override @Override
public boolean isWithin(Vector point) public boolean isWithin(Vector point)
{ {
double sigNum = Math.signum(evaluate(point)); double evalResult = evaluate(point);
if (sigNum == 0.0) if (Math.abs(evalResult) < MINIMUM_RESOLUTION)
return true; return true;
double sigNum = Math.signum(evalResult);
return sigNum == this.sigNum; return sigNum == this.sigNum;
} }
@ -94,7 +95,10 @@ public class SidedPlane extends Plane implements Membership
@Override @Override
public boolean isWithin(double x, double y, double z) public boolean isWithin(double x, double y, double z)
{ {
double sigNum = Math.signum(this.x * x + this.y * y + this.z * z); double evalResult = this.x * x + this.y * y + this.z * z;
if (Math.abs(evalResult) < MINIMUM_RESOLUTION)
return true;
double sigNum = Math.signum(evalResult);
return sigNum == this.sigNum; return sigNum == this.sigNum;
} }

View File

@ -21,6 +21,10 @@ package org.apache.lucene.spatial.spatial4j.geo3d;
* going through the origin. */ * going through the origin. */
public class Vector public class Vector
{ {
/** Values that are all considered to be essentially zero have a magnitude
* less than this. */
public static final double MINIMUM_RESOLUTION = 1e-15;
public final double x; public final double x;
public final double y; public final double y;
public final double z; public final double z;
@ -56,13 +60,31 @@ public class Vector
*/ */
public Vector normalize() { public Vector normalize() {
double denom = magnitude(); double denom = magnitude();
if (denom < 1e-10) if (denom < MINIMUM_RESOLUTION)
// Degenerate, can't normalize // Degenerate, can't normalize
return null; return null;
double normFactor = 1.0/denom; double normFactor = 1.0/denom;
return new Vector(x*normFactor,y*normFactor,z*normFactor); return new Vector(x*normFactor,y*normFactor,z*normFactor);
} }
/** Evaluate a vector (dot product) and check for "zero".
*@param v is the vector to evaluate.
*@return true if the evaluation yielded zero.
*/
public boolean evaluateIsZero(final Vector v) {
return Math.abs(evaluate(v)) < MINIMUM_RESOLUTION;
}
/** Evaluate a vector (do a dot product) snd check for "zero".
*@param x is the x value of the vector to evaluate.
*@param y is the x value of the vector to evaluate.
*@param z is the x value of the vector to evaluate.
*@return true if the evaluation yielded zero.
*/
public boolean evaluateIsZero(final double x, final double y, final double z) {
return Math.abs(evaluate(x,y,z)) < MINIMUM_RESOLUTION;
}
/** Evaluate a vector (do a dot product). /** Evaluate a vector (do a dot product).
*@param v is the vector to evaluate. *@param v is the vector to evaluate.
*@return the result. *@return the result.

View File

@ -21,12 +21,10 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.carrotsearch.randomizedtesting.annotations.Seed; import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import org.apache.lucene.spatial.composite.CompositeSpatialStrategy; import org.apache.lucene.spatial.composite.CompositeSpatialStrategy;
import org.apache.lucene.spatial.prefix.RandomSpatialOpStrategyTestCase; import org.apache.lucene.spatial.prefix.RandomSpatialOpStrategyTestCase;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy; import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
@ -34,7 +32,6 @@ import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree; import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialOperation; import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy; import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoBBox;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoBBoxFactory; import org.apache.lucene.spatial.spatial4j.geo3d.GeoBBoxFactory;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoCircle; import org.apache.lucene.spatial.spatial4j.geo3d.GeoCircle;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoPath; import org.apache.lucene.spatial.spatial4j.geo3d.GeoPath;
@ -80,42 +77,14 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
} }
@Test @Test
//@Repeat(iterations = 2000) @Repeat(iterations = 2000)
@Seed("B808B88D6F8E285C") //@Seed("B808B88D6F8E285C")
public void testOperations() throws IOException { public void testOperations() throws IOException {
setupStrategy(); setupStrategy();
testOperationRandomShapes(SpatialOperation.Intersects); testOperationRandomShapes(SpatialOperation.Intersects);
} }
@Test
public void testBigCircleFailure() throws IOException {
Rectangle rect = ctx.makeRectangle(-162, 89, -46, 38);
GeoCircle rawShape = new GeoCircle(-9 * DEGREES_TO_RADIANS, 134 * DEGREES_TO_RADIANS, 159 * DEGREES_TO_RADIANS);
Shape shape = new Geo3dShape(rawShape, ctx);
assertTrue(rect.relate(shape).intersects() == false); //DWS: unsure if this is correct or not but passes
//since they don't intersect, then the following cell rect can't be WITHIN the circle
final Rectangle cellRect = ctx.makeRectangle(-11.25, 0, 0, 5.625);
assert cellRect.relate(rect).intersects();
assertTrue(cellRect.relate(shape) != SpatialRelation.WITHIN);
}
@Test
public void testWideRectFailure() throws IOException {
Rectangle rect = ctx.makeRectangle(-29, 9, 16, 25);
final GeoBBox geoBBox = GeoBBoxFactory.makeGeoBBox(
74 * DEGREES_TO_RADIANS, -31 * DEGREES_TO_RADIANS, -29 * DEGREES_TO_RADIANS, -45 * DEGREES_TO_RADIANS);
Shape shape = new Geo3dShape(geoBBox, ctx);
//Rect(minX=-22.5,maxX=-11.25,minY=11.25,maxY=16.875)
//since they don't intersect, then the following cell rect can't be WITHIN the geo3d shape
final Rectangle cellRect = ctx.makeRectangle(-22.5, -11.25, 11.25, 16.875);
assert cellRect.relate(rect).intersects();
assertTrue(rect.relate(shape).intersects() == false);
assertTrue(cellRect.relate(shape) != SpatialRelation.WITHIN);
// setupStrategy();
// testOperation(rect, SpatialOperation.Intersects, shape, false);
}
private Shape makeTriangle(double x1, double y1, double x2, double y2, double x3, double y3) { private Shape makeTriangle(double x1, double y1, double x2, double y2, double x3, double y3) {
final List<GeoPoint> geoPoints = new ArrayList<>(); final List<GeoPoint> geoPoints = new ArrayList<>();
geoPoints.add(new GeoPoint(y1 * DEGREES_TO_RADIANS, x1 * DEGREES_TO_RADIANS)); geoPoints.add(new GeoPoint(y1 * DEGREES_TO_RADIANS, x1 * DEGREES_TO_RADIANS));
@ -159,7 +128,7 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
case 1: { case 1: {
// Circles // Circles
while (true) { while (true) {
final int circleRadius = random().nextInt(180); final int circleRadius = random().nextInt(179) + 1;
final Point point = randomPoint(); final Point point = randomPoint();
try { try {
final GeoShape shape = new GeoCircle(point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS, final GeoShape shape = new GeoCircle(point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS,
@ -188,6 +157,7 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
lrhcPoint.getY() * DEGREES_TO_RADIANS, lrhcPoint.getY() * DEGREES_TO_RADIANS,
ulhcPoint.getX() * DEGREES_TO_RADIANS, ulhcPoint.getX() * DEGREES_TO_RADIANS,
lrhcPoint.getX() * DEGREES_TO_RADIANS); lrhcPoint.getX() * DEGREES_TO_RADIANS);
//System.err.println("Trial rectangle shape: "+shape);
return new Geo3dShape(shape, ctx); return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where // This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where