LUCENE-6196: Geo3d API, 3d planar geometry for surface of a sphere.

This merge commit renames a couple test utilities that appeared to be tests but weren't.

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1678005 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
David Wayne Smiley 2015-05-06 14:24:44 +00:00
commit f760a51f80
57 changed files with 10207 additions and 2 deletions

View File

@ -92,6 +92,12 @@ New Features
expression helper VariableContext
(Jack Conradson via Ryan Ernst)
* LUCENE-6196: New Spatial "Geo3d" API with partial Spatial4j integration.
It is a set of shapes implemented using 3D planar geometry for calculating
spatial relations on the surface of a sphere. Shapes include Point, BBox,
Circle, Path (buffered line string), and Polygon.
(Karl Wright via David Smiley)
Optimizations
* LUCENE-6379: IndexWriter.deleteDocuments(Query...) now detects if

View File

@ -0,0 +1,168 @@
package org.apache.lucene.spatial.spatial4j;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.shape.impl.RectangleImpl;
import org.apache.lucene.spatial.spatial4j.geo3d.Bounds;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoArea;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoAreaFactory;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoPoint;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoShape;
/**
* A 3D planar geometry based Spatial4j Shape implementation.
*
* @lucene.experimental
*/
public class Geo3dShape implements Shape {
public final SpatialContext ctx;
public final GeoShape shape;
private Rectangle boundingBox = null;
public final static double RADIANS_PER_DEGREE = Math.PI / 180.0;
public final static double DEGREES_PER_RADIAN = 1.0 / RADIANS_PER_DEGREE;
public Geo3dShape(GeoShape shape, SpatialContext ctx) {
if (!ctx.isGeo()) {
throw new IllegalArgumentException("SpatialContext.isGeo() must be true");
}
this.ctx = ctx;
this.shape = shape;
}
@Override
public SpatialRelation relate(Shape other) {
if (other instanceof Rectangle)
return relate((Rectangle)other);
else if (other instanceof Point)
return relate((Point)other);
else
throw new RuntimeException("Unimplemented shape relationship determination: " + other.getClass());
}
protected SpatialRelation relate(Rectangle r) {
// Construct the right kind of GeoArea first
GeoArea geoArea = GeoAreaFactory.makeGeoArea(r.getMaxY() * RADIANS_PER_DEGREE,
r.getMinY() * RADIANS_PER_DEGREE,
r.getMinX() * RADIANS_PER_DEGREE,
r.getMaxX() * RADIANS_PER_DEGREE);
int relationship = geoArea.getRelationship(shape);
if (relationship == GeoArea.WITHIN)
return SpatialRelation.WITHIN;
else if (relationship == GeoArea.CONTAINS)
return SpatialRelation.CONTAINS;
else if (relationship == GeoArea.OVERLAPS)
return SpatialRelation.INTERSECTS;
else if (relationship == GeoArea.DISJOINT)
return SpatialRelation.DISJOINT;
else
throw new RuntimeException("Unknown relationship returned: "+relationship);
}
protected SpatialRelation relate(Point p) {
// Create a GeoPoint
GeoPoint point = new GeoPoint(p.getY()*RADIANS_PER_DEGREE, p.getX()*RADIANS_PER_DEGREE);
if (shape.isWithin(point)) {
// Point within shape
return SpatialRelation.CONTAINS;
}
return SpatialRelation.DISJOINT;
}
protected final double ROUNDOFF_ADJUSTMENT = 0.01;
@Override
public Rectangle getBoundingBox() {
if (boundingBox == null) {
Bounds bounds = shape.getBounds(null);
double leftLon;
double rightLon;
if (bounds.checkNoLongitudeBound()) {
leftLon = -180.0;
rightLon = 180.0;
} else {
leftLon = bounds.getLeftLongitude().doubleValue() * DEGREES_PER_RADIAN;
rightLon = bounds.getRightLongitude().doubleValue() * DEGREES_PER_RADIAN;
}
double minLat;
if (bounds.checkNoBottomLatitudeBound()) {
minLat = -90.0;
} else {
minLat = bounds.getMinLatitude().doubleValue() * DEGREES_PER_RADIAN;
}
double maxLat;
if (bounds.checkNoTopLatitudeBound()) {
maxLat = 90.0;
} else {
maxLat = bounds.getMaxLatitude().doubleValue() * DEGREES_PER_RADIAN;
}
boundingBox = new RectangleImpl(leftLon, rightLon, minLat, maxLat, ctx).getBuffered(ROUNDOFF_ADJUSTMENT, ctx);
}
return boundingBox;
}
@Override
public boolean hasArea() {
return true;
}
@Override
public double getArea(SpatialContext ctx) {
throw new RuntimeException("Unimplemented");
}
@Override
public Point getCenter() {
throw new RuntimeException("Unimplemented");
}
@Override
public Shape getBuffered(double distance, SpatialContext ctx) {
return this;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public String toString() {
return "Geo3dShape{" + shape + '}';
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Geo3dShape))
return false;
Geo3dShape tr = (Geo3dShape)other;
return tr.shape.equals(shape);
}
@Override
public int hashCode() {
return shape.hashCode();
}
}

View File

@ -0,0 +1,306 @@
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.
*/
/**
* An object for accumulating bounds information.
* The bounds object is initially empty. Bounding points
* are then applied by supplying (x,y,z) tuples. It is also
* possible to indicate the following edge cases:
* (1) No longitude bound possible
* (2) No upper latitude bound possible
* (3) No lower latitude bound possible
* When any of these have been applied, further application of
* points cannot override that decision.
*
* @lucene.experimental
*/
public class Bounds {
protected boolean noLongitudeBound = false;
protected boolean noTopLatitudeBound = false;
protected boolean noBottomLatitudeBound = false;
protected Double minLatitude = null;
protected Double maxLatitude = null;
// For longitude bounds, this class needs to worry about keeping track of the distinction
// between left-side bounds and right-side bounds. Points are always submitted in pairs
// which have a maximum longitude separation of Math.PI. It's therefore always possible
// to determine which point represents a left bound, and which point represents a right
// bound.
//
// The next problem is how to compare two of the same kind of bound, e.g. two left bounds.
// We need to keep track of the leftmost longitude of the shape, but since this is a circle,
// this is arbitrary. What we could try to do instead would be to find a pair of (left,right) bounds such
// that:
// (1) all other bounds are within, and
// (2) the left minus right distance is minimized
// Unfortunately, there are still shapes that cannot be summarized in this way correctly.
// For example. consider a spiral that entirely circles the globe; we might arbitrarily choose
// lat/lon bounds that do not in fact circle the globe.
//
// One way to handle the longitude issue correctly is therefore to stipulate that we
// walk the bounds of the shape in some kind of connected order. Each point or circle is therefore
// added in a sequence. We also need an interior point to make sure we have the right
// choice of longitude bounds. But even with this, we still can't always choose whether the actual shape
// goes right or left.
//
// We can make the specification truly general by submitting the following in order:
// addSide(PlaneSide side, Membership... constraints)
// ...
// This is unambiguous, but I still can't see yet how this would help compute the bounds. The plane
// solution would in general seem to boil down to the same logic that relies on points along the path
// to define the shape boundaries. I guess the one thing that you do know for a bounded edge is that
// the endpoints are actually connected. But it is not clear whether relationship helps in any way.
//
// In any case, if we specify shapes by a sequence of planes, we should stipulate that multiple sequences
// are allowed, provided they progressively tile an area of the sphere that is connected and sequential.
// For example, paths do alternating rectangles and circles, in sequence. Each sequence member is
// described by a sequence of planes. I think it would also be reasonable to insist that the first segment
// of a shape overlap or adjoin the previous shape.
//
// Here's a way to think about it that might help: Traversing every edge should grow the longitude bounds
// in the direction of the traversal. So if the traversal is always known to be less than PI in total longitude
// angle, then it is possible to use the endpoints to determine the unambiguous extension of the envelope.
// For example, say you are currently at longitude -0.5. The next point is at longitude PI-0.1. You could say
// that the difference in longitude going one way around would be beter than the distance the other way
// around, and therefore the longitude envelope should be extended accordingly. But in practice, when an
// edge goes near a pole and may be inclined as well, the longer longitude change might be the right path, even
// if the arc length is short. So this too doesn't work.
//
// Given we have a hard time making an exact match, here's the current proposal. The proposal is a
// heuristic, based on the idea that most areas are small compared to the circumference of the globe.
// We keep track of the last point we saw, and take each point as it arrives, and compute its longitude.
// Then, we have a choice as to which way to expand the envelope: we can expand by going to the left or
// to the right. We choose the direction with the least longitude difference. (If we aren't sure,
// and can recognize that, we can set "unconstrained in longitude".)
protected Double leftLongitude = null;
protected Double rightLongitude = null;
public Bounds() {
}
public Double getMaxLatitude() {
return maxLatitude;
}
public Double getMinLatitude() {
return minLatitude;
}
public Double getLeftLongitude() {
return leftLongitude;
}
public Double getRightLongitude() {
return rightLongitude;
}
public boolean checkNoLongitudeBound() {
return noLongitudeBound;
}
public boolean checkNoTopLatitudeBound() {
return noTopLatitudeBound;
}
public boolean checkNoBottomLatitudeBound() {
return noBottomLatitudeBound;
}
public Bounds addHorizontalCircle(double z) {
if (!noTopLatitudeBound || !noBottomLatitudeBound) {
// Compute a latitude value
double latitude = Math.asin(z);
addLatitudeBound(latitude);
}
return this;
}
public Bounds addLatitudeZone(double latitude) {
if (!noTopLatitudeBound || !noBottomLatitudeBound) {
addLatitudeBound(latitude);
}
return this;
}
public Bounds addLongitudeSlice(double newLeftLongitude, double newRightLongitude) {
if (!noLongitudeBound) {
addLongitudeBound(newLeftLongitude, newRightLongitude);
}
return this;
}
protected void addLatitudeBound(double latitude) {
if (!noTopLatitudeBound && (maxLatitude == null || latitude > maxLatitude))
maxLatitude = latitude;
if (!noBottomLatitudeBound && (minLatitude == null || latitude < minLatitude))
minLatitude = latitude;
}
protected void addLongitudeBound(double newLeftLongitude, double newRightLongitude) {
if (leftLongitude == null && rightLongitude == null) {
leftLongitude = newLeftLongitude;
rightLongitude = newRightLongitude;
} else {
// Map the current range to something monotonically increasing
double currentLeftLongitude = leftLongitude;
double currentRightLongitude = rightLongitude;
if (currentRightLongitude < currentLeftLongitude)
currentRightLongitude += 2.0 * Math.PI;
double adjustedLeftLongitude = newLeftLongitude;
double adjustedRightLongitude = newRightLongitude;
if (adjustedRightLongitude < adjustedLeftLongitude)
adjustedRightLongitude += 2.0 * Math.PI;
// Compare to see what the relationship is
if (currentLeftLongitude <= adjustedLeftLongitude && currentRightLongitude >= adjustedRightLongitude) {
// No adjustment needed.
} else if (currentLeftLongitude >= adjustedLeftLongitude && currentRightLongitude <= adjustedRightLongitude) {
// New longitude entirely contains old one
leftLongitude = newLeftLongitude;
rightLongitude = newRightLongitude;
} else {
if (currentLeftLongitude > adjustedLeftLongitude) {
// New left longitude needed
leftLongitude = newLeftLongitude;
}
if (currentRightLongitude < adjustedRightLongitude) {
// New right longitude needed
rightLongitude = newRightLongitude;
}
}
}
double testRightLongitude = rightLongitude;
if (testRightLongitude < leftLongitude)
testRightLongitude += Math.PI * 2.0;
// If the bound exceeds 180 degrees, we know we could have screwed up.
if (testRightLongitude - leftLongitude >= Math.PI) {
noLongitudeBound = true;
leftLongitude = null;
rightLongitude = null;
}
}
protected void addLongitudeBound(double longitude) {
// If this point is within the current bounds, we're done; otherwise
// expand one side or the other.
if (leftLongitude == null && rightLongitude == null) {
leftLongitude = longitude;
rightLongitude = longitude;
} else {
// Compute whether we're to the right of the left value. But the left value may be greater than
// the right value.
double currentLeftLongitude = leftLongitude;
double currentRightLongitude = rightLongitude;
if (currentRightLongitude < currentLeftLongitude)
currentRightLongitude += 2.0 * Math.PI;
// We have a range to look at that's going in the right way.
// Now, do the same trick with the computed longitude.
if (longitude < currentLeftLongitude)
longitude += 2.0 * Math.PI;
if (longitude < currentLeftLongitude || longitude > currentRightLongitude) {
// Outside of current bounds. Consider carefully how we'll expand.
double leftExtensionAmt;
double rightExtensionAmt;
if (longitude < currentLeftLongitude) {
leftExtensionAmt = currentLeftLongitude - longitude;
} else {
leftExtensionAmt = currentLeftLongitude + 2.0 * Math.PI - longitude;
}
if (longitude > currentRightLongitude) {
rightExtensionAmt = longitude - currentRightLongitude;
} else {
rightExtensionAmt = longitude + 2.0 * Math.PI - currentRightLongitude;
}
if (leftExtensionAmt < rightExtensionAmt) {
currentLeftLongitude = leftLongitude - leftExtensionAmt;
while (currentLeftLongitude <= -Math.PI) {
currentLeftLongitude += 2.0 * Math.PI;
}
leftLongitude = currentLeftLongitude;
} else {
currentRightLongitude = rightLongitude + rightExtensionAmt;
while (currentRightLongitude > Math.PI) {
currentRightLongitude -= 2.0 * Math.PI;
}
rightLongitude = currentRightLongitude;
}
}
}
double testRightLongitude = rightLongitude;
if (testRightLongitude < leftLongitude)
testRightLongitude += Math.PI * 2.0;
if (testRightLongitude - leftLongitude >= Math.PI) {
noLongitudeBound = true;
leftLongitude = null;
rightLongitude = null;
}
}
public Bounds addPoint(Vector v) {
return addPoint(v.x, v.y, v.z);
}
public Bounds addPoint(double x, double y, double z) {
if (!noLongitudeBound) {
// Get a longitude value
double longitude = Math.atan2(y, x);
//System.err.println(" add longitude bound at "+longitude * 180.0/Math.PI);
addLongitudeBound(longitude);
}
if (!noTopLatitudeBound || !noBottomLatitudeBound) {
// Compute a latitude value
double latitude = Math.asin(z);
addLatitudeBound(latitude);
}
return this;
}
public Bounds addPoint(double latitude, double longitude) {
if (!noLongitudeBound) {
// Get a longitude value
addLongitudeBound(longitude);
}
if (!noTopLatitudeBound || !noBottomLatitudeBound) {
// Compute a latitude value
addLatitudeBound(latitude);
}
return this;
}
public Bounds noLongitudeBound() {
noLongitudeBound = true;
leftLongitude = null;
rightLongitude = null;
return this;
}
public Bounds noTopLatitudeBound() {
noTopLatitudeBound = true;
maxLatitude = null;
return this;
}
public Bounds noBottomLatitudeBound() {
noBottomLatitudeBound = true;
minLatitude = null;
return this;
}
}

View File

@ -0,0 +1,55 @@
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.
*/
/**
* A GeoArea represents a standard 2-D breakdown of a part of sphere. It can
* be bounded in latitude, or bounded in both latitude and longitude, or not
* bounded at all. The purpose of the interface is to describe bounding shapes used for
* computation of geo hashes.
*
* @lucene.experimental
*/
public interface GeoArea extends Membership {
// Since we don't know what each GeoArea's constraints are,
// we put the onus on the GeoArea implementation to do the right thing.
// This will, of course, rely heavily on methods provided by
// the underlying GeoShape class.
public static final int CONTAINS = 0;
public static final int WITHIN = 1;
public static final int OVERLAPS = 2;
public static final int DISJOINT = 3;
/**
* Find the spatial relationship between a shape and the current geo area.
* Note: return value is how the GeoShape relates to the GeoArea, not the
* other way around. For example, if this GeoArea is entirely within the
* shape, then CONTAINS should be returned. If the shape is entirely enclosed
* by this GeoArea, then WITHIN should be returned.
* Note well: When a shape consists of multiple independent overlapping subshapes,
* it is sometimes impossible to determine the distinction between
* OVERLAPS and CONTAINS. In that case, OVERLAPS may be returned even
* though the proper result would in fact be CONTAINS. Code accordingly.
*
* @param shape is the shape to consider.
* @return the relationship, from the perspective of the shape.
*/
public int getRelationship(GeoShape shape);
}

View File

@ -0,0 +1,42 @@
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.
*/
/**
* Factory for {@link org.apache.lucene.spatial.spatial4j.geo3d.GeoArea}.
*
* @lucene.experimental
*/
public class GeoAreaFactory {
private GeoAreaFactory() {
}
/**
* Create a GeoArea of the right kind given the specified bounds.
*
* @param topLat is the top latitude
* @param bottomLat is the bottom latitude
* @param leftLon is the left longitude
* @param rightLon is the right longitude
* @return a GeoArea corresponding to what was specified.
*/
public static GeoArea makeGeoArea(double topLat, double bottomLat, double leftLon, double rightLon) {
return GeoBBoxFactory.makeGeoBBox(topLat, bottomLat, leftLon, rightLon);
}
}

View File

@ -0,0 +1,37 @@
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.
*/
/**
* All bounding box shapes have this interface in common.
* This describes methods that bounding boxes have above and beyond
* GeoMembershipShape's.
*
* @lucene.experimental
*/
public interface GeoBBox extends GeoMembershipShape, GeoSizeable, GeoArea {
/**
* Expand box by specified angle.
*
* @param angle is the angle amount to expand the GeoBBox by.
* @return a new GeoBBox.
*/
public GeoBBox expand(double angle);
}

View File

@ -0,0 +1,58 @@
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.
*/
/**
* All bounding box shapes can derive from this base class, which furnishes
* some common code
*
* @lucene.internal
*/
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
public abstract boolean isWithin(final Vector point);
protected final static int ALL_INSIDE = 0;
protected final static int SOME_INSIDE = 1;
protected final static int NONE_INSIDE = 2;
protected int isShapeInsideBBox(final GeoShape path) {
final GeoPoint[] pathPoints = path.getEdgePoints();
boolean foundOutside = false;
boolean foundInside = false;
for (GeoPoint p : pathPoints) {
if (isWithin(p)) {
foundInside = true;
} else {
foundOutside = true;
}
}
if (!foundInside && !foundOutside)
return NONE_INSIDE;
if (foundInside && !foundOutside)
return ALL_INSIDE;
if (foundOutside && !foundInside)
return NONE_INSIDE;
return SOME_INSIDE;
}
}

View File

@ -0,0 +1,111 @@
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.
*/
/**
* Factory for {@link org.apache.lucene.spatial.spatial4j.geo3d.GeoBBox}.
*
* @lucene.experimental
*/
public class GeoBBoxFactory {
private GeoBBoxFactory() {
}
/**
* Create a geobbox of the right kind given the specified bounds.
*
* @param topLat is the top latitude
* @param bottomLat is the bottom latitude
* @param leftLon is the left longitude
* @param rightLon is the right longitude
* @return a GeoBBox corresponding to what was specified.
*/
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)
topLat = Math.PI * 0.5;
if (bottomLat < -Math.PI * 0.5)
bottomLat = -Math.PI * 0.5;
if (leftLon < -Math.PI)
leftLon = -Math.PI;
if (rightLon > Math.PI)
rightLon = Math.PI;
if (Math.abs(leftLon + Math.PI) < Vector.MINIMUM_RESOLUTION && Math.abs(rightLon - Math.PI) < Vector.MINIMUM_RESOLUTION) {
if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION && Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION)
return new GeoWorld();
if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_RESOLUTION) {
if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION || Math.abs(topLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION)
return new GeoDegeneratePoint(topLat, 0.0);
return new GeoDegenerateLatitudeZone(topLat);
}
if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION)
return new GeoNorthLatitudeZone(bottomLat);
else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION)
return new GeoSouthLatitudeZone(topLat);
return new GeoLatitudeZone(topLat, bottomLat);
}
//System.err.println(" not latitude zone");
double extent = rightLon - leftLon;
if (extent < 0.0)
extent += Math.PI * 2.0;
if (topLat == Math.PI * 0.5 && bottomLat == -Math.PI * 0.5) {
if (Math.abs(leftLon - rightLon) < Vector.MINIMUM_RESOLUTION)
return new GeoDegenerateLongitudeSlice(leftLon);
if (extent >= Math.PI)
return new GeoWideLongitudeSlice(leftLon, rightLon);
return new GeoLongitudeSlice(leftLon, rightLon);
}
//System.err.println(" not longitude slice");
if (Math.abs(leftLon - rightLon) < Vector.MINIMUM_RESOLUTION) {
if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_RESOLUTION)
return new GeoDegeneratePoint(topLat, leftLon);
return new GeoDegenerateVerticalLine(topLat, bottomLat, leftLon);
}
//System.err.println(" not vertical line");
if (extent >= Math.PI) {
if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_RESOLUTION) {
//System.err.println(" wide degenerate line");
return new GeoWideDegenerateHorizontalLine(topLat, leftLon, rightLon);
}
if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION) {
return new GeoWideNorthRectangle(bottomLat, leftLon, rightLon);
} else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION) {
return new GeoWideSouthRectangle(topLat, leftLon, rightLon);
}
//System.err.println(" wide rect");
return new GeoWideRectangle(topLat, bottomLat, leftLon, rightLon);
}
if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_RESOLUTION) {
if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION || Math.abs(topLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION) {
return new GeoDegeneratePoint(topLat, 0.0);
}
//System.err.println(" horizontal line");
return new GeoDegenerateHorizontalLine(topLat, leftLon, rightLon);
}
if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION) {
return new GeoNorthRectangle(bottomLat, leftLon, rightLon);
} else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION) {
return new GeoSouthRectangle(topLat, leftLon, rightLon);
}
//System.err.println(" rectangle");
return new GeoRectangle(topLat, bottomLat, leftLon, rightLon);
}
}

View File

@ -0,0 +1,94 @@
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.
*/
/**
* Base extended shape object.
*
* @lucene.internal
*/
public abstract class GeoBaseExtendedShape implements GeoShape {
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);
public GeoBaseExtendedShape() {
}
/**
* Check if a point is within this shape.
*
* @param point is the point to check.
* @return true if the point is within this shape
*/
@Override
public abstract boolean isWithin(final Vector point);
/**
* Check if a point is within this shape.
*
* @param x is x coordinate of point to check.
* @param y is y coordinate of point to check.
* @param z is z coordinate of point to check.
* @return true if the point is within this shape
*/
@Override
public abstract boolean isWithin(final double x, final double y, final double z);
/**
* Return a sample point that is on the edge of the shape.
*
* @return a number of edge points, one for each disconnected edge.
*/
@Override
public abstract GeoPoint[] getEdgePoints();
/**
* Assess whether a plane, within the provided bounds, intersects
* with the shape.
*
* @param plane is the plane to assess for intersection with the shape's edges or
* bounding curves.
* @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).
* @return true if there's such an intersection, false if not.
*/
@Override
public abstract boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... 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();
if (isWithin(NORTH_POLE)) {
bounds.noTopLatitudeBound().noLongitudeBound();
}
if (isWithin(SOUTH_POLE)) {
bounds.noBottomLatitudeBound().noLongitudeBound();
}
return bounds;
}
}

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.
*/
/**
* Circular area with a center and radius.
*
* @lucene.experimental
*/
public class GeoCircle extends GeoBaseExtendedShape implements GeoDistanceShape, GeoSizeable {
public final GeoPoint center;
public final double cutoffAngle;
public final double cutoffNormalDistance;
public final double cutoffLinearDistance;
public final SidedPlane circlePlane;
public final GeoPoint[] edgePoints;
public static final GeoPoint[] circlePoints = new GeoPoint[0];
public GeoCircle(final double lat, final double lon, final double cutoffAngle) {
super();
if (lat < -Math.PI * 0.5 || lat > Math.PI * 0.5)
throw new IllegalArgumentException("Latitude out of bounds");
if (lon < -Math.PI || lon > Math.PI)
throw new IllegalArgumentException("Longitude out of bounds");
if (cutoffAngle <= 0.0 || cutoffAngle > Math.PI)
throw new IllegalArgumentException("Cutoff angle out of bounds");
final double sinAngle = Math.sin(cutoffAngle);
final double cosAngle = Math.cos(cutoffAngle);
this.center = new GeoPoint(lat, lon);
this.cutoffNormalDistance = sinAngle;
// Need the chord distance. This is just the chord distance: sqrt((1 - cos(angle))^2 + (sin(angle))^2).
final double xDiff = 1.0 - cosAngle;
this.cutoffLinearDistance = Math.sqrt(xDiff * xDiff + sinAngle * sinAngle);
this.cutoffAngle = cutoffAngle;
this.circlePlane = new SidedPlane(center, center, -cosAngle);
// Compute a point on the circle boundary.
if (cutoffAngle == Math.PI)
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
public double getRadius() {
return cutoffAngle;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return center;
}
/**
* Compute an estimate of "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*/
@Override
public double computeNormalDistance(final GeoPoint point) {
double normalDistance = this.center.normalDistance(point);
if (normalDistance > cutoffNormalDistance)
return Double.MAX_VALUE;
return normalDistance;
}
/**
* Compute an estimate of "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*/
@Override
public double computeNormalDistance(final double x, final double y, final double z) {
double normalDistance = this.center.normalDistance(x, y, z);
if (normalDistance > cutoffNormalDistance)
return Double.MAX_VALUE;
return normalDistance;
}
/**
* Compute a squared estimate of the "distance" to the
* GeoPoint. Double.MAX_VALUE indicates a point outside of the
* shape.
*/
@Override
public double computeSquaredNormalDistance(final GeoPoint point) {
double normalDistanceSquared = this.center.normalDistanceSquared(point);
if (normalDistanceSquared > cutoffNormalDistance * cutoffNormalDistance)
return Double.MAX_VALUE;
return normalDistanceSquared;
}
/**
* Compute a squared estimate of the "distance" to the
* GeoPoint. Double.MAX_VALUE indicates a point outside of the
* shape.
*/
@Override
public double computeSquaredNormalDistance(final double x, final double y, final double z) {
double normalDistanceSquared = this.center.normalDistanceSquared(x, y, z);
if (normalDistanceSquared > cutoffNormalDistance * cutoffNormalDistance)
return Double.MAX_VALUE;
return normalDistanceSquared;
}
/**
* Compute a linear distance to the vector.
* return Double.MAX_VALUE for points outside the shape.
*/
@Override
public double computeLinearDistance(final GeoPoint point) {
double linearDistance = this.center.linearDistance(point);
if (linearDistance > cutoffLinearDistance)
return Double.MAX_VALUE;
return linearDistance;
}
/**
* Compute a linear distance to the vector.
* return Double.MAX_VALUE for points outside the shape.
*/
@Override
public double computeLinearDistance(final double x, final double y, final double z) {
double linearDistance = this.center.linearDistance(x, y, z);
if (linearDistance > cutoffLinearDistance)
return Double.MAX_VALUE;
return linearDistance;
}
/**
* Compute a squared linear distance to the vector.
*/
@Override
public double computeSquaredLinearDistance(final GeoPoint point) {
double linearDistanceSquared = this.center.linearDistanceSquared(point);
if (linearDistanceSquared > cutoffLinearDistance * cutoffLinearDistance)
return Double.MAX_VALUE;
return linearDistanceSquared;
}
/**
* Compute a squared linear distance to the vector.
*/
@Override
public double computeSquaredLinearDistance(final double x, final double y, final double z) {
double linearDistanceSquared = this.center.linearDistanceSquared(x, y, z);
if (linearDistanceSquared > cutoffLinearDistance * cutoffLinearDistance)
return Double.MAX_VALUE;
return linearDistanceSquared;
}
/**
* Compute a true, accurate, great-circle distance.
* Double.MAX_VALUE indicates a point is outside of the shape.
*/
@Override
public double computeArcDistance(final GeoPoint point) {
double dist = this.center.arcDistance(point);
if (dist > cutoffAngle)
return Double.MAX_VALUE;
return dist;
}
@Override
public boolean isWithin(final Vector point) {
// Fastest way of determining membership
return circlePlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
// Fastest way of determining membership
return circlePlane.isWithin(x, y, z);
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
return circlePlane.intersects(p, notablePoints, circlePoints, 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) {
bounds = super.getBounds(bounds);
bounds.addPoint(center);
circlePlane.recordBounds(bounds);
return bounds;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoCircle))
return false;
GeoCircle other = (GeoCircle) o;
return other.center.equals(center) && other.cutoffAngle == cutoffAngle;
}
@Override
public int hashCode() {
int result;
long temp;
result = center.hashCode();
temp = Double.doubleToLongBits(cutoffAngle);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "GeoCircle: {center=" + center + ", radius=" + cutoffAngle + "(" + cutoffAngle * 180.0 / Math.PI + ")}";
}
}

View File

@ -0,0 +1,120 @@
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.
*/
import java.util.ArrayList;
import java.util.List;
/**
* GeoComposite is a set of GeoMembershipShape's, treated as a unit.
*
* @lucene.experimental
*/
public class GeoCompositeMembershipShape implements GeoMembershipShape {
protected final List<GeoMembershipShape> shapes = new ArrayList<GeoMembershipShape>();
public GeoCompositeMembershipShape() {
}
/**
* Add a shape to the composite.
*/
public void addShape(final GeoMembershipShape shape) {
shapes.add(shape);
}
@Override
public boolean isWithin(final Vector point) {
//System.err.println("Checking whether point "+point+" is within Composite");
for (GeoMembershipShape shape : shapes) {
if (shape.isWithin(point)) {
//System.err.println(" Point is within "+shape);
return true;
}
}
return false;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
for (GeoMembershipShape shape : shapes) {
if (shape.isWithin(x, y, z))
return true;
}
return false;
}
@Override
public GeoPoint[] getEdgePoints() {
return shapes.get(0).getEdgePoints();
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
for (GeoMembershipShape shape : shapes) {
if (shape.intersects(p, notablePoints, bounds))
return true;
}
return false;
}
/**
* 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();
for (GeoMembershipShape shape : shapes) {
bounds = shape.getBounds(bounds);
}
return bounds;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoCompositeMembershipShape))
return false;
GeoCompositeMembershipShape other = (GeoCompositeMembershipShape) o;
if (other.shapes.size() != shapes.size())
return false;
for (int i = 0; i < shapes.size(); i++) {
if (!other.shapes.get(i).equals(shapes.get(i)))
return false;
}
return true;
}
@Override
public int hashCode() {
return shapes.hashCode();//TODO cache
}
@Override
public String toString() {
return "GeoCompositeMembershipShape: {" + shapes + '}';
}
}

View File

@ -0,0 +1,273 @@
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.
*/
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
/**
* GeoConvexPolygon objects are generic building blocks of more complex structures.
* The only restrictions on these objects are: (1) they must be convex; (2) they must have
* a maximum extent no larger than PI. Violating either one of these limits will
* cause the logic to fail.
*
* @lucene.experimental
*/
public class GeoConvexPolygon extends GeoBaseExtendedShape implements GeoMembershipShape {
protected final List<GeoPoint> points;
protected final BitSet isInternalEdges;
protected SidedPlane[] edges = null;
protected boolean[] internalEdges = null;
protected GeoPoint[][] notableEdgePoints = null;
protected GeoPoint[] edgePoints = null;
protected double fullDistance = 0.0;
/**
* Create a convex polygon from a list of points. The first point must be on the
* external edge.
*/
public GeoConvexPolygon(final List<GeoPoint> pointList) {
this.points = pointList;
this.isInternalEdges = null;
donePoints(false);
}
/**
* Create a convex polygon from a list of points, keeping track of which boundaries
* are internal. This is used when creating a polygon as a building block for another shape.
*/
public GeoConvexPolygon(final List<GeoPoint> pointList, final BitSet internalEdgeFlags, final boolean returnEdgeInternal) {
this.points = pointList;
this.isInternalEdges = internalEdgeFlags;
donePoints(returnEdgeInternal);
}
/**
* Create a convex polygon, with a starting latitude and longitude.
* Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI}
*/
public GeoConvexPolygon(final double startLatitude, final double startLongitude) {
points = new ArrayList<GeoPoint>();
isInternalEdges = new BitSet();
// Argument checking
if (startLatitude > Math.PI * 0.5 || startLatitude < -Math.PI * 0.5)
throw new IllegalArgumentException("Latitude out of range");
if (startLongitude < -Math.PI || startLongitude > Math.PI)
throw new IllegalArgumentException("Longitude out of range");
final GeoPoint p = new GeoPoint(startLatitude, startLongitude);
points.add(p);
}
/**
* Add a point to the polygon.
* Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI}
*
* @param latitude is the latitude of the next point.
* @param longitude is the longitude of the next point.
* @param isInternalEdge is true if the edge just added should be considered "internal", and not
* intersected as part of the intersects() operation.
*/
public void addPoint(final double latitude, final double longitude, final boolean isInternalEdge) {
// Argument checking
if (latitude > Math.PI * 0.5 || latitude < -Math.PI * 0.5)
throw new IllegalArgumentException("Latitude out of range");
if (longitude < -Math.PI || longitude > Math.PI)
throw new IllegalArgumentException("Longitude out of range");
final GeoPoint p = new GeoPoint(latitude, longitude);
isInternalEdges.set(points.size(), isInternalEdge);
points.add(p);
}
/**
* Finish the polygon, by connecting the last added point with the starting point.
*/
public void donePoints(final boolean isInternalReturnEdge) {
// If fewer than 3 points, can't do it.
if (points.size() < 3)
throw new IllegalArgumentException("Polygon needs at least three points.");
// Time to construct the planes. If the polygon is truly convex, then any adjacent point
// to a segment can provide an interior measurement.
edges = new SidedPlane[points.size()];
notableEdgePoints = new GeoPoint[points.size()][];
internalEdges = new boolean[points.size()];
for (int i = 0; i < points.size(); i++) {
final GeoPoint start = points.get(i);
final boolean isInternalEdge = (isInternalEdges != null ? (i == isInternalEdges.size() ? isInternalReturnEdge : isInternalEdges.get(i)) : false);
final GeoPoint end = points.get(legalIndex(i + 1));
final double distance = start.arcDistance(end);
if (distance > fullDistance)
fullDistance = distance;
final GeoPoint check = points.get(legalIndex(i + 2));
final SidedPlane sp = new SidedPlane(check, start, end);
//System.out.println("Created edge "+sp+" using start="+start+" end="+end+" check="+check);
edges[i] = sp;
notableEdgePoints[i] = new GeoPoint[]{start, end};
internalEdges[i] = isInternalEdge;
}
createCenterPoint();
}
protected void createCenterPoint() {
// In order to naively confirm that the polygon is convex, I would need to
// check every edge, and verify that every point (other than the edge endpoints)
// is within the edge's sided plane. This is an order n^2 operation. That's still
// not wrong, though, because everything else about polygons has a similar cost.
for (int edgeIndex = 0; edgeIndex < edges.length; edgeIndex++) {
final SidedPlane edge = edges[edgeIndex];
for (int pointIndex = 0; pointIndex < points.size(); pointIndex++) {
if (pointIndex != edgeIndex && pointIndex != legalIndex(edgeIndex + 1)) {
if (!edge.isWithin(points.get(pointIndex)))
throw new IllegalArgumentException("Polygon is not convex: Point " + points.get(pointIndex) + " Edge " + edge);
}
}
}
edgePoints = new GeoPoint[]{points.get(0)};
}
protected int legalIndex(int index) {
while (index >= points.size())
index -= points.size();
return index;
}
@Override
public boolean isWithin(final Vector point) {
for (final SidedPlane edge : edges) {
if (!edge.isWithin(point))
return false;
}
return true;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
for (final SidedPlane edge : edges) {
if (!edge.isWithin(x, y, z))
return false;
}
return true;
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
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++) {
final SidedPlane edge = edges[edgeIndex];
final GeoPoint[] points = this.notableEdgePoints[edgeIndex];
if (!internalEdges[edgeIndex]) {
//System.err.println(" non-internal edge "+edge);
// Edges flagged as 'internal only' are excluded from the matching
// Construct boundaries
final Membership[] membershipBounds = new Membership[edges.length - 1];
int count = 0;
for (int otherIndex = 0; otherIndex < edges.length; otherIndex++) {
if (otherIndex != edgeIndex) {
membershipBounds[count++] = edges[otherIndex];
}
}
if (edge.intersects(p, notablePoints, points, bounds, membershipBounds)) {
//System.err.println(" intersects!");
return true;
}
}
}
//System.err.println(" no intersection");
return false;
}
/**
* 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) {
bounds = super.getBounds(bounds);
// Add all the points
for (final GeoPoint point : points) {
bounds.addPoint(point);
}
// Add planes with membership.
for (int edgeIndex = 0; edgeIndex < edges.length; edgeIndex++) {
final SidedPlane edge = edges[edgeIndex];
// Construct boundaries
final Membership[] membershipBounds = new Membership[edges.length - 1];
int count = 0;
for (int otherIndex = 0; otherIndex < edges.length; otherIndex++) {
if (otherIndex != edgeIndex) {
membershipBounds[count++] = edges[otherIndex];
}
}
edge.recordBounds(bounds, membershipBounds);
}
if (fullDistance >= Math.PI) {
// We can't reliably assume that bounds did its longitude calculation right, so we force it to be unbounded.
bounds.noLongitudeBound();
}
return bounds;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoConvexPolygon))
return false;
GeoConvexPolygon other = (GeoConvexPolygon) o;
if (other.points.size() != points.size())
return false;
for (int i = 0; i < points.size(); i++) {
if (!other.points.get(i).equals(points.get(i)))
return false;
}
return true;
}
@Override
public int hashCode() {
return points.hashCode();
}
@Override
public String toString() {
StringBuilder edgeString = new StringBuilder("{");
for (int i = 0; i < edges.length; i++) {
edgeString.append(edges[i]).append(" internal? ").append(internalEdges[i]).append("; ");
}
edgeString.append("}");
return "GeoConvexPolygon: {points=" + points + " edges=" + edgeString + "}";
}
}

View File

@ -0,0 +1,203 @@
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.
*/
/**
* Degenerate bounding box limited on two sides (left lon, right lon).
* The left-right maximum extent for this shape is PI; for anything larger, use
* GeoWideDegenerateHorizontalLine.
*
* @lucene.internal
*/
public class GeoDegenerateHorizontalLine extends GeoBBoxBase {
public final double latitude;
public final double leftLon;
public final double rightLon;
public final GeoPoint LHC;
public final GeoPoint RHC;
public final Plane plane;
public final SidedPlane leftPlane;
public final SidedPlane rightPlane;
public final GeoPoint[] planePoints;
public final GeoPoint centerPoint;
public final GeoPoint[] edgePoints;
/**
* Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI}
*/
public GeoDegenerateHorizontalLine(final double latitude, final double leftLon, double rightLon) {
// Argument checking
if (latitude > Math.PI * 0.5 || latitude < -Math.PI * 0.5)
throw new IllegalArgumentException("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.latitude = latitude;
this.leftLon = leftLon;
this.rightLon = rightLon;
final double sinLatitude = Math.sin(latitude);
final double cosLatitude = Math.cos(latitude);
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 two points
this.LHC = new GeoPoint(sinLatitude, sinLeftLon, cosLatitude, cosLeftLon);
this.RHC = new GeoPoint(sinLatitude, sinRightLon, cosLatitude, cosRightLon);
this.plane = new Plane(sinLatitude);
// 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(sinLatitude, sinMiddleLon, cosLatitude, cosMiddleLon);
this.leftPlane = new SidedPlane(centerPoint, cosLeftLon, sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint, cosRightLon, sinRightLon);
this.planePoints = new GeoPoint[]{LHC, RHC};
this.edgePoints = new GeoPoint[]{centerPoint};
}
@Override
public GeoBBox expand(final double angle) {
double newTopLat = latitude + angle;
double newBottomLat = latitude - 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 plane.evaluateIsZero(point) &&
leftPlane.isWithin(point) &&
rightPlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return plane.evaluateIsZero(x, y, z) &&
leftPlane.isWithin(x, y, z) &&
rightPlane.isWithin(x, y, z);
}
@Override
public double getRadius() {
double topAngle = centerPoint.arcDistance(RHC);
double bottomAngle = centerPoint.arcDistance(LHC);
return Math.max(topAngle, bottomAngle);
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
return p.intersects(plane, notablePoints, planePoints, bounds, leftPlane, rightPlane);
}
/**
* 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(latitude).addLongitudeSlice(leftLon, rightLon);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
if (path.intersects(plane, planePoints, leftPlane, rightPlane))
return OVERLAPS;
if (path.isWithin(centerPoint))
return CONTAINS;
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoDegenerateHorizontalLine))
return false;
GeoDegenerateHorizontalLine other = (GeoDegenerateHorizontalLine) o;
return other.LHC.equals(LHC) && other.RHC.equals(RHC);
}
@Override
public int hashCode() {
int result = LHC.hashCode();
result = 31 * result + RHC.hashCode();
return result;
}
@Override
public String toString() {
return "GeoDegenerateHorizontalLine: {latitude=" + latitude + "(" + latitude * 180.0 / Math.PI + "), leftlon=" + leftLon + "(" + leftLon * 180.0 / Math.PI + "), rightLon=" + rightLon + "(" + rightLon * 180.0 / Math.PI + ")}";
}
}

View File

@ -0,0 +1,143 @@
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 of one specific latitude with
* no longitude bounds.
*
* @lucene.internal
*/
public class GeoDegenerateLatitudeZone extends GeoBBoxBase {
public final double latitude;
public final double sinLatitude;
public final Plane plane;
public final GeoPoint interiorPoint;
public final GeoPoint[] edgePoints;
public final static GeoPoint[] planePoints = new GeoPoint[0];
public GeoDegenerateLatitudeZone(final double latitude) {
this.latitude = latitude;
this.sinLatitude = Math.sin(latitude);
double cosLatitude = Math.cos(latitude);
this.plane = new Plane(sinLatitude);
// Compute an interior point.
interiorPoint = new GeoPoint(cosLatitude, 0.0, sinLatitude);
edgePoints = new GeoPoint[]{interiorPoint};
}
@Override
public GeoBBox expand(final double angle) {
double newTopLat = latitude + angle;
double newBottomLat = latitude - angle;
return GeoBBoxFactory.makeGeoBBox(newTopLat, newBottomLat, -Math.PI, Math.PI);
}
@Override
public boolean isWithin(final Vector point) {
return Math.abs(point.z - this.sinLatitude) < 1e-10;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return Math.abs(z - this.sinLatitude) < 1e-10;
}
@Override
public double getRadius() {
return Math.PI;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
// Totally arbitrary
return interiorPoint;
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
return p.intersects(plane, 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(latitude);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
// 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.
//System.out.println("Got here! latitude="+latitude+" path="+path);
if (path.intersects(plane, planePoints)) {
return OVERLAPS;
}
if (path.isWithin(interiorPoint)) {
return CONTAINS;
}
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoDegenerateLatitudeZone))
return false;
GeoDegenerateLatitudeZone other = (GeoDegenerateLatitudeZone) o;
return other.latitude == latitude;
}
@Override
public int hashCode() {
long temp = Double.doubleToLongBits(latitude);
int result = (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "GeoDegenerateLatitudeZone: {lat=" + latitude + "(" + latitude * 180.0 / Math.PI + ")}";
}
}

View File

@ -0,0 +1,159 @@
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.
*/
/**
* Degenerate longitude slice.
*
* @lucene.internal
*/
public class GeoDegenerateLongitudeSlice extends GeoBBoxBase {
public final double longitude;
public final double sinLongitude;
public final double cosLongitude;
public final SidedPlane boundingPlane;
public final Plane plane;
public final GeoPoint interiorPoint;
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}
*/
public GeoDegenerateLongitudeSlice(final double longitude) {
// Argument checking
if (longitude < -Math.PI || longitude > Math.PI)
throw new IllegalArgumentException("Longitude out of range");
this.longitude = longitude;
this.sinLongitude = Math.sin(longitude);
this.cosLongitude = Math.cos(longitude);
this.plane = new Plane(cosLongitude, sinLongitude);
// We need a bounding plane too, which is perpendicular to the longitude plane and sided so that the point (0.0, longitude) is inside.
this.interiorPoint = new GeoPoint(cosLongitude, sinLongitude, 0.0);
this.boundingPlane = new SidedPlane(interiorPoint, -sinLongitude, cosLongitude);
this.edgePoints = new GeoPoint[]{interiorPoint};
}
@Override
public GeoBBox expand(final double angle) {
// Figuring out when we escalate to a special case requires some prefiguring
double newLeftLon = longitude - angle;
double newRightLon = longitude + angle;
double currentLonSpan = 2.0 * angle;
if (currentLonSpan + 2.0 * angle >= Math.PI * 2.0) {
newLeftLon = -Math.PI;
newRightLon = Math.PI;
}
return GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, newLeftLon, newRightLon);
}
@Override
public boolean isWithin(final Vector point) {
return plane.evaluateIsZero(point) &&
boundingPlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return plane.evaluateIsZero(x, y, z) &&
boundingPlane.isWithin(x, y, z);
}
@Override
public double getRadius() {
return Math.PI * 0.5;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return interiorPoint;
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
return p.intersects(plane, notablePoints, planePoints, bounds, boundingPlane);
}
/**
* 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().noBottomLatitudeBound();
bounds.addLongitudeSlice(longitude, longitude);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
// Look for intersections.
if (path.intersects(plane, planePoints, boundingPlane))
return OVERLAPS;
if (path.isWithin(interiorPoint))
return CONTAINS;
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoDegenerateLongitudeSlice))
return false;
GeoDegenerateLongitudeSlice other = (GeoDegenerateLongitudeSlice) o;
return other.longitude == longitude;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(longitude);
result = (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "GeoDegenerateLongitudeSlice: {longitude=" + longitude + "(" + longitude * 180.0 / Math.PI + ")}";
}
}

View File

@ -0,0 +1,197 @@
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 class represents a degenerate point bounding box.
* It is not a simple GeoPoint because we must have the latitude and longitude.
*
* @lucene.internal
*/
public class GeoDegeneratePoint extends GeoPoint implements GeoBBox {
public final double latitude;
public final double longitude;
public final GeoPoint[] edgePoints;
public GeoDegeneratePoint(final double lat, final double lon) {
super(lat, lon);
this.latitude = lat;
this.longitude = lon;
this.edgePoints = new GeoPoint[]{this};
}
/**
* Expand box by specified angle.
*
* @param angle is the angle amount to expand the GeoBBox by.
* @return a new GeoBBox.
*/
@Override
public GeoBBox expand(final double angle) {
final double newTopLat = latitude + angle;
final double newBottomLat = latitude - angle;
final double newLeftLon = longitude - angle;
final double newRightLon = longitude + angle;
return GeoBBoxFactory.makeGeoBBox(newTopLat, newBottomLat, newLeftLon, newRightLon);
}
/**
* Return a sample point that is on the edge of the shape.
*
* @return an interior point.
*/
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
/**
* Assess whether a plane, within the provided bounds, intersects
* with the shape.
*
* @param plane is the plane to assess for intersection with the shape's edges or
* bounding curves.
* @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).
* @return true if there's such an intersection, false if not.
*/
@Override
public boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds) {
// If not on the plane, no intersection
if (!plane.evaluateIsZero(this))
return false;
for (Membership m : bounds) {
if (!m.isWithin(this))
return false;
}
return true;
}
/**
* 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.addPoint(latitude, longitude);
return bounds;
}
/**
* Equals
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoDegeneratePoint))
return false;
GeoDegeneratePoint other = (GeoDegeneratePoint) o;
return other.latitude == latitude && other.longitude == longitude;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(latitude);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(longitude);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "GeoDegeneratePoint: {lat=" + latitude + "(" + latitude * 180.0 / Math.PI + "), lon=" + longitude + "(" + longitude * 180.0 / Math.PI + ")}";
}
/**
* Check if a point is within this shape.
*
* @param point is the point to check.
* @return true if the point is within this shape
*/
@Override
public boolean isWithin(final Vector point) {
return isWithin(point.x, point.y, point.z);
}
/**
* Check if a point is within this shape.
*
* @param x is x coordinate of point to check.
* @param y is y coordinate of point to check.
* @param z is z coordinate of point to check.
* @return true if the point is within this shape
*/
@Override
public boolean isWithin(final double x, final double y, final double z) {
return x == this.x && y == this.y && z == this.z;
}
/**
* Returns the radius of a circle into which the GeoSizeable area can
* be inscribed.
*
* @return the radius.
*/
@Override
public double getRadius() {
return 0.0;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return this;
}
/**
* Find the spatial relationship between a shape and the current geo area.
* Note: return value is how the GeoShape relates to the GeoArea, not the
* other way around. For example, if this GeoArea is entirely within the
* shape, then CONTAINS should be returned. If the shape is entirely enclosed
* by this GeoArea, then WITHIN should be returned.
*
* @param shape is the shape to consider.
* @return the relationship, from the perspective of the shape.
*/
@Override
public int getRelationship(final GeoShape shape) {
if (shape.isWithin(this)) {
//System.err.println("Degenerate point "+this+" is WITHIN shape "+shape);
return CONTAINS;
}
//System.err.println("Degenerate point "+this+" is NOT within shape "+shape);
return DISJOINT;
}
}

View File

@ -0,0 +1,206 @@
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.
*/
/**
* Degenerate bounding box limited on two sides (top lat, bottom lat).
*
* @lucene.internal
*/
public class GeoDegenerateVerticalLine extends GeoBBoxBase {
public final double topLat;
public final double bottomLat;
public final double longitude;
public final GeoPoint UHC;
public final GeoPoint LHC;
public final SidedPlane topPlane;
public final SidedPlane bottomPlane;
public final SidedPlane boundingPlane;
public final Plane plane;
public final GeoPoint[] planePoints;
public final GeoPoint centerPoint;
public final GeoPoint[] edgePoints;
/**
* Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, longitude: {@code -PI -> PI}
*/
public GeoDegenerateVerticalLine(final double topLat, final double bottomLat, final double longitude) {
// Argument checking
if (topLat > Math.PI * 0.5 || topLat < -Math.PI * 0.5)
throw new IllegalArgumentException("Top latitude out of range");
if (bottomLat > Math.PI * 0.5 || bottomLat < -Math.PI * 0.5)
throw new IllegalArgumentException("Bottom latitude out of range");
if (topLat < bottomLat)
throw new IllegalArgumentException("Top latitude less than bottom latitude");
if (longitude < -Math.PI || longitude > Math.PI)
throw new IllegalArgumentException("Longitude out of range");
this.topLat = topLat;
this.bottomLat = bottomLat;
this.longitude = longitude;
final double sinTopLat = Math.sin(topLat);
final double cosTopLat = Math.cos(topLat);
final double sinBottomLat = Math.sin(bottomLat);
final double cosBottomLat = Math.cos(bottomLat);
final double sinLongitude = Math.sin(longitude);
final double cosLongitude = Math.cos(longitude);
// Now build the two points
this.UHC = new GeoPoint(sinTopLat, sinLongitude, cosTopLat, cosLongitude);
this.LHC = new GeoPoint(sinBottomLat, sinLongitude, cosBottomLat, cosLongitude);
this.plane = new Plane(cosLongitude, sinLongitude);
final double middleLat = (topLat + bottomLat) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
final double cosMiddleLat = Math.cos(middleLat);
this.centerPoint = new GeoPoint(sinMiddleLat, sinLongitude, cosMiddleLat, cosLongitude);
this.topPlane = new SidedPlane(centerPoint, sinTopLat);
this.bottomPlane = new SidedPlane(centerPoint, sinBottomLat);
this.boundingPlane = new SidedPlane(centerPoint, -sinLongitude, cosLongitude);
this.planePoints = new GeoPoint[]{UHC, LHC};
this.edgePoints = new GeoPoint[]{centerPoint};
}
@Override
public GeoBBox expand(final double angle) {
final double newTopLat = topLat + angle;
final double newBottomLat = bottomLat - angle;
double newLeftLon = longitude - angle;
double newRightLon = longitude + angle;
double currentLonSpan = 2.0 * 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 plane.evaluateIsZero(point) &&
boundingPlane.isWithin(point) &&
topPlane.isWithin(point) &&
bottomPlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return plane.evaluateIsZero(x, y, z) &&
boundingPlane.isWithin(x, y, z) &&
topPlane.isWithin(x, y, z) &&
bottomPlane.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 topAngle = centerPoint.arcDistance(UHC);
final double bottomAngle = centerPoint.arcDistance(LHC);
return Math.max(topAngle, bottomAngle);
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
return p.intersects(plane, notablePoints, planePoints, bounds, boundingPlane, topPlane, 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.addLatitudeZone(topLat).addLatitudeZone(bottomLat)
.addLongitudeSlice(longitude, longitude);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" relationship to "+path);
if (path.intersects(plane, planePoints, boundingPlane, topPlane, bottomPlane)) {
//System.err.println(" overlaps");
return OVERLAPS;
}
if (path.isWithin(centerPoint)) {
//System.err.println(" contains");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoDegenerateVerticalLine))
return false;
GeoDegenerateVerticalLine other = (GeoDegenerateVerticalLine) o;
return other.UHC.equals(UHC) && other.LHC.equals(LHC);
}
@Override
public int hashCode() {
int result = UHC.hashCode();
result = 31 * result + LHC.hashCode();
return result;
}
@Override
public String toString() {
return "GeoDegenerateVerticalLine: {longitude=" + longitude + "(" + longitude * 180.0 / Math.PI + "), toplat=" + topLat + "(" + topLat * 180.0 / Math.PI + "), bottomlat=" + bottomLat + "(" + bottomLat * 180.0 / Math.PI + ")}";
}
}

View File

@ -0,0 +1,153 @@
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.
*/
/**
* Generic geo-distance-capable shape class description. An implementer
* of this interface is capable of computing the described "distance" values,
* which are meant to provide both actual distance values, as well as
* distance estimates that can be computed more cheaply.
*
* @lucene.experimental
*/
public interface GeoDistance extends Membership {
/**
* Compute this shape's normal "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*
* @param point is the point to compute the distance to.
* @return the normal distance, defined as the perpendicular distance from
* from the point to one of the shape's bounding plane. Normal
* distances can therefore typically only go up to PI/2, except
* when they represent the sum of a sequence of normal distances.
*/
public double computeNormalDistance(GeoPoint point);
/**
* Compute this shape's normal "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*
* @param x is the point's unit x coordinate (using U.S. convention).
* @param y is the point's unit y coordinate (using U.S. convention).
* @param z is the point's unit z coordinate (using U.S. convention).
* @return the normal distance, defined as the perpendicular distance from
* from the point to one of the shape's bounding plane. Normal
* distances can therefore typically only go up to PI/2, except
* when they represent the sum of a sequence of normal distances.
*/
public double computeNormalDistance(double x, double y, double z);
/**
* Compute the square of this shape's normal "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*
* @param point is the point to compute the distance to.
* @return the square of the normal distance, defined as the perpendicular
* distance from
* from the point to one of the shape's bounding plane. Normal
* distances can therefore typically only go up to PI/2, except
* when they represent the sum of a sequence of normal distances.
*/
public double computeSquaredNormalDistance(GeoPoint point);
/**
* Compute the square of this shape's normal "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*
* @param x is the point's unit x coordinate (using U.S. convention).
* @param y is the point's unit y coordinate (using U.S. convention).
* @param z is the point's unit z coordinate (using U.S. convention).
* @return the square of the normal distance, defined as the perpendicular
* distance from
* from the point to one of the shape's bounding plane. Normal
* distances can therefore typically only go up to PI/2, except
* when they represent the sum of a sequence of normal distances.
*/
public double computeSquaredNormalDistance(double x, double y, double z);
/**
* Compute this shape's linear "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*
* @param point is the point to compute the distance to.
* @return the linear (or chord) distance, defined as the distance from
* from the point to the nearest point on the unit sphere and on one of the shape's
* bounding planes. Linear distances can therefore typically go up to PI,
* except when they represent the sum of a sequence of linear distances.
*/
public double computeLinearDistance(GeoPoint point);
/**
* Compute this shape's linear "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*
* @param x is the point's unit x coordinate (using U.S. convention).
* @param y is the point's unit y coordinate (using U.S. convention).
* @param z is the point's unit z coordinate (using U.S. convention).
* @return the linear (or chord) distance, defined as the distance from
* from the point to the nearest point on the unit sphere and on one of the shape's
* bounding planes. Linear distances can therefore typically go up to PI,
* except when they represent the sum of a sequence of linear distances.
*/
public double computeLinearDistance(double x, double y, double z);
/**
* Compute the square of this shape's linear "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*
* @param point is the point to compute the distance to.
* @return the square of the linear (or chord) distance, defined as the
* distance from
* from the point to the nearest point on the unit sphere and on one of the shape's
* bounding planes. Linear distances can therefore typically go up to PI,
* except when they represent the sum of a sequence of linear distances.
*/
public double computeSquaredLinearDistance(GeoPoint point);
/**
* Compute the square of this shape's linear "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*
* @param x is the point's unit x coordinate (using U.S. convention).
* @param y is the point's unit y coordinate (using U.S. convention).
* @param z is the point's unit z coordinate (using U.S. convention).
* @return the square of the linear (or chord) distance, defined as the distance from
* from the point to the nearest point on the unit sphere and on one of the shape's
* bounding planes. Linear distances can therefore typically go up to PI,
* except when they represent the sum of a sequence of linear distances.
*/
public double computeSquaredLinearDistance(double x, double y, double z);
/**
* Compute a true, accurate, great-circle distance to a point.
* Double.MAX_VALUE indicates a point is outside of the shape.
*
* @param point is the point.
* @return the distance.
*/
public double computeArcDistance(GeoPoint point);
}

View File

@ -0,0 +1,28 @@
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.
*/
/**
* Distance shapes have capabilities of both geohashing and distance
* computation (which also includes point membership determination).
*
* @lucene.experimental
*/
public interface GeoDistanceShape extends GeoMembershipShape, GeoDistance {
}

View File

@ -0,0 +1,198 @@
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 latitude.
*
* @lucene.internal
*/
public class GeoLatitudeZone extends GeoBBoxBase {
public final double topLat;
public final double bottomLat;
public final double cosTopLat;
public final double cosBottomLat;
public final SidedPlane topPlane;
public final SidedPlane bottomPlane;
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
// special case that most GeoBBox's do not have.
public final GeoPoint topBoundaryPoint;
public final GeoPoint bottomBoundaryPoint;
// Edge points
public final GeoPoint[] edgePoints;
public GeoLatitudeZone(final double topLat, final double bottomLat) {
this.topLat = topLat;
this.bottomLat = bottomLat;
final double sinTopLat = Math.sin(topLat);
final double sinBottomLat = Math.sin(bottomLat);
this.cosTopLat = Math.cos(topLat);
this.cosBottomLat = Math.cos(bottomLat);
// Construct sample points, so we get our sidedness right
final Vector topPoint = new Vector(0.0, 0.0, sinTopLat);
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 = (topLat + bottomLat) * 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.bottomBoundaryPoint = new GeoPoint(Math.sqrt(1.0 - sinBottomLat * sinBottomLat), 0.0, sinBottomLat);
this.topPlane = new SidedPlane(interiorPoint, sinTopLat);
this.bottomPlane = new SidedPlane(interiorPoint, sinBottomLat);
this.edgePoints = new GeoPoint[]{topBoundaryPoint, bottomBoundaryPoint};
}
@Override
public GeoBBox expand(final double angle) {
final double newTopLat = topLat + angle;
final double newBottomLat = bottomLat - angle;
return GeoBBoxFactory.makeGeoBBox(newTopLat, newBottomLat, -Math.PI, Math.PI);
}
@Override
public boolean isWithin(final Vector point) {
return topPlane.isWithin(point) &&
bottomPlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return topPlane.isWithin(x, y, z) &&
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 (topLat > 0.0 && bottomLat < 0.0)
return Math.PI;
double maxCosLat = cosTopLat;
if (maxCosLat < cosBottomLat)
maxCosLat = cosBottomLat;
return maxCosLat * Math.PI;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
// This is totally arbitrary and only a cartesian could agree with it.
return interiorPoint;
}
@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, bottomPlane) ||
p.intersects(bottomPlane, notablePoints, planePoints, 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.noLongitudeBound().addLatitudeZone(topLat).addLatitudeZone(bottomLat);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
final int insideRectangle = isShapeInsideBBox(path);
if (insideRectangle == SOME_INSIDE)
return OVERLAPS;
final boolean topBoundaryInsideShape = path.isWithin(topBoundaryPoint);
final boolean bottomBoundaryInsideShape = path.isWithin(bottomBoundaryPoint);
if (topBoundaryInsideShape && !bottomBoundaryInsideShape ||
!topBoundaryInsideShape && bottomBoundaryInsideShape)
return OVERLAPS;
final boolean insideShape = topBoundaryInsideShape && bottomBoundaryInsideShape;
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, bottomPlane) ||
path.intersects(bottomPlane, planePoints, topPlane))
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 GeoLatitudeZone))
return false;
GeoLatitudeZone other = (GeoLatitudeZone) o;
return other.topPlane.equals(topPlane) && other.bottomPlane.equals(bottomPlane);
}
@Override
public int hashCode() {
int result = topPlane.hashCode();
result = 31 * result + bottomPlane.hashCode();
return result;
}
@Override
public String toString() {
return "GeoLatitudeZone: {toplat=" + topLat + "(" + topLat * 180.0 / Math.PI + "), bottomlat=" + bottomLat + "(" + bottomLat * 180.0 / Math.PI + ")}";
}
}

View File

@ -0,0 +1,202 @@
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 left and right.
* The left-right maximum extent for this shape is PI; for anything larger, use
* GeoWideLongitudeSlice.
*
* @lucene.internal
*/
public class GeoLongitudeSlice extends GeoBBoxBase {
public final double leftLon;
public final double rightLon;
public final SidedPlane leftPlane;
public final SidedPlane rightPlane;
public final static GeoPoint[] planePoints = new GeoPoint[]{NORTH_POLE, SOUTH_POLE};
public final GeoPoint centerPoint;
public final static GeoPoint[] edgePoints = new GeoPoint[]{NORTH_POLE};
/**
* Accepts only values in the following ranges: lon: {@code -PI -> PI}
*/
public GeoLongitudeSlice(final double leftLon, double rightLon) {
// Argument checking
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.leftLon = leftLon;
this.rightLon = rightLon;
final double sinLeftLon = Math.sin(leftLon);
final double cosLeftLon = Math.cos(leftLon);
final double sinRightLon = Math.sin(rightLon);
final double cosRightLon = Math.cos(rightLon);
// Normalize
while (leftLon > rightLon) {
rightLon += Math.PI * 2.0;
}
final double middleLon = (leftLon + rightLon) * 0.5;
this.centerPoint = new GeoPoint(0.0, middleLon);
this.leftPlane = new SidedPlane(centerPoint, cosLeftLon, sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint, cosRightLon, sinRightLon);
}
@Override
public GeoBBox expand(final double 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(Math.PI * 0.5, -Math.PI * 0.5, newLeftLon, newRightLon);
}
@Override
public boolean isWithin(final Vector point) {
return leftPlane.isWithin(point) &&
rightPlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return leftPlane.isWithin(x, y, z) &&
rightPlane.isWithin(x, y, z);
}
@Override
public double getRadius() {
// Compute the extent and divide by two
double extent = rightLon - leftLon;
if (extent < 0.0)
extent += Math.PI * 2.0;
return Math.max(Math.PI * 0.5, extent * 0.5);
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
return p.intersects(leftPlane, notablePoints, planePoints, bounds, rightPlane) ||
p.intersects(rightPlane, notablePoints, planePoints, bounds, leftPlane);
}
/**
* 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().noBottomLatitudeBound();
bounds.addLongitudeSlice(leftLon, rightLon);
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(NORTH_POLE);
if (insideRectangle == ALL_INSIDE && insideShape)
return OVERLAPS;
if (path.intersects(leftPlane, planePoints, rightPlane) ||
path.intersects(rightPlane, planePoints, leftPlane)) {
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
return WITHIN;
}
if (insideShape) {
return CONTAINS;
}
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoLongitudeSlice))
return false;
GeoLongitudeSlice other = (GeoLongitudeSlice) o;
return other.leftLon == leftLon && other.rightLon == rightLon;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(leftLon);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(rightLon);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "GeoLongitudeSlice: {leftlon=" + leftLon + "(" + leftLon * 180.0 / Math.PI + "), rightlon=" + rightLon + "(" + rightLon * 180.0 / Math.PI + ")}";
}
}

View File

@ -0,0 +1,28 @@
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.
*/
/**
* Membership shapes have capabilities of both geohashing and membership
* determination.
*
* @lucene.experimental
*/
public interface GeoMembershipShape extends GeoShape, Membership {
}

View File

@ -0,0 +1,176 @@
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.
*
* @lucene.internal
*/
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;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return interiorPoint;
}
@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,248 @@
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.
*
* @lucene.internal
*/
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;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@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

@ -0,0 +1,669 @@
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.
*/
import java.util.ArrayList;
import java.util.List;
/**
* GeoSearchableShape representing a path across the surface of the globe,
* with a specified half-width. Path is described by a series of points.
* Distances are measured from the starting point along the path, and then at right
* angles to the path.
*
* @lucene.experimental
*/
public class GeoPath extends GeoBaseExtendedShape implements GeoDistanceShape {
public final double cutoffAngle;
public final double cutoffOffset;
public final double originDistance;
public final double chordDistance;
public final List<SegmentEndpoint> points = new ArrayList<SegmentEndpoint>();
public final List<PathSegment> segments = new ArrayList<PathSegment>();
public GeoPoint[] edgePoints = null;
public GeoPath(final double cutoffAngle) {
super();
if (cutoffAngle <= 0.0 || cutoffAngle > Math.PI * 0.5)
throw new IllegalArgumentException("Cutoff angle out of bounds");
this.cutoffAngle = cutoffAngle;
final double cosAngle = Math.cos(cutoffAngle);
final double sinAngle = Math.sin(cutoffAngle);
// Cutoff offset is the linear distance given the angle
this.cutoffOffset = sinAngle;
this.originDistance = cosAngle;
// Compute chord distance
double xDiff = 1.0 - cosAngle;
this.chordDistance = Math.sqrt(xDiff * xDiff + sinAngle * sinAngle);
}
public void addPoint(double lat, double lon) {
if (lat < -Math.PI * 0.5 || lat > Math.PI * 0.5)
throw new IllegalArgumentException("Latitude out of range");
if (lon < -Math.PI || lon > Math.PI)
throw new IllegalArgumentException("Longitude out of range");
final GeoPoint end = new GeoPoint(lat, lon);
if (points.size() > 0) {
final GeoPoint start = points.get(points.size() - 1).point;
final PathSegment ps = new PathSegment(start, end, cutoffOffset, cutoffAngle, chordDistance);
// Check for degeneracy; if the segment is degenerate, don't include the point
if (ps.isDegenerate())
return;
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);
points.add(se);
}
public void done() {
if (points.size() == 0)
throw new IllegalArgumentException("Path must have at least one point");
if (segments.size() > 0) {
edgePoints = new GeoPoint[]{points.get(0).circlePlane.getSampleIntersectionPoint(segments.get(0).invertedStartCutoffPlane)};
}
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);
}
}
/**
* Compute an estimate of "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*/
@Override
public double computeNormalDistance(final GeoPoint point) {
// Algorithm:
// (1) If the point is within any of the segments along the path, return that value.
// (2) If the point is within any of the segment end circles along the path, return that value.
double currentDistance = 0.0;
for (PathSegment segment : segments) {
double distance = segment.pathNormalDistance(point);
if (distance != Double.MAX_VALUE)
return currentDistance + distance;
currentDistance += segment.fullNormalDistance;
}
int segmentIndex = 0;
currentDistance = 0.0;
for (SegmentEndpoint endpoint : points) {
double distance = endpoint.pathNormalDistance(point);
if (distance != Double.MAX_VALUE)
return currentDistance + distance;
if (segmentIndex < segments.size())
currentDistance += segments.get(segmentIndex++).fullNormalDistance;
}
return Double.MAX_VALUE;
}
/**
* Compute an estimate of "distance" to the GeoPoint.
* A return value of Double.MAX_VALUE should be returned for
* points outside of the shape.
*/
@Override
public double computeNormalDistance(final double x, final double y, final double z) {
return computeNormalDistance(new GeoPoint(x, y, z));
}
/**
* Compute a squared estimate of the "distance" to the
* GeoPoint. Double.MAX_VALUE indicates a point outside of the
* shape.
*/
@Override
public double computeSquaredNormalDistance(final GeoPoint point) {
double pd = computeNormalDistance(point);
if (pd == Double.MAX_VALUE)
return pd;
return pd * pd;
}
/**
* Compute a squared estimate of the "distance" to the
* GeoPoint. Double.MAX_VALUE indicates a point outside of the
* shape.
*/
@Override
public double computeSquaredNormalDistance(final double x, final double y, final double z) {
return computeSquaredNormalDistance(new GeoPoint(x, y, z));
}
/**
* Compute a linear distance to the point.
*/
@Override
public double computeLinearDistance(final GeoPoint point) {
// Algorithm:
// (1) If the point is within any of the segments along the path, return that value.
// (2) If the point is within any of the segment end circles along the path, return that value.
double currentDistance = 0.0;
for (PathSegment segment : segments) {
double distance = segment.pathLinearDistance(point);
if (distance != Double.MAX_VALUE)
return currentDistance + distance;
currentDistance += segment.fullLinearDistance;
}
int segmentIndex = 0;
currentDistance = 0.0;
for (SegmentEndpoint endpoint : points) {
double distance = endpoint.pathLinearDistance(point);
if (distance != Double.MAX_VALUE)
return currentDistance + distance;
if (segmentIndex < segments.size())
currentDistance += segments.get(segmentIndex++).fullLinearDistance;
}
return Double.MAX_VALUE;
}
/**
* Compute a linear distance to the point.
*/
@Override
public double computeLinearDistance(final double x, final double y, final double z) {
return computeLinearDistance(new GeoPoint(x, y, z));
}
/**
* Compute a squared linear distance to the vector.
*/
@Override
public double computeSquaredLinearDistance(final GeoPoint point) {
double pd = computeLinearDistance(point);
if (pd == Double.MAX_VALUE)
return pd;
return pd * pd;
}
/**
* Compute a squared linear distance to the vector.
*/
@Override
public double computeSquaredLinearDistance(final double x, final double y, final double z) {
return computeSquaredLinearDistance(new GeoPoint(x, y, z));
}
/**
* Compute a true, accurate, great-circle distance.
* Double.MAX_VALUE indicates a point is outside of the shape.
*/
@Override
public double computeArcDistance(final GeoPoint point) {
// Algorithm:
// (1) If the point is within any of the segments along the path, return that value.
// (2) If the point is within any of the segment end circles along the path, return that value.
double currentDistance = 0.0;
for (PathSegment segment : segments) {
double distance = segment.pathDistance(point);
if (distance != Double.MAX_VALUE)
return currentDistance + distance;
currentDistance += segment.fullDistance;
}
int segmentIndex = 0;
currentDistance = 0.0;
for (SegmentEndpoint endpoint : points) {
double distance = endpoint.pathDistance(point);
if (distance != Double.MAX_VALUE)
return currentDistance + distance;
if (segmentIndex < segments.size())
currentDistance += segments.get(segmentIndex++).fullDistance;
}
return Double.MAX_VALUE;
}
@Override
public boolean isWithin(final Vector point) {
for (SegmentEndpoint pathPoint : points) {
if (pathPoint.isWithin(point))
return true;
}
for (PathSegment pathSegment : segments) {
if (pathSegment.isWithin(point))
return true;
}
return false;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
for (SegmentEndpoint pathPoint : points) {
if (pathPoint.isWithin(x, y, z))
return true;
}
for (PathSegment pathSegment : segments) {
if (pathSegment.isWithin(x, y, z))
return true;
}
return false;
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
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 also have to look for intersections with the cones described by the endpoints.
// Return "true" if any such intersections are found.
// For plane intersections, the basic idea is to come up with an equation of the line that is
// the intersection (if any). Then, find the intersections with the unit sphere (if any). If
// any of the intersection points are within the bounds, then we've detected an intersection.
// 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.
for (final SegmentEndpoint pathPoint : points) {
if (pathPoint.intersects(plane, notablePoints, bounds)) {
return true;
}
}
for (final PathSegment pathSegment : segments) {
if (pathSegment.intersects(plane, notablePoints, bounds)) {
return true;
}
}
return false;
}
/**
* 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) {
bounds = super.getBounds(bounds);
// For building bounds, order matters. We want to traverse
// never more than 180 degrees longitude at a pop or we risk having the
// bounds object get itself inverted. So do the edges first.
for (PathSegment pathSegment : segments) {
pathSegment.getBounds(bounds);
}
for (SegmentEndpoint pathPoint : points) {
pathPoint.getBounds(bounds);
}
return bounds;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoPath))
return false;
GeoPath p = (GeoPath) o;
if (points.size() != p.points.size())
return false;
if (cutoffAngle != p.cutoffAngle)
return false;
for (int i = 0; i < points.size(); i++) {
SegmentEndpoint point = points.get(i);
SegmentEndpoint point2 = p.points.get(i);
if (!point.equals(point2))
return false;
}
return true;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(cutoffAngle);
result = (int) (temp ^ (temp >>> 32));
result = 31 * result + points.hashCode();
return result;
}
@Override
public String toString() {
return "GeoPath: {width=" + cutoffAngle + "(" + cutoffAngle * 180.0 / Math.PI + "), points={" + points + "}}";
}
/**
* This is precalculated data for segment endpoint.
*/
public static class SegmentEndpoint {
public final GeoPoint point;
public final SidedPlane circlePlane;
public final double cutoffNormalDistance;
public final double cutoffAngle;
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) {
this.point = point;
this.cutoffNormalDistance = cutoffOffset;
this.cutoffAngle = cutoffAngle;
this.chordDistance = chordDistance;
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) {
return circlePlane.isWithin(point);
}
public boolean isWithin(final double x, final double y, final double z) {
return circlePlane.isWithin(x, y, z);
}
public double pathDistance(final GeoPoint point) {
double dist = this.point.arcDistance(point);
if (dist > cutoffAngle)
return Double.MAX_VALUE;
return dist;
}
public double pathNormalDistance(final GeoPoint point) {
double dist = this.point.normalDistance(point);
if (dist > cutoffNormalDistance)
return Double.MAX_VALUE;
return dist;
}
public double pathLinearDistance(final GeoPoint point) {
double dist = this.point.linearDistance(point);
if (dist > chordDistance)
return Double.MAX_VALUE;
return dist;
}
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
return circlePlane.intersects(p, notablePoints, this.notablePoints, bounds, this.cutoffPlanes);
}
public void getBounds(Bounds bounds) {
bounds.addPoint(point);
circlePlane.recordBounds(bounds);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof SegmentEndpoint))
return false;
SegmentEndpoint other = (SegmentEndpoint) o;
return point.equals(other.point);
}
@Override
public int hashCode() {
return point.hashCode();
}
@Override
public String toString() {
return point.toString();
}
}
/**
* This is the precalculated data for a path segment.
*/
public static class PathSegment {
public final GeoPoint start;
public final GeoPoint end;
public final double fullDistance;
public final double fullNormalDistance;
public final double fullLinearDistance;
public final Plane normalizedConnectingPlane;
public final SidedPlane upperConnectingPlane;
public final SidedPlane lowerConnectingPlane;
public final SidedPlane startCutoffPlane;
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 arcWidth;
public final double chordDistance;
// For the adjoining SegmentEndpoint...
public final SidedPlane invertedStartCutoffPlane;
public final SidedPlane invertedEndCutoffPlane;
public PathSegment(final GeoPoint start, final GeoPoint end, final double planeBoundingOffset, final double arcWidth, final double chordDistance) {
this.start = start;
this.end = end;
this.planeBoundingOffset = planeBoundingOffset;
this.arcWidth = arcWidth;
this.chordDistance = chordDistance;
fullDistance = start.arcDistance(end);
fullNormalDistance = start.normalDistance(end);
fullLinearDistance = start.linearDistance(end);
normalizedConnectingPlane = new Plane(start, end).normalize();
if (normalizedConnectingPlane == null) {
upperConnectingPlane = null;
lowerConnectingPlane = null;
startCutoffPlane = null;
endCutoffPlane = null;
upperConnectingPlanePoints = null;
lowerConnectingPlanePoints = null;
startCutoffPlanePoints = null;
endCutoffPlanePoints = null;
invertedStartCutoffPlane = null;
invertedEndCutoffPlane = null;
} else {
// Either start or end should be on the correct side
upperConnectingPlane = new SidedPlane(start, normalizedConnectingPlane, -planeBoundingOffset);
lowerConnectingPlane = new SidedPlane(start, normalizedConnectingPlane, planeBoundingOffset);
// Cutoff planes use opposite endpoints as correct side examples
startCutoffPlane = new SidedPlane(end, normalizedConnectingPlane, start);
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);
invertedEndCutoffPlane = new SidedPlane(endCutoffPlane);
}
}
public boolean isDegenerate() {
return normalizedConnectingPlane == null;
}
public boolean isWithin(final Vector point) {
return startCutoffPlane.isWithin(point) &&
endCutoffPlane.isWithin(point) &&
upperConnectingPlane.isWithin(point) &&
lowerConnectingPlane.isWithin(point);
}
public boolean isWithin(final double x, final double y, final double z) {
return startCutoffPlane.isWithin(x, y, z) &&
endCutoffPlane.isWithin(x, y, z) &&
upperConnectingPlane.isWithin(x, y, z) &&
lowerConnectingPlane.isWithin(x, y, z);
}
public double pathDistance(final GeoPoint point) {
if (!isWithin(point))
return Double.MAX_VALUE;
// Compute the distance, filling in both components.
final double perpDistance = Math.PI * 0.5 - Tools.safeAcos(Math.abs(normalizedConnectingPlane.evaluate(point)));
final Plane normalizedPerpPlane = new Plane(normalizedConnectingPlane, point).normalize();
final double pathDistance = Math.PI * 0.5 - Tools.safeAcos(Math.abs(normalizedPerpPlane.evaluate(start)));
return perpDistance + pathDistance;
}
public double pathNormalDistance(final GeoPoint point) {
if (!isWithin(point))
return Double.MAX_VALUE;
final double pointEval = Math.abs(normalizedConnectingPlane.evaluate(point));
// Want no allocations or expensive operations! so we do this the hard way
final double perpX = normalizedConnectingPlane.y * point.z - normalizedConnectingPlane.z * point.y;
final double perpY = normalizedConnectingPlane.z * point.x - normalizedConnectingPlane.x * point.z;
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 (Math.abs(perpX) < Vector.MINIMUM_RESOLUTION && Math.abs(perpY) < Vector.MINIMUM_RESOLUTION && Math.abs(perpZ) < Vector.MINIMUM_RESOLUTION)
return point.normalDistance(start);
final double normFactor = 1.0 / Math.sqrt(perpX * perpX + perpY * perpY + perpZ * perpZ);
final double perpEval = Math.abs(perpX * start.x + perpY * start.y + perpZ * start.z);
return perpEval * normFactor + pointEval;
}
public double pathLinearDistance(final GeoPoint point) {
if (!isWithin(point))
return Double.MAX_VALUE;
// We have a normalized connecting plane.
// First, compute the perpendicular plane.
// Want no allocations or expensive operations! so we do this the hard way
final double perpX = normalizedConnectingPlane.y * point.z - normalizedConnectingPlane.z * point.y;
final double perpY = normalizedConnectingPlane.z * point.x - normalizedConnectingPlane.x * point.z;
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 (Math.abs(perpX) < Vector.MINIMUM_RESOLUTION && Math.abs(perpY) < Vector.MINIMUM_RESOLUTION && Math.abs(perpZ) < Vector.MINIMUM_RESOLUTION)
return point.linearDistance(start);
// Next, we need the vector of the line, which is the cross product of the normalized connecting plane
// and the perpendicular plane that we just calculated.
final double lineX = normalizedConnectingPlane.y * perpZ - normalizedConnectingPlane.z * perpY;
final double lineY = normalizedConnectingPlane.z * perpX - normalizedConnectingPlane.x * perpZ;
final double lineZ = normalizedConnectingPlane.x * perpY - normalizedConnectingPlane.y * perpX;
// Now, compute a normalization factor
final double normalizer = 1.0 / Math.sqrt(lineX * lineX + lineY * lineY + lineZ * lineZ);
// Pick which point by using bounding planes
double normLineX = lineX * normalizer;
double normLineY = lineY * normalizer;
double normLineZ = lineZ * normalizer;
if (!startCutoffPlane.isWithin(normLineX, normLineY, normLineZ) ||
!endCutoffPlane.isWithin(normLineX, normLineY, normLineZ)) {
normLineX = -normLineX;
normLineY = -normLineY;
normLineZ = -normLineZ;
}
// Compute linear distance for the two points
return point.linearDistance(normLineX, normLineY, normLineZ) + start.linearDistance(normLineX, normLineY, normLineZ);
}
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
return upperConnectingPlane.intersects(p, notablePoints, upperConnectingPlanePoints, bounds, lowerConnectingPlane, startCutoffPlane, endCutoffPlane) ||
lowerConnectingPlane.intersects(p, notablePoints, lowerConnectingPlanePoints, bounds, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
}
public void getBounds(Bounds bounds) {
// We need to do all bounding planes as well as corner points
bounds.addPoint(start).addPoint(end);
upperConnectingPlane.recordBounds(startCutoffPlane, bounds, lowerConnectingPlane, endCutoffPlane);
startCutoffPlane.recordBounds(lowerConnectingPlane, bounds, endCutoffPlane, upperConnectingPlane);
lowerConnectingPlane.recordBounds(endCutoffPlane, bounds, upperConnectingPlane, startCutoffPlane);
endCutoffPlane.recordBounds(upperConnectingPlane, bounds, startCutoffPlane, lowerConnectingPlane);
upperConnectingPlane.recordBounds(bounds, lowerConnectingPlane, startCutoffPlane, endCutoffPlane);
lowerConnectingPlane.recordBounds(bounds, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
startCutoffPlane.recordBounds(bounds, endCutoffPlane, upperConnectingPlane, lowerConnectingPlane);
endCutoffPlane.recordBounds(bounds, startCutoffPlane, upperConnectingPlane, lowerConnectingPlane);
if (fullDistance >= Math.PI) {
// Too large a segment basically means that we can confuse the Bounds object. Specifically, if our span exceeds 180 degrees
// in longitude (which even a segment whose actual length is less than that might if it goes close to a pole).
// Unfortunately, we can get arbitrarily close to the pole, so this may still not work in all cases.
bounds.noLongitudeBound();
}
}
}
}

View File

@ -0,0 +1,42 @@
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 class represents a point on the surface of a unit sphere.
*
* @lucene.experimental
*/
public class GeoPoint extends Vector {
public GeoPoint(final double sinLat, final double sinLon, final double cosLat, final double cosLon) {
super(cosLat * cosLon, cosLat * sinLon, sinLat);
}
public GeoPoint(final double lat, final double lon) {
this(Math.sin(lat), Math.sin(lon), Math.cos(lat), Math.cos(lon));
}
public GeoPoint(final double x, final double y, final double z) {
super(x, y, z);
}
public double arcDistance(final GeoPoint v) {
return Tools.safeAcos(dotProduct(v));
}
}

View File

@ -0,0 +1,170 @@
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.
*/
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
/**
* Class which constructs a GeoMembershipShape representing an arbitrary polygon.
*
* @lucene.experimental
*/
public class GeoPolygonFactory {
private GeoPolygonFactory() {
}
/**
* Create a GeoMembershipShape of the right kind given the specified bounds.
*
* @param pointList is a list of the GeoPoints to build an arbitrary polygon out of.
* @param convexPointIndex is the index of a single convex point whose conformation with
* its neighbors determines inside/outside for the entire polygon.
* @return a GeoMembershipShape corresponding to what was specified.
*/
public static GeoMembershipShape makeGeoPolygon(List<GeoPoint> pointList, int convexPointIndex) {
// The basic operation uses a set of points, two points determining one particular edge, and a sided plane
// describing membership.
return buildPolygonShape(pointList, convexPointIndex, getLegalIndex(convexPointIndex + 1, pointList.size()),
new SidedPlane(pointList.get(getLegalIndex(convexPointIndex - 1, pointList.size())),
pointList.get(convexPointIndex), pointList.get(getLegalIndex(convexPointIndex + 1, pointList.size()))),
false);
}
public static GeoMembershipShape buildPolygonShape(List<GeoPoint> pointsList, int startPointIndex, int endPointIndex, SidedPlane startingEdge, boolean isInternalEdge) {
// Algorithm as follows:
// Start with sided edge. Go through all points in some order. For each new point, determine if the point is within all edges considered so far.
// If not, put it into a list of points for recursion. If it is within, add new edge and keep going.
// Once we detect a point that is within, if there are points put aside for recursion, then call recursively.
// Current composite. This is what we'll actually be returning.
final GeoCompositeMembershipShape rval = new GeoCompositeMembershipShape();
final List<GeoPoint> recursionList = new ArrayList<GeoPoint>();
final List<GeoPoint> currentList = new ArrayList<GeoPoint>();
final BitSet internalEdgeList = new BitSet();
final List<SidedPlane> currentPlanes = new ArrayList<SidedPlane>();
// Initialize the current list and current planes
currentList.add(pointsList.get(startPointIndex));
currentList.add(pointsList.get(endPointIndex));
internalEdgeList.set(currentPlanes.size(), isInternalEdge);
currentPlanes.add(startingEdge);
// Now, scan all remaining points, in order. We'll use an index and just add to it.
for (int i = 0; i < pointsList.size() - 2; i++) {
GeoPoint newPoint = pointsList.get(getLegalIndex(i + endPointIndex + 1, pointsList.size()));
if (isWithin(newPoint, currentPlanes)) {
// Construct a sided plane based on the last two points, and the previous point
SidedPlane newBoundary = new SidedPlane(currentList.get(currentList.size() - 2), newPoint, currentList.get(currentList.size() - 1));
// Construct a sided plane based on the return trip
SidedPlane returnBoundary = new SidedPlane(currentList.get(currentList.size() - 1), currentList.get(0), newPoint);
// Verify that none of the points beyond the new point in the list are inside the polygon we'd
// be creating if we stopped making the current polygon right now.
boolean pointInside = false;
for (int j = i + 1; j < pointsList.size() - 2; j++) {
GeoPoint checkPoint = pointsList.get(getLegalIndex(j + endPointIndex + 1, pointsList.size()));
boolean isInside = true;
if (isInside && !newBoundary.isWithin(checkPoint))
isInside = false;
if (isInside && !returnBoundary.isWithin(checkPoint))
isInside = false;
if (isInside) {
for (SidedPlane plane : currentPlanes) {
if (!plane.isWithin(checkPoint)) {
isInside = false;
break;
}
}
}
if (isInside) {
pointInside = true;
break;
}
}
if (!pointInside) {
// Any excluded points?
boolean isInternalBoundary = recursionList.size() > 0;
if (isInternalBoundary) {
// Handle exclusion
recursionList.add(newPoint);
recursionList.add(currentList.get(currentList.size() - 1));
if (recursionList.size() == pointsList.size()) {
// We are trying to recurse with a list the same size as the one we started with.
// Clearly, the polygon cannot be constructed
throw new IllegalArgumentException("Polygon is illegal; cannot be decomposed into convex parts");
}
// We want the other side for the recursion
SidedPlane otherSideNewBoundary = new SidedPlane(newBoundary);
rval.addShape(buildPolygonShape(recursionList, recursionList.size() - 2, recursionList.size() - 1, otherSideNewBoundary, true));
recursionList.clear();
}
currentList.add(newPoint);
internalEdgeList.set(currentPlanes.size(), isInternalBoundary);
currentPlanes.add(newBoundary);
} else {
recursionList.add(newPoint);
}
} else {
recursionList.add(newPoint);
}
}
boolean returnEdgeInternalBoundary = recursionList.size() > 0;
if (returnEdgeInternalBoundary) {
// The last step back to the start point had a recursion, so take care of that before we complete our work
recursionList.add(currentList.get(0));
recursionList.add(currentList.get(currentList.size() - 1));
if (recursionList.size() == pointsList.size()) {
// We are trying to recurse with a list the same size as the one we started with.
// Clearly, the polygon cannot be constructed
throw new IllegalArgumentException("Polygon is illegal; cannot be decomposed into convex parts");
}
// Construct a sided plane based on these two points, and the previous point
SidedPlane newBoundary = new SidedPlane(currentList.get(currentList.size() - 2), currentList.get(0), currentList.get(currentList.size() - 1));
// We want the other side for the recursion
SidedPlane otherSideNewBoundary = new SidedPlane(newBoundary);
rval.addShape(buildPolygonShape(recursionList, recursionList.size() - 2, recursionList.size() - 1, otherSideNewBoundary, true));
recursionList.clear();
}
// Now, add in the current shape.
rval.addShape(new GeoConvexPolygon(currentList, internalEdgeList, returnEdgeInternalBoundary));
//System.out.println("Done creating polygon");
return rval;
}
protected static boolean isWithin(GeoPoint newPoint, List<SidedPlane> currentPlanes) {
for (SidedPlane p : currentPlanes) {
if (!p.isWithin(newPoint))
return false;
}
return true;
}
protected static int getLegalIndex(int index, int size) {
while (index < 0) {
index += size;
}
while (index >= size) {
index -= size;
}
return index;
}
}

View File

@ -0,0 +1,264 @@
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 four sides (top lat, bottom lat, left lon, right lon).
* The left-right maximum extent for this shape is PI; for anything larger, use
* GeoWideRectangle.
*
* @lucene.internal
*/
public class GeoRectangle extends GeoBBoxBase {
public final double topLat;
public final double bottomLat;
public final double leftLon;
public final double rightLon;
public final double cosMiddleLat;
public final GeoPoint ULHC;
public final GeoPoint URHC;
public final GeoPoint LRHC;
public final GeoPoint LLHC;
public final SidedPlane topPlane;
public final SidedPlane bottomPlane;
public final SidedPlane leftPlane;
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[] edgePoints;
/**
* Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI}
*/
public GeoRectangle(final double topLat, final double bottomLat, 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 (bottomLat > Math.PI * 0.5 || bottomLat < -Math.PI * 0.5)
throw new IllegalArgumentException("Bottom latitude out of range");
if (topLat < bottomLat)
throw new IllegalArgumentException("Top latitude less than bottom latitude");
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.bottomLat = bottomLat;
this.leftLon = leftLon;
this.rightLon = rightLon;
final double sinTopLat = Math.sin(topLat);
final double cosTopLat = Math.cos(topLat);
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.ULHC = new GeoPoint(sinTopLat, sinLeftLon, cosTopLat, cosLeftLon);
this.URHC = new GeoPoint(sinTopLat, sinRightLon, cosTopLat, cosRightLon);
this.LRHC = new GeoPoint(sinBottomLat, sinRightLon, cosBottomLat, cosRightLon);
this.LLHC = new GeoPoint(sinBottomLat, sinLeftLon, cosBottomLat, cosLeftLon);
final double middleLat = (topLat + 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.topPlane = new SidedPlane(centerPoint, sinTopLat);
this.bottomPlane = new SidedPlane(centerPoint, sinBottomLat);
this.leftPlane = new SidedPlane(centerPoint, cosLeftLon, sinLeftLon);
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};
}
@Override
public GeoBBox expand(final double angle) {
final double newTopLat = topLat + angle;
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 topPlane.isWithin(point) &&
bottomPlane.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) &&
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 topAngle = centerPoint.arcDistance(URHC);
final double bottomAngle = centerPoint.arcDistance(LLHC);
return Math.max(centerAngle, Math.max(topAngle, bottomAngle));
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
return p.intersects(topPlane, notablePoints, topPlanePoints, bounds, bottomPlane, leftPlane, rightPlane) ||
p.intersects(bottomPlane, notablePoints, bottomPlanePoints, bounds, topPlane, leftPlane, rightPlane) ||
p.intersects(leftPlane, notablePoints, leftPlanePoints, bounds, rightPlane, topPlane, bottomPlane) ||
p.intersects(rightPlane, notablePoints, rightPlanePoints, bounds, leftPlane, topPlane, 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.addLatitudeZone(topLat).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(ULHC);
if (insideRectangle == ALL_INSIDE && insideShape) {
//System.err.println(" inside of each other");
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) {
//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 GeoRectangle))
return false;
GeoRectangle other = (GeoRectangle) o;
return other.ULHC.equals(ULHC) && other.LRHC.equals(LRHC);
}
@Override
public int hashCode() {
int result = ULHC.hashCode();
result = 31 * result + LRHC.hashCode();
return result;
}
@Override
public String toString() {
return "GeoRectangle: {toplat=" + topLat + "(" + topLat * 180.0 / Math.PI + "), bottomlat=" + bottomLat + "(" + bottomLat * 180.0 / Math.PI + "), leftlon=" + leftLon + "(" + leftLon * 180.0 / Math.PI + "), rightlon=" + rightLon + "(" + rightLon * 180.0 / Math.PI + ")}";
}
}

View File

@ -0,0 +1,71 @@
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.
*/
/**
* Generic shape. This describes methods that help GeoAreas figure out
* how they interact with a shape, for the purposes of coming up with a
* set of geo hash values.
*
* @lucene.experimental
*/
public interface GeoShape extends Membership {
/**
* Return a sample point that is on the outside edge/boundary of the shape.
*
* @return samples of all edge points from distinct edge sections. Typically one point
* is returned, but zero or two are also possible.
*/
public GeoPoint[] getEdgePoints();
/**
* Assess whether a plane, within the provided bounds, intersects
* with the shape. Note well that this method is allowed to return "true"
* if there are internal edges of a composite shape which intersect the plane.
* Doing this can cause getRelationship() for most GeoBBox shapes to return
* OVERLAPS rather than the more correct CONTAINS, but that cannot be
* 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
* 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
* intersection must be within in order to qualify (provided by a GeoArea).
* @return true if there's such an intersection, false if not.
*/
public boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... 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.
*/
public Bounds getBounds(final Bounds bounds);
/**
* Equals
*/
public boolean equals(Object o);
}

View File

@ -0,0 +1,41 @@
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.
*/
/**
* Some shapes can compute radii of a geocircle in which they are inscribed.
*
* @lucene.experimental
*/
public interface GeoSizeable {
/**
* Returns the radius of a circle into which the GeoSizeable area can
* be inscribed.
*
* @return the radius.
*/
public double getRadius();
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
public GeoPoint getCenter();
}

View File

@ -0,0 +1,172 @@
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.
*
* @lucene.internal
*/
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;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return interiorPoint;
}
@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,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 (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.
*
* @lucene.internal
*/
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;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@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

@ -0,0 +1,233 @@
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.
*/
/**
* Degenerate bounding box wider than PI and limited on two sides (left lon, right lon).
*
* @lucene.internal
*/
public class GeoWideDegenerateHorizontalLine extends GeoBBoxBase {
public final double latitude;
public final double leftLon;
public final double rightLon;
public final GeoPoint LHC;
public final GeoPoint RHC;
public final Plane plane;
public final SidedPlane leftPlane;
public final SidedPlane rightPlane;
public final GeoPoint[] planePoints;
public final GeoPoint centerPoint;
public final EitherBound eitherBound;
public final GeoPoint[] edgePoints;
/**
* 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 GeoWideDegenerateHorizontalLine(final double latitude, final double leftLon, double rightLon) {
// Argument checking
if (latitude > Math.PI * 0.5 || latitude < -Math.PI * 0.5)
throw new IllegalArgumentException("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.latitude = latitude;
this.leftLon = leftLon;
this.rightLon = rightLon;
final double sinLatitude = Math.sin(latitude);
final double cosLatitude = Math.cos(latitude);
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 two points
this.LHC = new GeoPoint(sinLatitude, sinLeftLon, cosLatitude, cosLeftLon);
this.RHC = new GeoPoint(sinLatitude, sinRightLon, cosLatitude, cosRightLon);
this.plane = new Plane(sinLatitude);
// Normalize
while (leftLon > rightLon) {
rightLon += Math.PI * 2.0;
}
double middleLon = (leftLon + rightLon) * 0.5;
double sinMiddleLon = Math.sin(middleLon);
double cosMiddleLon = Math.cos(middleLon);
this.centerPoint = new GeoPoint(sinLatitude, sinMiddleLon, cosLatitude, cosMiddleLon);
this.leftPlane = new SidedPlane(centerPoint, cosLeftLon, sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint, cosRightLon, sinRightLon);
this.planePoints = new GeoPoint[]{LHC, RHC};
this.eitherBound = new EitherBound();
this.edgePoints = new GeoPoint[]{centerPoint};
}
@Override
public GeoBBox expand(final double angle) {
final double newTopLat = latitude + angle;
final double newBottomLat = latitude - 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) {
if (point == null)
return false;
return plane.evaluateIsZero(point) &&
(leftPlane.isWithin(point) ||
rightPlane.isWithin(point));
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return plane.evaluateIsZero(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 topAngle = centerPoint.arcDistance(RHC);
final double bottomAngle = centerPoint.arcDistance(LHC);
return Math.max(topAngle, bottomAngle);
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@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(plane, notablePoints, planePoints, bounds, eitherBound);
}
/**
* 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(latitude)
.addLongitudeSlice(leftLon, rightLon);
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
if (path.intersects(plane, planePoints, eitherBound)) {
return OVERLAPS;
}
if (path.isWithin(centerPoint)) {
return CONTAINS;
}
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoWideDegenerateHorizontalLine))
return false;
GeoWideDegenerateHorizontalLine other = (GeoWideDegenerateHorizontalLine) o;
return other.LHC.equals(LHC) && other.RHC.equals(RHC);
}
@Override
public int hashCode() {
int result = LHC.hashCode();
result = 31 * result + RHC.hashCode();
return result;
}
@Override
public String toString() {
return "GeoWideDegenerateHorizontalLine: {latitude=" + latitude + "(" + latitude * 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

@ -0,0 +1,200 @@
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 left and right sides (
* left lon, right lon).
*
* @lucene.internal
*/
public class GeoWideLongitudeSlice extends GeoBBoxBase {
public final double leftLon;
public final double rightLon;
public final SidedPlane leftPlane;
public final SidedPlane rightPlane;
public final static GeoPoint[] planePoints = new GeoPoint[]{NORTH_POLE, SOUTH_POLE};
public final GeoPoint centerPoint;
public final static GeoPoint[] edgePoints = new GeoPoint[]{NORTH_POLE};
/**
* Accepts only values in the following ranges: lon: {@code -PI -> PI}.
* Horizantal angle must be greater than or equal to PI.
*/
public GeoWideLongitudeSlice(final double leftLon, double rightLon) {
// Argument checking
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.leftLon = leftLon;
this.rightLon = rightLon;
final double sinLeftLon = Math.sin(leftLon);
final double cosLeftLon = Math.cos(leftLon);
final double sinRightLon = Math.sin(rightLon);
final double cosRightLon = Math.cos(rightLon);
// Normalize
while (leftLon > rightLon) {
rightLon += Math.PI * 2.0;
}
final double middleLon = (leftLon + rightLon) * 0.5;
this.centerPoint = new GeoPoint(0.0, middleLon);
this.leftPlane = new SidedPlane(centerPoint, cosLeftLon, sinLeftLon);
this.rightPlane = new SidedPlane(centerPoint, cosRightLon, sinRightLon);
}
@Override
public GeoBBox expand(final double 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(Math.PI * 0.5, -Math.PI * 0.5, newLeftLon, newRightLon);
}
@Override
public boolean isWithin(final Vector point) {
return leftPlane.isWithin(point) ||
rightPlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return leftPlane.isWithin(x, y, z) ||
rightPlane.isWithin(x, y, z);
}
@Override
public double getRadius() {
// Compute the extent and divide by two
double extent = rightLon - leftLon;
if (extent < 0.0)
extent += Math.PI * 2.0;
return Math.max(Math.PI * 0.5, extent * 0.5);
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@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(leftPlane, notablePoints, planePoints, bounds) ||
p.intersects(rightPlane, 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.noTopLatitudeBound().noBottomLatitudeBound();
bounds.addLongitudeSlice(leftLon, rightLon);
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(NORTH_POLE);
if (insideRectangle == ALL_INSIDE && insideShape)
return OVERLAPS;
if (path.intersects(leftPlane, planePoints) ||
path.intersects(rightPlane, planePoints))
return OVERLAPS;
if (insideRectangle == ALL_INSIDE)
return WITHIN;
if (insideShape)
return CONTAINS;
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoWideLongitudeSlice))
return false;
GeoWideLongitudeSlice other = (GeoWideLongitudeSlice) o;
return other.leftLon == leftLon && other.rightLon == rightLon;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(leftLon);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(rightLon);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "GeoWideLongitudeSlice: {leftlon=" + leftLon + "(" + leftLon * 180.0 / Math.PI + "), rightlon=" + rightLon + "(" + rightLon * 180.0 / Math.PI + ")}";
}
}

View File

@ -0,0 +1,268 @@
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).
*
* @lucene.internal
*/
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);
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@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

@ -0,0 +1,286 @@
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 four sides (top lat,
* bottom lat, left lon, right lon).
*
* @lucene.internal
*/
public class GeoWideRectangle extends GeoBBoxBase {
public final double topLat;
public final double bottomLat;
public final double leftLon;
public final double rightLon;
public final double cosMiddleLat;
public final GeoPoint ULHC;
public final GeoPoint URHC;
public final GeoPoint LRHC;
public final GeoPoint LLHC;
public final SidedPlane topPlane;
public final SidedPlane bottomPlane;
public final SidedPlane leftPlane;
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 EitherBound eitherBound;
public final GeoPoint[] edgePoints;
/**
* 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 GeoWideRectangle(final double topLat, final double bottomLat, 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 (bottomLat > Math.PI * 0.5 || bottomLat < -Math.PI * 0.5)
throw new IllegalArgumentException("Bottom latitude out of range");
if (topLat < bottomLat)
throw new IllegalArgumentException("Top latitude less than bottom latitude");
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.bottomLat = bottomLat;
this.leftLon = leftLon;
this.rightLon = rightLon;
final double sinTopLat = Math.sin(topLat);
final double cosTopLat = Math.cos(topLat);
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.ULHC = new GeoPoint(sinTopLat, sinLeftLon, cosTopLat, cosLeftLon);
this.URHC = new GeoPoint(sinTopLat, sinRightLon, cosTopLat, cosRightLon);
this.LRHC = new GeoPoint(sinBottomLat, sinRightLon, cosBottomLat, cosRightLon);
this.LLHC = new GeoPoint(sinBottomLat, sinLeftLon, cosBottomLat, cosLeftLon);
final double middleLat = (topLat + 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.topPlane = new SidedPlane(centerPoint, sinTopLat);
this.bottomPlane = new SidedPlane(centerPoint, sinBottomLat);
this.leftPlane = new SidedPlane(centerPoint, cosLeftLon, sinLeftLon);
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.edgePoints = new GeoPoint[]{ULHC};
}
@Override
public GeoBBox expand(final double angle) {
final double newTopLat = topLat + angle;
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 topPlane.isWithin(point) &&
bottomPlane.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) &&
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 topAngle = centerPoint.arcDistance(URHC);
final double bottomAngle = centerPoint.arcDistance(LLHC);
return Math.max(centerAngle, Math.max(topAngle, bottomAngle));
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@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, bottomPlane, eitherBound) ||
p.intersects(bottomPlane, notablePoints, bottomPlanePoints, bounds, topPlane, eitherBound) ||
p.intersects(leftPlane, notablePoints, leftPlanePoints, bounds, topPlane, bottomPlane) ||
p.intersects(rightPlane, notablePoints, rightPlanePoints, bounds, topPlane, 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.addLatitudeZone(topLat).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(ULHC);
if (insideRectangle == ALL_INSIDE && insideShape) {
//System.err.println(" both inside each other");
return OVERLAPS;
}
if (path.intersects(topPlane, topPlanePoints, bottomPlane, eitherBound) ||
path.intersects(bottomPlane, bottomPlanePoints, topPlane, eitherBound) ||
path.intersects(leftPlane, leftPlanePoints, topPlane, bottomPlane) ||
path.intersects(rightPlane, rightPlanePoints, topPlane, 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 GeoWideRectangle))
return false;
GeoWideRectangle other = (GeoWideRectangle) o;
return other.ULHC.equals(ULHC) && other.LRHC.equals(LRHC);
}
@Override
public int hashCode() {
int result = ULHC.hashCode();
result = 31 * result + LRHC.hashCode();
return result;
}
@Override
public String toString() {
return "GeoWideRectangle: {toplat=" + topLat + "(" + topLat * 180.0 / Math.PI + "), 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

@ -0,0 +1,264 @@
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).
*
* @lucene.internal
*/
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);
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
return centerPoint;
}
@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

@ -0,0 +1,115 @@
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 including the entire world.
*
* @lucene.internal
*/
public class GeoWorld extends GeoBBoxBase {
protected final static GeoPoint originPoint = new GeoPoint(1.0, 0.0, 0.0);
protected final static GeoPoint[] edgePoints = new GeoPoint[0];
public GeoWorld() {
}
@Override
public GeoBBox expand(final double angle) {
return this;
}
@Override
public double getRadius() {
return Math.PI;
}
/**
* Returns the center of a circle into which the area will be inscribed.
*
* @return the center.
*/
@Override
public GeoPoint getCenter() {
// Totally arbitrary
return originPoint;
}
@Override
public boolean isWithin(final Vector point) {
return true;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return true;
}
@Override
public GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
return false;
}
/**
* 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().noBottomLatitudeBound();
return bounds;
}
@Override
public int getRelationship(final GeoShape path) {
if (path.getEdgePoints().length > 0)
// Path is always within the world
return WITHIN;
return OVERLAPS;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof GeoWorld))
return false;
return true;
}
@Override
public int hashCode() {
return 0;
}
@Override
public String toString() {
return "GeoWorld";
}
}

View File

@ -0,0 +1,45 @@
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.
*/
/**
* Interface describing 3d shape membership methods.
*
* @lucene.experimental
*/
public interface Membership {
/**
* Check if a point is within this shape.
*
* @param point is the point to check.
* @return true if the point is within this shape
*/
public boolean isWithin(final Vector point);
/**
* Check if a point is within this shape.
*
* @param x is x coordinate of point to check.
* @param y is y coordinate of point to check.
* @param z is z coordinate of point to check.
* @return true if the point is within this shape
*/
public boolean isWithin(final double x, final double y, final double z);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,125 @@
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.
*/
/**
* Combination of a plane, and a sign value indicating what evaluation values are on the correct
* side of the plane.
*
* @lucene.experimental
*/
public class SidedPlane extends Plane implements Membership {
public final double sigNum;
/**
* Construct a SidedPlane identical to an existing one, but reversed.
*
* @param sidedPlane is the existing plane.
*/
public SidedPlane(SidedPlane sidedPlane) {
super(sidedPlane, sidedPlane.D);
this.sigNum = -sidedPlane.sigNum;
}
/**
* Construct a sided plane from a pair of vectors describing points, and including
* origin, plus a point p which describes the side.
*
* @param p point to evaluate
* @param A is the first in-plane point
* @param B is the second in-plane point
*/
public SidedPlane(Vector p, Vector A, Vector B) {
super(A, B);
sigNum = Math.signum(evaluate(p));
}
/**
* Construct a sided plane from a point and a Z coordinate.
*
* @param p point to evaluate.
* @param height is the Z coordinate of the plane.
*/
public SidedPlane(Vector p, double height) {
super(height);
sigNum = Math.signum(evaluate(p));
}
/**
* Construct a sided vertical plane from a point and specified x and y coordinates.
*
* @param p point to evaluate.
* @param x is the specified x.
* @param y is the specified y.
*/
public SidedPlane(Vector p, double x, double y) {
super(x, y);
sigNum = Math.signum(evaluate(p));
}
/**
* Construct a sided plane with a normal vector and offset.
*
* @param p point to evaluate.
* @param v is the normal vector.
* @param D is the origin offset for the plan.
*/
public SidedPlane(Vector p, Vector v, double D) {
super(v, D);
sigNum = Math.signum(evaluate(p));
}
/**
* Check if a point is within this shape.
*
* @param point is the point to check.
* @return true if the point is within this shape
*/
@Override
public boolean isWithin(Vector point) {
double evalResult = evaluate(point);
if (Math.abs(evalResult) < MINIMUM_RESOLUTION)
return true;
double sigNum = Math.signum(evalResult);
return sigNum == this.sigNum;
}
/**
* Check if a point is within this shape.
*
* @param x is x coordinate of point to check.
* @param y is y coordinate of point to check.
* @param z is z coordinate of point to check.
* @return true if the point is within this shape
*/
@Override
public boolean isWithin(double x, double y, double z) {
double evalResult = evaluate(x, y, z);
if (Math.abs(evalResult) < MINIMUM_RESOLUTION)
return true;
double sigNum = Math.signum(evalResult);
return sigNum == this.sigNum;
}
@Override
public String toString() {
return "[A=" + x + ", B=" + y + ", C=" + z + ", D=" + D + ", side=" + sigNum + "]";
}
}

View File

@ -0,0 +1,42 @@
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.
*/
/**
* Static methods globally useful for 3d geometric work.
*
* @lucene.experimental
*/
public class Tools {
private Tools() {
}
/**
* Java acos yields a NAN if you take an arc-cos of an
* angle that's just a tiny bit greater than 1.0, so
* here's a more resilient version.
*/
public static double safeAcos(double value) {
if (value > 1.0)
value = 1.0;
else if (value < -1.0)
value = -1.0;
return Math.acos(value);
}
}

View File

@ -0,0 +1,327 @@
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.
*/
/**
* A 3d vector in space, not necessarily
* going through the origin.
*
* @lucene.experimental
*/
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-12;
/**
* For squared quantities, the bound is squared too.
*/
public static final double MINIMUM_RESOLUTION_SQUARED = MINIMUM_RESOLUTION * MINIMUM_RESOLUTION;
public final double x;
public final double y;
public final double z;
/**
* Construct from (U.S.) x,y,z coordinates.
*/
public Vector(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
/**
* Construct a vector that is perpendicular to
* two other (non-zero) vectors. If the vectors are parallel,
* the result vector will have magnitude 0.
*
* @param A is the first vector
* @param B is the second
*/
public Vector(final Vector A, final Vector B) {
// x = u2v3 - u3v2
// y = u3v1 - u1v3
// z = u1v2 - u2v1
this(A.y * B.z - A.z * B.y,
A.z * B.x - A.x * B.z,
A.x * B.y - A.y * B.x);
}
/**
* Compute a normalized unit vector based on the current vector.
*
* @return the normalized vector, or null if the current vector has
* a magnitude of zero.
*/
public Vector normalize() {
double denom = magnitude();
if (denom < MINIMUM_RESOLUTION)
// Degenerate, can't normalize
return null;
double normFactor = 1.0 / denom;
return new Vector(x * normFactor, y * normFactor, z * normFactor);
}
/**
* Do a dot product.
*
* @param v is the vector to multiply.
* @return the result.
*/
public double dotProduct(final Vector v) {
return this.x * v.x + this.y * v.y + this.z * v.z;
}
/**
* Do a dot product.
*
* @param x is the x value of the vector to multiply.
* @param y is the y value of the vector to multiply.
* @param z is the z value of the vector to multiply.
* @return the result.
*/
public double dotProduct(final double x, final double y, final double z) {
return this.x * x + this.y * y + this.z * z;
}
/**
* Determine if this vector, taken from the origin,
* describes a point within a set of planes.
*
* @param bounds is the first part of the set of planes.
* @param moreBounds is the second part of the set of planes.
* @return true if the point is within the bounds.
*/
public boolean isWithin(final Membership[] bounds, final Membership[] moreBounds) {
// Return true if the point described is within all provided bounds
for (Membership bound : bounds) {
if (bound != null && !bound.isWithin(this))
return false;
}
for (Membership bound : moreBounds) {
if (bound != null && !bound.isWithin(this))
return false;
}
return true;
}
/**
* Translate vector.
*/
public Vector translate(final double xOffset, final double yOffset, final double zOffset) {
return new Vector(x - xOffset, y - yOffset, z - zOffset);
}
/**
* Rotate vector counter-clockwise in x-y by an angle.
*/
public Vector rotateXY(final double angle) {
return rotateXY(Math.sin(angle), Math.cos(angle));
}
/**
* Rotate vector counter-clockwise in x-y by an angle, expressed as sin and cos.
*/
public Vector rotateXY(final double sinAngle, final double cosAngle) {
return new Vector(x * cosAngle - y * sinAngle, x * sinAngle + y * cosAngle, z);
}
/**
* Rotate vector counter-clockwise in x-z by an angle.
*/
public Vector rotateXZ(final double angle) {
return rotateXZ(Math.sin(angle), Math.cos(angle));
}
/**
* Rotate vector counter-clockwise in x-z by an angle, expressed as sin and cos.
*/
public Vector rotateXZ(final double sinAngle, final double cosAngle) {
return new Vector(x * cosAngle - z * sinAngle, y, x * sinAngle + z * cosAngle);
}
/**
* Rotate vector counter-clockwise in z-y by an angle.
*/
public Vector rotateZY(final double angle) {
return rotateZY(Math.sin(angle), Math.cos(angle));
}
/**
* Rotate vector counter-clockwise in z-y by an angle, expressed as sin and cos.
*/
public Vector rotateZY(final double sinAngle, final double cosAngle) {
return new Vector(x, z * sinAngle + y * cosAngle, z * cosAngle - y * sinAngle);
}
/**
* Compute the square of a straight-line distance to a point described by the
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI.
*
* @param v is the vector to compute a distance to.
* @return the square of the linear distance.
*/
public double linearDistanceSquared(final Vector v) {
double deltaX = this.x - v.x;
double deltaY = this.y - v.y;
double deltaZ = this.z - v.z;
return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
}
/**
* Compute the square of a straight-line distance to a point described by the
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI.
*
* @param x is the x part of the vector to compute a distance to.
* @param y is the y part of the vector to compute a distance to.
* @param z is the z part of the vector to compute a distance to.
* @return the square of the linear distance.
*/
public double linearDistanceSquared(final double x, final double y, final double z) {
double deltaX = this.x - x;
double deltaY = this.y - y;
double deltaZ = this.z - z;
return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
}
/**
* Compute the straight-line distance to a point described by the
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI.
*
* @param v is the vector to compute a distance to.
* @return the linear distance.
*/
public double linearDistance(final Vector v) {
return Math.sqrt(linearDistanceSquared(v));
}
/**
* Compute the straight-line distance to a point described by the
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI.
*
* @param x is the x part of the vector to compute a distance to.
* @param y is the y part of the vector to compute a distance to.
* @param z is the z part of the vector to compute a distance to.
* @return the linear distance.
*/
public double linearDistance(final double x, final double y, final double z) {
return Math.sqrt(linearDistanceSquared(x, y, z));
}
/**
* Compute the square of the normal distance to a vector described by a
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI/2.
*
* @param v is the vector to compute a distance to.
* @return the square of the normal distance.
*/
public double normalDistanceSquared(final Vector v) {
double t = dotProduct(v);
double deltaX = this.x * t - v.x;
double deltaY = this.y * t - v.y;
double deltaZ = this.z * t - v.z;
return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
}
/**
* Compute the square of the normal distance to a vector described by a
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI/2.
*
* @param x is the x part of the vector to compute a distance to.
* @param y is the y part of the vector to compute a distance to.
* @param z is the z part of the vector to compute a distance to.
* @return the square of the normal distance.
*/
public double normalDistanceSquared(final double x, final double y, final double z) {
double t = dotProduct(x, y, z);
double deltaX = this.x * t - x;
double deltaY = this.y * t - y;
double deltaZ = this.z * t - z;
return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
}
/**
* Compute the normal (perpendicular) distance to a vector described by a
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI/2.
*
* @param v is the vector to compute a distance to.
* @return the normal distance.
*/
public double normalDistance(final Vector v) {
return Math.sqrt(normalDistanceSquared(v));
}
/**
* Compute the normal (perpendicular) distance to a vector described by a
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI/2.
*
* @param x is the x part of the vector to compute a distance to.
* @param y is the y part of the vector to compute a distance to.
* @param z is the z part of the vector to compute a distance to.
* @return the normal distance.
*/
public double normalDistance(final double x, final double y, final double z) {
return Math.sqrt(normalDistanceSquared(x, y, z));
}
/**
* Compute the magnitude of this vector.
*
* @return the magnitude.
*/
public double magnitude() {
return Math.sqrt(x * x + y * y + z * z);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Vector))
return false;
Vector other = (Vector) o;
return (other.x == x && other.y == y && other.z == z);
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(x);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(y);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(z);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "[X=" + x + ", Y=" + y + ", Z=" + z + "]";
}
}

View File

@ -0,0 +1,21 @@
/*
* 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.
*/
/**
* Shapes implemented using 3D planar geometry.
*/
package org.apache.lucene.spatial.spatial4j.geo3d;

View File

@ -0,0 +1,19 @@
/*
* 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.
*/
/** Spatial4j stuff that ideally belongs in Spatial4j (isn't related to Lucene). */
package org.apache.lucene.spatial.spatial4j;

View File

@ -113,12 +113,12 @@ public abstract class RandomSpatialOpStrategyTestCase extends StrategyTestCase {
for (SearchResult result : got.results) {
String id = result.getId();
if (!remainingExpectedIds.remove(id)) {
fail("Shouldn't match", id, indexedShapes, queryShape, operation);
fail("qIdx:" + queryIdx + " Shouldn't match", id, indexedShapes, queryShape, operation);
}
}
if (!remainingExpectedIds.isEmpty()) {
String id = remainingExpectedIds.iterator().next();
fail("Should have matched", id, indexedShapes, queryShape, operation);
fail("qIdx:" + queryIdx + " Should have matched", id, indexedShapes, queryShape, operation);
}
}
}

View File

@ -0,0 +1,207 @@
package org.apache.lucene.spatial.spatial4j;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.spatial.composite.CompositeSpatialStrategy;
import org.apache.lucene.spatial.prefix.RandomSpatialOpStrategyTestCase;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoBBoxFactory;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoCircle;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoPath;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoPoint;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoPolygonFactory;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoShape;
import org.junit.Test;
import static com.spatial4j.core.distance.DistanceUtils.DEGREES_TO_RADIANS;
public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
private SpatialPrefixTree grid;
private RecursivePrefixTreeStrategy rptStrategy;
{
this.ctx = SpatialContext.GEO;
}
private void setupGeohashGrid() {
this.grid = new GeohashPrefixTree(ctx, 2);//A fairly shallow grid
this.rptStrategy = newRPT();
}
protected RecursivePrefixTreeStrategy newRPT() {
final RecursivePrefixTreeStrategy rpt = new RecursivePrefixTreeStrategy(this.grid,
getClass().getSimpleName() + "_rpt");
rpt.setDistErrPct(0.10);//not too many cells
return rpt;
}
@Override
protected boolean needsDocValues() {
return true;//due to SerializedDVStrategy
}
private void setupStrategy() {
//setup
setupGeohashGrid();
SerializedDVStrategy serializedDVStrategy = new SerializedDVStrategy(ctx, getClass().getSimpleName() + "_sdv");
this.strategy = new CompositeSpatialStrategy("composite_" + getClass().getSimpleName(),
rptStrategy, serializedDVStrategy);
}
@Test
public void testFailure1() throws IOException {
setupStrategy();
final List<GeoPoint> points = new ArrayList<GeoPoint>();
points.add(new GeoPoint(18 * DEGREES_TO_RADIANS, -27 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(-57 * DEGREES_TO_RADIANS, 146 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(14 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(-15 * DEGREES_TO_RADIANS, 153 * DEGREES_TO_RADIANS));
final Shape triangle = new Geo3dShape(GeoPolygonFactory.makeGeoPolygon(points,0),ctx);
final Rectangle rect = ctx.makeRectangle(-49, -45, 73, 86);
testOperation(rect,SpatialOperation.Intersects,triangle, false);
}
@Test
@Repeat(iterations = 10)
public void testOperations() throws IOException {
setupStrategy();
testOperationRandomShapes(SpatialOperation.Intersects);
}
private Shape makeTriangle(double x1, double y1, double x2, double y2, double x3, double y3) {
final List<GeoPoint> geoPoints = new ArrayList<>();
geoPoints.add(new GeoPoint(y1 * DEGREES_TO_RADIANS, x1 * DEGREES_TO_RADIANS));
geoPoints.add(new GeoPoint(y2 * DEGREES_TO_RADIANS, x2 * DEGREES_TO_RADIANS));
geoPoints.add(new GeoPoint(y3 * DEGREES_TO_RADIANS, x3 * DEGREES_TO_RADIANS));
final int convexPointIndex = 0;
final GeoShape shape = GeoPolygonFactory.makeGeoPolygon(geoPoints, convexPointIndex);
return new Geo3dShape(shape, ctx);
}
@Override
protected Shape randomIndexedShape() {
return randomRectangle();
}
@Override
protected Shape randomQueryShape() {
final int shapeType = random().nextInt(4);
switch (shapeType) {
case 0: {
// Polygons
final int vertexCount = random().nextInt(3) + 3;
while (true) {
final List<GeoPoint> geoPoints = new ArrayList<>();
while (geoPoints.size() < vertexCount) {
final Point point = randomPoint();
final GeoPoint gPt = new GeoPoint(point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS);
geoPoints.add(gPt);
}
final int convexPointIndex = random().nextInt(vertexCount); //If we get this wrong, hopefully we get IllegalArgumentException
try {
final GeoShape shape = GeoPolygonFactory.makeGeoPolygon(geoPoints, convexPointIndex);
return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
case 1: {
// Circles
while (true) {
final int circleRadius = random().nextInt(179) + 1;
final Point point = randomPoint();
try {
final GeoShape shape = new GeoCircle(point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS,
circleRadius * DEGREES_TO_RADIANS);
return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
case 2: {
// Rectangles
while (true) {
Point ulhcPoint = randomPoint();
Point lrhcPoint = randomPoint();
if (ulhcPoint.getY() < lrhcPoint.getY()) {
//swap
Point temp = ulhcPoint;
ulhcPoint = lrhcPoint;
lrhcPoint = temp;
}
try {
final GeoShape shape = GeoBBoxFactory.makeGeoBBox(ulhcPoint.getY() * DEGREES_TO_RADIANS,
lrhcPoint.getY() * DEGREES_TO_RADIANS,
ulhcPoint.getX() * DEGREES_TO_RADIANS,
lrhcPoint.getX() * DEGREES_TO_RADIANS);
//System.err.println("Trial rectangle shape: "+shape);
return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
case 3: {
// Paths
final int pointCount = random().nextInt(5) + 1;
final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS;
while (true) {
try {
final GeoPath path = new GeoPath(width);
for (int i = 0; i < pointCount; i++) {
final Point nextPoint = randomPoint();
path.addPoint(nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS);
}
path.done();
return new Geo3dShape(path, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
default:
throw new IllegalStateException("Unexpected shape type");
}
}
}

View File

@ -0,0 +1,263 @@
package org.apache.lucene.spatial.spatial4j;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.carrotsearch.randomizedtesting.RandomizedContext;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Point;
import org.apache.lucene.spatial.spatial4j.geo3d.Bounds;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoArea;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoBBox;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoBBoxFactory;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoCircle;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoPath;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoPoint;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoPolygonFactory;
import org.apache.lucene.spatial.spatial4j.geo3d.GeoShape;
import org.junit.Rule;
import org.junit.Test;
import static com.spatial4j.core.distance.DistanceUtils.DEGREES_TO_RADIANS;
public class Geo3dShapeRectRelationTest extends RandomizedShapeTestCase {
@Rule
public final LogRule testLog = LogRule.instance;
static Random random() {
return RandomizedContext.current().getRandom();
}
{
ctx = SpatialContext.GEO;
}
protected final static double RADIANS_PER_DEGREE = Math.PI/180.0;
@Test
public void testFailure1() {
final GeoBBox rect = GeoBBoxFactory.makeGeoBBox(88 * RADIANS_PER_DEGREE, 30 * RADIANS_PER_DEGREE, -30 * RADIANS_PER_DEGREE, 62 * RADIANS_PER_DEGREE);
final List<GeoPoint> points = new ArrayList<GeoPoint>();
points.add(new GeoPoint(66.2465299717 * RADIANS_PER_DEGREE, -29.1786158537 * RADIANS_PER_DEGREE));
points.add(new GeoPoint(43.684447915 * RADIANS_PER_DEGREE, 46.2210986329 * RADIANS_PER_DEGREE));
points.add(new GeoPoint(30.4579218227 * RADIANS_PER_DEGREE, 14.5238410082 * RADIANS_PER_DEGREE));
final GeoShape path = GeoPolygonFactory.makeGeoPolygon(points,0);
final GeoPoint point = new GeoPoint(34.2730264413182 * RADIANS_PER_DEGREE, 82.75500168892472 * RADIANS_PER_DEGREE);
// Apparently the rectangle thinks the polygon is completely within it... "shape inside rectangle"
assertTrue(GeoArea.WITHIN == rect.getRelationship(path));
// Point is within path? Apparently not...
assertFalse(path.isWithin(point));
// If it is within the path, it must be within the rectangle, and similarly visa versa
assertFalse(rect.isWithin(point));
}
protected static GeoBBox getBoundingBox(final GeoShape path) {
Bounds bounds = path.getBounds(null);
double leftLon;
double rightLon;
if (bounds.checkNoLongitudeBound()) {
leftLon = -Math.PI;
rightLon = Math.PI;
} else {
leftLon = bounds.getLeftLongitude().doubleValue();
rightLon = bounds.getRightLongitude().doubleValue();
}
double minLat;
if (bounds.checkNoBottomLatitudeBound()) {
minLat = -Math.PI * 0.5;
} else {
minLat = bounds.getMinLatitude().doubleValue();
}
double maxLat;
if (bounds.checkNoTopLatitudeBound()) {
maxLat = Math.PI * 0.5;
} else {
maxLat = bounds.getMaxLatitude().doubleValue();
}
return GeoBBoxFactory.makeGeoBBox(maxLat, minLat, leftLon, rightLon);
}
@Test
public void testGeoCircleRect() {
new RectIntersectionTestHelper<Geo3dShape>(ctx) {
@Override
protected Geo3dShape generateRandomShape(Point nearP) {
while (true) {
final int circleRadius = random().nextInt(179) + 1;//no 0-radius
final Point point = nearP;
try {
final GeoShape shape = new GeoCircle(point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS,
circleRadius * DEGREES_TO_RADIANS);
return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
@Override
protected Point randomPointInEmptyShape(Geo3dShape shape) {
GeoPoint geoPoint = ((GeoCircle)shape.shape).center;
return geoPointToSpatial4jPoint(geoPoint);
}
}.testRelateWithRectangle();
}
@Test
public void testGeoBBoxRect() {
new RectIntersectionTestHelper<Geo3dShape>(ctx) {
@Override
protected boolean isRandomShapeRectangular() {
return true;
}
@Override
protected Geo3dShape generateRandomShape(Point nearP) {
// (ignoring nearP)
Point ulhcPoint = randomPoint();
Point lrhcPoint = randomPoint();
if (ulhcPoint.getY() < lrhcPoint.getY()) {
//swap
Point temp = ulhcPoint;
ulhcPoint = lrhcPoint;
lrhcPoint = temp;
}
final GeoShape shape = GeoBBoxFactory.makeGeoBBox(ulhcPoint.getY() * DEGREES_TO_RADIANS,
lrhcPoint.getY() * DEGREES_TO_RADIANS,
ulhcPoint.getX() * DEGREES_TO_RADIANS,
lrhcPoint.getX() * DEGREES_TO_RADIANS);
return new Geo3dShape(shape, ctx);
}
@Override
protected Point randomPointInEmptyShape(Geo3dShape shape) {
return shape.getBoundingBox().getCenter();
}
}.testRelateWithRectangle();
}
@Test
public void testGeoPolygonRect() {
new RectIntersectionTestHelper<Geo3dShape>(ctx) {
@Override
protected Geo3dShape generateRandomShape(Point nearP) {
final Point centerPoint = randomPoint();
final int maxDistance = random().nextInt(160) + 20;
final int vertexCount = random().nextInt(3) + 3;
while (true) {
final List<GeoPoint> geoPoints = new ArrayList<>();
while (geoPoints.size() < vertexCount) {
final Point point = randomPoint();
if (ctx.getDistCalc().distance(point,centerPoint) > maxDistance)
continue;
final GeoPoint gPt = new GeoPoint(point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS);
geoPoints.add(gPt);
}
final int convexPointIndex = random().nextInt(vertexCount); //If we get this wrong, hopefully we get IllegalArgumentException
try {
final GeoShape shape = GeoPolygonFactory.makeGeoPolygon(geoPoints, convexPointIndex);
return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
@Override
protected Point randomPointInEmptyShape(Geo3dShape shape) {
throw new IllegalStateException("unexpected; need to finish test code");
}
@Override
protected int getWithinMinimum(int laps) {
// Long/thin so only 10% of the usual figure
return laps/10000;
}
}.testRelateWithRectangle();
}
@Test
public void testGeoPathRect() {
new RectIntersectionTestHelper<Geo3dShape>(ctx) {
@Override
protected Geo3dShape generateRandomShape(Point nearP) {
final Point centerPoint = randomPoint();
final int maxDistance = random().nextInt(160) + 20;
final int pointCount = random().nextInt(5) + 1;
final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS;
while (true) {
try {
final GeoPath path = new GeoPath(width);
int i = 0;
while (i < pointCount) {
final Point nextPoint = randomPoint();
if (ctx.getDistCalc().distance(nextPoint,centerPoint) > maxDistance)
continue;
path.addPoint(nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS);
i++;
}
path.done();
return new Geo3dShape(path, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
@Override
protected Point randomPointInEmptyShape(Geo3dShape shape) {
throw new IllegalStateException("unexpected; need to finish test code");
}
@Override
protected int getWithinMinimum(int laps) {
// Long/thin so only 10% of the usual figure
return laps/10000;
}
}.testRelateWithRectangle();
}
private Point geoPointToSpatial4jPoint(GeoPoint geoPoint) {
return ctx.makePoint(geoPoint.x * DistanceUtils.RADIANS_TO_DEGREES,
geoPoint.y * DistanceUtils.RADIANS_TO_DEGREES);
}
}

View File

@ -0,0 +1,83 @@
package org.apache.lucene.spatial.spatial4j;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter;
/**
* A utility logger for tests in which log statements are logged following
* test failure only. Add this to a JUnit based test class with a {@link org.junit.Rule}
* annotation.
*/
public class LogRule extends TestRuleAdapter {
//TODO does this need to be threadsafe (such as via thread-local state)?
private static ArrayList<LogEntry> logStack = new ArrayList<LogEntry>();
private static final int MAX_LOGS = 1000;
public static final LogRule instance = new LogRule();
private LogRule() {}
@Override
protected void before() throws Throwable {
logStack.clear();
}
@Override
protected void afterAlways(List<Throwable> errors) throws Throwable {
if (!errors.isEmpty())
logThenClear();
}
private void logThenClear() {
for (LogEntry entry : logStack) {
//no SLF4J in Lucene... fallback to this
if (entry.args != null && entry.args.length > 0) {
System.out.println(entry.msg + " " + Arrays.asList(entry.args) + "(no slf4j subst; sorry)");
} else {
System.out.println(entry.msg);
}
}
logStack.clear();
}
public static void clear() {
logStack.clear();
}
/**
* Enqueues a log message with substitution arguments ala SLF4J (i.e. {} syntax).
* If the test fails then it'll be logged then, otherwise it'll be forgotten.
*/
public static void log(String msg, Object... args) {
if (logStack.size() > MAX_LOGS) {
throw new RuntimeException("Too many log statements: "+logStack.size() + " > "+MAX_LOGS);
}
LogEntry entry = new LogEntry();
entry.msg = msg;
entry.args = args;
logStack.add(entry);
}
private static class LogEntry { String msg; Object[] args; }
}

View File

@ -0,0 +1,286 @@
package org.apache.lucene.spatial.spatial4j;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.carrotsearch.randomizedtesting.RandomizedTest;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.shape.impl.Range;
import static com.spatial4j.core.shape.SpatialRelation.CONTAINS;
import static com.spatial4j.core.shape.SpatialRelation.WITHIN;
/**
* A base test class with utility methods to help test shapes.
* Extends from RandomizedTest.
*/
public abstract class RandomizedShapeTestCase extends RandomizedTest {
protected static final double EPS = 10e-9;
protected SpatialContext ctx;//needs to be set ASAP
/** Used to reduce the space of numbers to increase the likelihood that
* random numbers become equivalent, and thus trigger different code paths.
* Also makes some random shapes easier to manually examine.
*/
protected final double DIVISIBLE = 2;// even coordinates; (not always used)
protected RandomizedShapeTestCase() {
}
public RandomizedShapeTestCase(SpatialContext ctx) {
this.ctx = ctx;
}
@SuppressWarnings("unchecked")
public static void checkShapesImplementEquals( Class<?>[] classes ) {
for( Class<?> clazz : classes ) {
try {
clazz.getDeclaredMethod( "equals", Object.class );
} catch (Exception e) {
fail("Shape needs to define 'equals' : " + clazz.getName());
}
try {
clazz.getDeclaredMethod( "hashCode" );
} catch (Exception e) {
fail("Shape needs to define 'hashCode' : " + clazz.getName());
}
}
}
//These few norm methods normalize the arguments for creating a shape to
// account for the dateline. Some tests loop past the dateline or have offsets
// that go past it and it's easier to have them coded that way and correct for
// it here. These norm methods should be used when needed, not frivolously.
protected double normX(double x) {
return ctx.isGeo() ? DistanceUtils.normLonDEG(x) : x;
}
protected double normY(double y) {
return ctx.isGeo() ? DistanceUtils.normLatDEG(y) : y;
}
protected Rectangle makeNormRect(double minX, double maxX, double minY, double maxY) {
if (ctx.isGeo()) {
if (Math.abs(maxX - minX) >= 360) {
minX = -180;
maxX = 180;
} else {
minX = DistanceUtils.normLonDEG(minX);
maxX = DistanceUtils.normLonDEG(maxX);
}
} else {
if (maxX < minX) {
double t = minX;
minX = maxX;
maxX = t;
}
minX = boundX(minX, ctx.getWorldBounds());
maxX = boundX(maxX, ctx.getWorldBounds());
}
if (maxY < minY) {
double t = minY;
minY = maxY;
maxY = t;
}
minY = boundY(minY, ctx.getWorldBounds());
maxY = boundY(maxY, ctx.getWorldBounds());
return ctx.makeRectangle(minX, maxX, minY, maxY);
}
public static double divisible(double v, double divisible) {
return (int) (Math.round(v / divisible) * divisible);
}
protected double divisible(double v) {
return divisible(v, DIVISIBLE);
}
/** reset()'s p, and confines to world bounds. Might not be divisible if
* the world bound isn't divisible too.
*/
protected Point divisible(Point p) {
Rectangle bounds = ctx.getWorldBounds();
double newX = boundX( divisible(p.getX()), bounds );
double newY = boundY( divisible(p.getY()), bounds );
p.reset(newX, newY);
return p;
}
static double boundX(double i, Rectangle bounds) {
return bound(i, bounds.getMinX(), bounds.getMaxX());
}
static double boundY(double i, Rectangle bounds) {
return bound(i, bounds.getMinY(), bounds.getMaxY());
}
static double bound(double i, double min, double max) {
if (i < min) return min;
if (i > max) return max;
return i;
}
protected void assertRelation(SpatialRelation expected, Shape a, Shape b) {
assertRelation(null, expected, a, b);
}
protected void assertRelation(String msg, SpatialRelation expected, Shape a, Shape b) {
_assertIntersect(msg, expected, a, b);
//check flipped a & b w/ transpose(), while we're at it
_assertIntersect(msg, expected.transpose(), b, a);
}
private void _assertIntersect(String msg, SpatialRelation expected, Shape a, Shape b) {
SpatialRelation sect = a.relate(b);
if (sect == expected)
return;
msg = ((msg == null) ? "" : msg+"\r") + a +" intersect "+b;
if (expected == WITHIN || expected == CONTAINS) {
if (a.getClass().equals(b.getClass())) // they are the same shape type
assertEquals(msg,a,b);
else {
//they are effectively points or lines that are the same location
assertTrue(msg,!a.hasArea());
assertTrue(msg,!b.hasArea());
Rectangle aBBox = a.getBoundingBox();
Rectangle bBBox = b.getBoundingBox();
if (aBBox.getHeight() == 0 && bBBox.getHeight() == 0
&& (aBBox.getMaxY() == 90 && bBBox.getMaxY() == 90
|| aBBox.getMinY() == -90 && bBBox.getMinY() == -90))
;//== a point at the pole
else
assertEquals(msg, aBBox, bBBox);
}
} else {
assertEquals(msg,expected,sect);//always fails
}
}
protected void assertEqualsRatio(String msg, double expected, double actual) {
double delta = Math.abs(actual - expected);
double base = Math.min(actual, expected);
double deltaRatio = base==0 ? delta : Math.min(delta,delta / base);
assertEquals(msg,0,deltaRatio, EPS);
}
protected int randomIntBetweenDivisible(int start, int end) {
return randomIntBetweenDivisible(start, end, (int)DIVISIBLE);
}
/** Returns a random integer between [start, end]. Integers between must be divisible by the 3rd argument. */
protected int randomIntBetweenDivisible(int start, int end, int divisible) {
// DWS: I tested this
int divisStart = (int) Math.ceil( (start+1) / (double)divisible );
int divisEnd = (int) Math.floor( (end-1) / (double)divisible );
int divisRange = Math.max(0,divisEnd - divisStart + 1);
int r = randomInt(1 + divisRange);//remember that '0' is counted
if (r == 0)
return start;
if (r == 1)
return end;
return (r-2 + divisStart)*divisible;
}
protected Rectangle randomRectangle(Point nearP) {
Rectangle bounds = ctx.getWorldBounds();
if (nearP == null)
nearP = randomPointIn(bounds);
Range xRange = randomRange(rarely() ? 0 : nearP.getX(), Range.xRange(bounds, ctx));
Range yRange = randomRange(rarely() ? 0 : nearP.getY(), Range.yRange(bounds, ctx));
return makeNormRect(
divisible(xRange.getMin()),
divisible(xRange.getMax()),
divisible(yRange.getMin()),
divisible(yRange.getMax()) );
}
private Range randomRange(double near, Range bounds) {
double mid = near + randomGaussian() * bounds.getWidth() / 6;
double width = Math.abs(randomGaussian()) * bounds.getWidth() / 6;//1/3rd
return new Range(mid - width / 2, mid + width / 2);
}
private double randomGaussianZeroTo(double max) {
if (max == 0)
return max;
assert max > 0;
double r;
do {
r = Math.abs(randomGaussian()) * (max * 0.50);
} while (r > max);
return r;
}
protected Rectangle randomRectangle(int divisible) {
double rX = randomIntBetweenDivisible(-180, 180, divisible);
double rW = randomIntBetweenDivisible(0, 360, divisible);
double rY1 = randomIntBetweenDivisible(-90, 90, divisible);
double rY2 = randomIntBetweenDivisible(-90, 90, divisible);
double rYmin = Math.min(rY1,rY2);
double rYmax = Math.max(rY1,rY2);
if (rW > 0 && rX == 180)
rX = -180;
return makeNormRect(rX, rX + rW, rYmin, rYmax);
}
protected Point randomPoint() {
return randomPointIn(ctx.getWorldBounds());
}
protected Point randomPointIn(Circle c) {
double d = c.getRadius() * randomDouble();
double angleDEG = 360 * randomDouble();
Point p = ctx.getDistCalc().pointOnBearing(c.getCenter(), d, angleDEG, ctx, null);
assertEquals(CONTAINS,c.relate(p));
return p;
}
protected Point randomPointIn(Rectangle r) {
double x = r.getMinX() + randomDouble()*r.getWidth();
double y = r.getMinY() + randomDouble()*r.getHeight();
x = normX(x);
y = normY(y);
Point p = ctx.makePoint(x,y);
assertEquals(CONTAINS,r.relate(p));
return p;
}
protected Point randomPointIn(Shape shape) {
if (!shape.hasArea())// or try the center?
throw new UnsupportedOperationException("Need area to define shape!");
Rectangle bbox = shape.getBoundingBox();
Point p;
do {
p = randomPointIn(bbox);
} while (!bbox.relate(p).intersects());
return p;
}
}

View File

@ -0,0 +1,229 @@
package org.apache.lucene.spatial.spatial4j;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.shape.impl.InfBufLine;
import com.spatial4j.core.shape.impl.PointImpl;
import static com.spatial4j.core.shape.SpatialRelation.CONTAINS;
import static com.spatial4j.core.shape.SpatialRelation.DISJOINT;
public abstract class RectIntersectionTestHelper<S extends Shape> extends RandomizedShapeTestCase {
public RectIntersectionTestHelper(SpatialContext ctx) {
super(ctx);
}
/** Override to return true if generateRandomShape is essentially a Rectangle. */
protected boolean isRandomShapeRectangular() {
return false;
}
protected abstract S generateRandomShape(Point nearP);
/** shape has no area; return a point in it */
protected abstract Point randomPointInEmptyShape(S shape);
// Minimum distribution of relationships
// Each shape has different characteristics, so we don't expect (for instance) shapes that
// are likely to be long and thin to contain as many rectangles as those that
// short and fat.
protected int getContainsMinimum(int laps) {
return laps/1000;
}
protected int getIntersectsMinimum(int laps) {
return laps/1000;
}
protected int getWithinMinimum(int laps) {
return laps/1000;
}
protected int getDisjointMinimum(int laps) {
return laps/1000;
}
protected int getBoundingMinimum(int laps) {
return laps/1000;
}
@SuppressWarnings("unchecked")
@Override
protected Point randomPointIn(Shape shape) {
if (!shape.hasArea()) {
final Point pt = randomPointInEmptyShape((S) shape);
assert shape.relate(pt).intersects() : "faulty randomPointInEmptyShape";
return pt;
}
return super.randomPointIn(shape);
}
public void testRelateWithRectangle() {
//counters for the different intersection cases
int i_C = 0, i_I = 0, i_W = 0, i_D = 0, i_bboxD = 0;
int laps = 0;
final int MINLAPS = scaledRandomIntBetween(20000, 200000);
while(i_C < getContainsMinimum(MINLAPS) || i_I < getIntersectsMinimum(MINLAPS) || i_W < getWithinMinimum(MINLAPS)
|| (!isRandomShapeRectangular() && i_D < getDisjointMinimum(MINLAPS)) || i_bboxD < getBoundingMinimum(MINLAPS)) {
laps++;
LogRule.clear();
if (laps > MINLAPS) {
fail("Did not find enough contains/within/intersection/disjoint/bounds cases in a reasonable number" +
" of random attempts. CWIDbD: " +
i_C + "("+getContainsMinimum(MINLAPS)+")," +
i_W + "("+getWithinMinimum(MINLAPS)+")," +
i_I + "("+getIntersectsMinimum(MINLAPS)+")," +
i_D + "("+getDisjointMinimum(MINLAPS)+")," +
i_bboxD + "("+getBoundingMinimum(MINLAPS)+")"
+ " Laps exceeded " + MINLAPS);
}
Point nearP = randomPointIn(ctx.getWorldBounds());
S s = generateRandomShape(nearP);
Rectangle r = randomRectangle(s.getBoundingBox().getCenter());
SpatialRelation ic = s.relate(r);
LogRule.log("S-R Rel: {}, Shape {}, Rectangle {}", ic, s, r);
if (ic != DISJOINT) {
assertTrue("if not disjoint then the shape's bbox shouldn't be disjoint",
s.getBoundingBox().relate(r).intersects());
}
try {
int MAX_TRIES = scaledRandomIntBetween(10, 100);
switch (ic) {
case CONTAINS:
i_C++;
for (int j = 0; j < MAX_TRIES; j++) {
Point p = randomPointIn(r);
assertRelation(null, CONTAINS, s, p);
}
break;
case WITHIN:
i_W++;
for (int j = 0; j < MAX_TRIES; j++) {
Point p = randomPointIn(s);
assertRelation(null, CONTAINS, r, p);
}
break;
case DISJOINT:
if (!s.getBoundingBox().relate(r).intersects()) {//bboxes are disjoint
i_bboxD++;
if (i_bboxD >= getBoundingMinimum(MINLAPS))
break;
} else {
i_D++;
}
for (int j = 0; j < MAX_TRIES; j++) {
Point p = randomPointIn(r);
assertRelation(null, DISJOINT, s, p);
}
break;
case INTERSECTS:
i_I++;
SpatialRelation pointR = null;//set once
Rectangle randomPointSpace = null;
MAX_TRIES = 1000;//give many attempts
for (int j = 0; j < MAX_TRIES; j++) {
Point p;
if (j < 4) {
p = new PointImpl(0, 0, ctx);
InfBufLine.cornerByQuadrant(r, j + 1, p);
} else {
if (randomPointSpace == null) {
if (pointR == DISJOINT) {
randomPointSpace = intersectRects(r,s.getBoundingBox());
} else {//CONTAINS
randomPointSpace = r;
}
}
p = randomPointIn(randomPointSpace);
}
SpatialRelation pointRNew = s.relate(p);
if (pointR == null) {
pointR = pointRNew;
} else if (pointR != pointRNew) {
break;
} else if (j >= MAX_TRIES) {
//TODO consider logging instead of failing
fail("Tried intersection brute-force too many times without success");
}
}
break;
default: fail(""+ic);
} // switch
} catch (AssertionError e) {
onAssertFail(e, s, r, ic);
}
} // while loop
System.out.println("Laps: "+laps + " CWIDbD: "+i_C+","+i_W+","+i_I+","+i_D+","+i_bboxD);
}
protected void onAssertFail(AssertionError e, S s, Rectangle r, SpatialRelation ic) {
throw e;
}
private Rectangle intersectRects(Rectangle r1, Rectangle r2) {
assert r1.relate(r2).intersects();
final double minX, maxX;
if (r1.relateXRange(r2.getMinX(),r2.getMinX()).intersects()) {
minX = r2.getMinX();
} else {
minX = r1.getMinX();
}
if (r1.relateXRange(r2.getMaxX(),r2.getMaxX()).intersects()) {
maxX = r2.getMaxX();
} else {
maxX = r1.getMaxX();
}
final double minY, maxY;
if (r1.relateYRange(r2.getMinY(),r2.getMinY()).intersects()) {
minY = r2.getMinY();
} else {
minY = r1.getMinY();
}
if (r1.relateYRange(r2.getMaxY(),r2.getMaxY()).intersects()) {
maxY = r2.getMaxY();
} else {
maxY = r1.getMaxY();
}
return ctx.makeRectangle(minX, maxX, minY, maxY);
}
}

View File

@ -0,0 +1,269 @@
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.
*/
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class GeoBBoxTest {
protected final double DEGREES_TO_RADIANS = Math.PI / 180.0;
@Test
public void testBBoxDegenerate() {
GeoBBox box;
GeoConvexPolygon cp;
int relationship;
List<GeoPoint> points = new ArrayList<GeoPoint>();
points.add(new GeoPoint(24 * DEGREES_TO_RADIANS, -30 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(-11 * DEGREES_TO_RADIANS, 101 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(-49 * DEGREES_TO_RADIANS, -176 * DEGREES_TO_RADIANS));
GeoMembershipShape shape = GeoPolygonFactory.makeGeoPolygon(points, 0);
box = GeoBBoxFactory.makeGeoBBox(-64 * DEGREES_TO_RADIANS, -64 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS, 180 * DEGREES_TO_RADIANS);
relationship = box.getRelationship(shape);
assertEquals(GeoArea.CONTAINS, relationship);
box = GeoBBoxFactory.makeGeoBBox(-61.85 * DEGREES_TO_RADIANS, -67.5 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS, -168.75 * DEGREES_TO_RADIANS);
System.out.println("Shape = " + shape + " Rect = " + box);
relationship = box.getRelationship(shape);
assertEquals(GeoArea.CONTAINS, relationship);
}
@Test
public void testBBoxPointWithin() {
GeoBBox box;
GeoPoint gp;
// Standard normal Rect box, not crossing dateline
box = GeoBBoxFactory.makeGeoBBox(0.0, -Math.PI * 0.25, -1.0, 1.0);
gp = new GeoPoint(-0.1, 0.0);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(0.1, 0.0);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-Math.PI * 0.5, 0.0);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-0.1, 1.1);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-0.1, -1.1);
assertFalse(box.isWithin(gp));
// Standard normal Rect box, crossing dateline
box = GeoBBoxFactory.makeGeoBBox(0.0, -Math.PI * 0.25, Math.PI - 1.0, -Math.PI + 1.0);
gp = new GeoPoint(-0.1, -Math.PI);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(0.1, -Math.PI);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-Math.PI * 0.5, -Math.PI);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-0.1, -Math.PI + 1.1);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-0.1, -Math.PI - 1.1);
assertFalse(box.isWithin(gp));
// Latitude zone rectangle
box = GeoBBoxFactory.makeGeoBBox(0.0, -Math.PI * 0.25, -Math.PI, Math.PI);
gp = new GeoPoint(-0.1, -Math.PI);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(0.1, -Math.PI);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-Math.PI * 0.5, -Math.PI);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-0.1, -Math.PI + 1.1);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(-0.1, -Math.PI - 1.1);
assertTrue(box.isWithin(gp));
// World
box = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, -Math.PI, Math.PI);
gp = new GeoPoint(-0.1, -Math.PI);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(0.1, -Math.PI);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(-Math.PI * 0.5, -Math.PI);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(-0.1, -Math.PI + 1.1);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(-0.1, -Math.PI - 1.1);
assertTrue(box.isWithin(gp));
}
@Test
public void testBBoxExpand() {
GeoBBox box;
GeoPoint gp;
// Standard normal Rect box, not crossing dateline
box = GeoBBoxFactory.makeGeoBBox(0.0, -Math.PI * 0.25, -1.0, 1.0);
box = box.expand(0.1);
gp = new GeoPoint(0.05, 0.0);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(0.15, 0.0);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-Math.PI * 0.25 - 0.05, 0.0);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(-Math.PI * 0.25 - 0.15, 0.0);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-0.1, -1.05);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(-0.1, -1.15);
assertFalse(box.isWithin(gp));
gp = new GeoPoint(-0.1, 1.05);
assertTrue(box.isWithin(gp));
gp = new GeoPoint(-0.1, 1.15);
assertFalse(box.isWithin(gp));
}
@Test
public void testBBoxBounds() {
GeoBBox c;
Bounds b;
c = GeoBBoxFactory.makeGeoBBox(0.0, -Math.PI * 0.25, -1.0, 1.0);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(-1.0, b.getLeftLongitude(), 0.000001);
assertEquals(1.0, b.getRightLongitude(), 0.000001);
assertEquals(-Math.PI * 0.25, b.getMinLatitude(), 0.000001);
assertEquals(0.0, b.getMaxLatitude(), 0.000001);
c = GeoBBoxFactory.makeGeoBBox(0.0, -Math.PI * 0.25, 1.0, -1.0);
b = c.getBounds(null);
assertTrue(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
//assertEquals(1.0,b.getLeftLongitude(),0.000001);
//assertEquals(-1.0,b.getRightLongitude(),0.000001);
assertEquals(-Math.PI * 0.25, b.getMinLatitude(), 0.000001);
assertEquals(0.0, b.getMaxLatitude(), 0.000001);
c = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, -1.0, 1.0);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
assertEquals(-1.0, b.getLeftLongitude(), 0.000001);
assertEquals(1.0, b.getRightLongitude(), 0.000001);
c = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, 1.0, -1.0);
b = c.getBounds(null);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
//assertEquals(1.0,b.getLeftLongitude(),0.000001);
//assertEquals(-1.0,b.getRightLongitude(),0.000001);
// Check wide variants of rectangle and longitude slice
c = GeoBBoxFactory.makeGeoBBox(0.0, -Math.PI * 0.25, -Math.PI + 0.1, Math.PI - 0.1);
b = c.getBounds(null);
assertTrue(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
//assertEquals(-Math.PI+0.1,b.getLeftLongitude(),0.000001);
//assertEquals(Math.PI-0.1,b.getRightLongitude(),0.000001);
assertEquals(-Math.PI * 0.25, b.getMinLatitude(), 0.000001);
assertEquals(0.0, b.getMaxLatitude(), 0.000001);
c = GeoBBoxFactory.makeGeoBBox(0.0, -Math.PI * 0.25, Math.PI - 0.1, -Math.PI + 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(Math.PI - 0.1, b.getLeftLongitude(), 0.000001);
assertEquals(-Math.PI + 0.1, b.getRightLongitude(), 0.000001);
assertEquals(-Math.PI * 0.25, b.getMinLatitude(), 0.000001);
assertEquals(0.0, b.getMaxLatitude(), 0.000001);
c = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, -Math.PI + 0.1, Math.PI - 0.1);
b = c.getBounds(null);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
//assertEquals(-Math.PI+0.1,b.getLeftLongitude(),0.000001);
//assertEquals(Math.PI-0.1,b.getRightLongitude(),0.000001);
c = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, Math.PI - 0.1, -Math.PI + 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
assertEquals(Math.PI - 0.1, b.getLeftLongitude(), 0.000001);
assertEquals(-Math.PI + 0.1, b.getRightLongitude(), 0.000001);
// Check latitude zone
c = GeoBBoxFactory.makeGeoBBox(1.0, -1.0, -Math.PI, Math.PI);
b = c.getBounds(null);
assertTrue(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(-1.0, b.getMinLatitude(), 0.000001);
assertEquals(1.0, b.getMaxLatitude(), 0.000001);
// Now, combine a few things to test the bounds object
GeoBBox c1;
GeoBBox c2;
c1 = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, -Math.PI, 0.0);
c2 = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, 0.0, Math.PI);
b = new Bounds();
b = c1.getBounds(b);
b = c2.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
c1 = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, -Math.PI, 0.0);
c2 = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, 0.0, Math.PI * 0.5);
b = new Bounds();
b = c1.getBounds(b);
b = c2.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
//assertEquals(-Math.PI,b.getLeftLongitude(),0.000001);
//assertEquals(Math.PI*0.5,b.getRightLongitude(),0.000001);
c1 = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, -Math.PI * 0.5, 0.0);
c2 = GeoBBoxFactory.makeGeoBBox(Math.PI * 0.5, -Math.PI * 0.5, 0.0, Math.PI);
b = new Bounds();
b = c1.getBounds(b);
b = c2.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
//assertEquals(-Math.PI * 0.5,b.getLeftLongitude(),0.000001);
//assertEquals(Math.PI,b.getRightLongitude(),0.000001);
}
}

View File

@ -0,0 +1,219 @@
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.
*/
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class GeoCircleTest {
@Test
public void testCircleDistance() {
GeoCircle c;
GeoPoint gp;
c = new GeoCircle(0.0, -0.5, 0.1);
gp = new GeoPoint(0.0, 0.0);
assertEquals(Double.MAX_VALUE, c.computeArcDistance(gp), 0.0);
assertEquals(Double.MAX_VALUE, c.computeLinearDistance(gp), 0.0);
assertEquals(Double.MAX_VALUE, c.computeNormalDistance(gp), 0.0);
gp = new GeoPoint(0.0, -0.5);
assertEquals(0.0, c.computeArcDistance(gp), 0.000001);
assertEquals(0.0, c.computeLinearDistance(gp), 0.000001);
assertEquals(0.0, c.computeNormalDistance(gp), 0.000001);
gp = new GeoPoint(0.05, -0.5);
assertEquals(0.05, c.computeArcDistance(gp), 0.000001);
assertEquals(0.049995, c.computeLinearDistance(gp), 0.000001);
assertEquals(0.049979, c.computeNormalDistance(gp), 0.000001);
}
@Test
public void testCirclePointWithin() {
GeoCircle c;
GeoPoint gp;
c = new GeoCircle(0.0, -0.5, 0.1);
gp = new GeoPoint(0.0, 0.0);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.5);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.55);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.45);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(Math.PI * 0.5, 0.0);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(0.0, Math.PI);
assertFalse(c.isWithin(gp));
}
@Test
public void testCircleBounds() {
GeoCircle c;
Bounds b;
// Vertical circle cases
c = new GeoCircle(0.0, -0.5, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(-0.6, b.getLeftLongitude(), 0.000001);
assertEquals(-0.4, b.getRightLongitude(), 0.000001);
assertEquals(-0.1, b.getMinLatitude(), 0.000001);
assertEquals(0.1, b.getMaxLatitude(), 0.000001);
c = new GeoCircle(0.0, 0.5, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(0.4, b.getLeftLongitude(), 0.000001);
assertEquals(0.6, b.getRightLongitude(), 0.000001);
assertEquals(-0.1, b.getMinLatitude(), 0.000001);
assertEquals(0.1, b.getMaxLatitude(), 0.000001);
c = new GeoCircle(0.0, 0.0, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(-0.1, b.getLeftLongitude(), 0.000001);
assertEquals(0.1, b.getRightLongitude(), 0.000001);
assertEquals(-0.1, b.getMinLatitude(), 0.000001);
assertEquals(0.1, b.getMaxLatitude(), 0.000001);
c = new GeoCircle(0.0, Math.PI, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(Math.PI - 0.1, b.getLeftLongitude(), 0.000001);
assertEquals(-Math.PI + 0.1, b.getRightLongitude(), 0.000001);
assertEquals(-0.1, b.getMinLatitude(), 0.000001);
assertEquals(0.1, b.getMaxLatitude(), 0.000001);
// Horizontal circle cases
c = new GeoCircle(Math.PI * 0.5, 0.0, 0.1);
b = c.getBounds(null);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(Math.PI * 0.5 - 0.1, b.getMinLatitude(), 0.000001);
c = new GeoCircle(-Math.PI * 0.5, 0.0, 0.1);
b = c.getBounds(null);
assertTrue(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
assertEquals(-Math.PI * 0.5 + 0.1, b.getMaxLatitude(), 0.000001);
// Now do a somewhat tilted plane, facing different directions.
c = new GeoCircle(0.01, 0.0, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(0.11, b.getMaxLatitude(), 0.000001);
assertEquals(-0.09, b.getMinLatitude(), 0.000001);
assertEquals(-0.1, b.getLeftLongitude(), 0.00001);
assertEquals(0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(0.01, Math.PI, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(0.11, b.getMaxLatitude(), 0.000001);
assertEquals(-0.09, b.getMinLatitude(), 0.000001);
assertEquals(Math.PI - 0.1, b.getLeftLongitude(), 0.00001);
assertEquals(-Math.PI + 0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(0.01, Math.PI * 0.5, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(0.11, b.getMaxLatitude(), 0.000001);
assertEquals(-0.09, b.getMinLatitude(), 0.000001);
assertEquals(Math.PI * 0.5 - 0.1, b.getLeftLongitude(), 0.00001);
assertEquals(Math.PI * 0.5 + 0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(0.01, -Math.PI * 0.5, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(0.11, b.getMaxLatitude(), 0.000001);
assertEquals(-0.09, b.getMinLatitude(), 0.000001);
assertEquals(-Math.PI * 0.5 - 0.1, b.getLeftLongitude(), 0.00001);
assertEquals(-Math.PI * 0.5 + 0.1, b.getRightLongitude(), 0.00001);
// Slightly tilted, PI/4 direction.
c = new GeoCircle(0.01, Math.PI * 0.25, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(0.11, b.getMaxLatitude(), 0.000001);
assertEquals(-0.09, b.getMinLatitude(), 0.000001);
assertEquals(Math.PI * 0.25 - 0.1, b.getLeftLongitude(), 0.00001);
assertEquals(Math.PI * 0.25 + 0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(0.01, -Math.PI * 0.25, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(0.11, b.getMaxLatitude(), 0.000001);
assertEquals(-0.09, b.getMinLatitude(), 0.000001);
assertEquals(-Math.PI * 0.25 - 0.1, b.getLeftLongitude(), 0.00001);
assertEquals(-Math.PI * 0.25 + 0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(-0.01, Math.PI * 0.25, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(0.09, b.getMaxLatitude(), 0.000001);
assertEquals(-0.11, b.getMinLatitude(), 0.000001);
assertEquals(Math.PI * 0.25 - 0.1, b.getLeftLongitude(), 0.00001);
assertEquals(Math.PI * 0.25 + 0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(-0.01, -Math.PI * 0.25, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(0.09, b.getMaxLatitude(), 0.000001);
assertEquals(-0.11, b.getMinLatitude(), 0.000001);
assertEquals(-Math.PI * 0.25 - 0.1, b.getLeftLongitude(), 0.00001);
assertEquals(-Math.PI * 0.25 + 0.1, b.getRightLongitude(), 0.00001);
// Now do a somewhat tilted plane.
c = new GeoCircle(0.01, -0.5, 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(0.11, b.getMaxLatitude(), 0.000001);
assertEquals(-0.09, b.getMinLatitude(), 0.000001);
assertEquals(-0.6, b.getLeftLongitude(), 0.00001);
assertEquals(-0.4, b.getRightLongitude(), 0.00001);
}
}

View File

@ -0,0 +1,88 @@
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.
*/
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class GeoConvexPolygonTest {
@Test
public void testPolygonPointWithin() {
GeoConvexPolygon c;
GeoPoint gp;
c = new GeoConvexPolygon(-0.1, -0.5);
c.addPoint(0.0, -0.6, false);
c.addPoint(0.1, -0.5, false);
c.addPoint(0.0, -0.4, false);
c.donePoints(false);
// Sample some points within
gp = new GeoPoint(0.0, -0.5);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.55);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.45);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(-0.05, -0.5);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.05, -0.5);
assertTrue(c.isWithin(gp));
// Sample some nearby points outside
gp = new GeoPoint(0.0, -0.65);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.35);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(-0.15, -0.5);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(0.15, -0.5);
assertFalse(c.isWithin(gp));
// Random points outside
gp = new GeoPoint(0.0, 0.0);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(Math.PI * 0.5, 0.0);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(0.0, Math.PI);
assertFalse(c.isWithin(gp));
}
@Test
public void testPolygonBounds() {
GeoConvexPolygon c;
Bounds b;
c = new GeoConvexPolygon(-0.1, -0.5);
c.addPoint(0.0, -0.6, false);
c.addPoint(0.1, -0.5, false);
c.addPoint(0.0, -0.4, false);
c.donePoints(false);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(-0.6, b.getLeftLongitude(), 0.000001);
assertEquals(-0.4, b.getRightLongitude(), 0.000001);
assertEquals(-0.1, b.getMinLatitude(), 0.000001);
assertEquals(0.1, b.getMaxLatitude(), 0.000001);
}
}

View File

@ -0,0 +1,184 @@
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.
*/
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class GeoPathTest {
@Test
public void testPathDistance() {
// Start with a really simple case
GeoPath p;
GeoPoint gp;
p = new GeoPath(0.1);
p.addPoint(0.0, 0.0);
p.addPoint(0.0, 0.1);
p.addPoint(0.0, 0.2);
p.done();
gp = new GeoPoint(Math.PI * 0.5, 0.15);
assertEquals(Double.MAX_VALUE, p.computeArcDistance(gp), 0.0);
gp = new GeoPoint(0.05, 0.15);
assertEquals(0.15 + 0.05, p.computeArcDistance(gp), 0.000001);
gp = new GeoPoint(0.0, 0.12);
assertEquals(0.12 + 0.0, p.computeArcDistance(gp), 0.000001);
gp = new GeoPoint(-0.15, 0.05);
assertEquals(Double.MAX_VALUE, p.computeArcDistance(gp), 0.000001);
gp = new GeoPoint(0.0, 0.25);
assertEquals(0.20 + 0.05, p.computeArcDistance(gp), 0.000001);
gp = new GeoPoint(0.0, -0.05);
assertEquals(0.0 + 0.05, p.computeArcDistance(gp), 0.000001);
// Compute path distances now
p = new GeoPath(0.1);
p.addPoint(0.0, 0.0);
p.addPoint(0.0, 0.1);
p.addPoint(0.0, 0.2);
p.done();
gp = new GeoPoint(0.05, 0.15);
assertEquals(0.15 + 0.05, p.computeArcDistance(gp), 0.000001);
gp = new GeoPoint(0.0, 0.12);
assertEquals(0.12, p.computeArcDistance(gp), 0.000001);
// Now try a vertical path, and make sure distances are as expected
p = new GeoPath(0.1);
p.addPoint(-Math.PI * 0.25, -0.5);
p.addPoint(Math.PI * 0.25, -0.5);
p.done();
gp = new GeoPoint(0.0, 0.0);
assertEquals(Double.MAX_VALUE, p.computeArcDistance(gp), 0.0);
gp = new GeoPoint(-0.1, -1.0);
assertEquals(Double.MAX_VALUE, p.computeArcDistance(gp), 0.0);
gp = new GeoPoint(Math.PI * 0.25 + 0.05, -0.5);
assertEquals(Math.PI * 0.5 + 0.05, p.computeArcDistance(gp), 0.000001);
gp = new GeoPoint(-Math.PI * 0.25 - 0.05, -0.5);
assertEquals(0.0 + 0.05, p.computeArcDistance(gp), 0.000001);
}
@Test
public void testPathPointWithin() {
// Tests whether we can properly detect whether a point is within a path or not
GeoPath p;
GeoPoint gp;
p = new GeoPath(0.1);
// Build a diagonal path crossing the equator
p.addPoint(-0.2, -0.2);
p.addPoint(0.2, 0.2);
p.done();
// Test points on the path
gp = new GeoPoint(-0.2, -0.2);
assertTrue(p.isWithin(gp));
gp = new GeoPoint(0.0, 0.0);
assertTrue(p.isWithin(gp));
gp = new GeoPoint(0.1, 0.1);
assertTrue(p.isWithin(gp));
// Test points off the path
gp = new GeoPoint(-0.2, 0.2);
assertFalse(p.isWithin(gp));
gp = new GeoPoint(-Math.PI * 0.5, 0.0);
assertFalse(p.isWithin(gp));
gp = new GeoPoint(0.2, -0.2);
assertFalse(p.isWithin(gp));
gp = new GeoPoint(0.0, Math.PI);
assertFalse(p.isWithin(gp));
// Repeat the test, but across the terminator
p = new GeoPath(0.1);
// Build a diagonal path crossing the equator
p.addPoint(-0.2, Math.PI - 0.2);
p.addPoint(0.2, -Math.PI + 0.2);
// Test points on the path
gp = new GeoPoint(-0.2, Math.PI - 0.2);
assertTrue(p.isWithin(gp));
gp = new GeoPoint(0.0, Math.PI);
assertTrue(p.isWithin(gp));
gp = new GeoPoint(0.1, -Math.PI + 0.1);
assertTrue(p.isWithin(gp));
// Test points off the path
gp = new GeoPoint(-0.2, -Math.PI + 0.2);
assertFalse(p.isWithin(gp));
gp = new GeoPoint(-Math.PI * 0.5, 0.0);
assertFalse(p.isWithin(gp));
gp = new GeoPoint(0.2, Math.PI - 0.2);
assertFalse(p.isWithin(gp));
gp = new GeoPoint(0.0, 0.0);
assertFalse(p.isWithin(gp));
}
@Test
public void testGetRelationship() {
GeoArea rect;
GeoPath p;
// Start by testing the basic kinds of relationship, increasing in order of difficulty.
p = new GeoPath(0.1);
p.addPoint(-0.3, -0.3);
p.addPoint(0.3, 0.3);
p.done();
// Easiest: The path is wholly contains the georect
rect = new GeoRectangle(0.05, -0.05, -0.05, 0.05);
assertEquals(GeoArea.CONTAINS, rect.getRelationship(p));
// Next easiest: Some endpoints of the rectangle are inside, and some are outside.
rect = new GeoRectangle(0.05, -0.05, -0.05, 0.5);
assertEquals(GeoArea.OVERLAPS, rect.getRelationship(p));
// Now, all points are outside, but the figures intersect
rect = new GeoRectangle(0.05, -0.05, -0.5, 0.5);
assertEquals(GeoArea.OVERLAPS, rect.getRelationship(p));
// Finally, all points are outside, and the figures *do not* intersect
rect = new GeoRectangle(0.5, -0.5, -0.5, 0.5);
assertEquals(GeoArea.WITHIN, rect.getRelationship(p));
// Check that segment edge overlap detection works
rect = new GeoRectangle(0.1, 0.0, -0.1, 0.0);
assertEquals(GeoArea.OVERLAPS, rect.getRelationship(p));
rect = new GeoRectangle(0.2, 0.1, -0.2, -0.1);
assertEquals(GeoArea.DISJOINT, rect.getRelationship(p));
// Check if overlap at endpoints behaves as expected next
rect = new GeoRectangle(0.5, -0.5, -0.5, -0.35);
assertEquals(GeoArea.OVERLAPS, rect.getRelationship(p));
rect = new GeoRectangle(0.5, -0.5, -0.5, -0.45);
assertEquals(GeoArea.DISJOINT, rect.getRelationship(p));
}
@Test
public void testPathBounds() {
GeoPath c;
Bounds b;
c = new GeoPath(0.1);
c.addPoint(-0.3, -0.3);
c.addPoint(0.3, 0.3);
c.done();
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(-0.4046919, b.getLeftLongitude(), 0.000001);
assertEquals(0.4046919, b.getRightLongitude(), 0.000001);
assertEquals(-0.3999999, b.getMinLatitude(), 0.000001);
assertEquals(0.3999999, b.getMaxLatitude(), 0.000001);
}
}

View File

@ -0,0 +1,145 @@
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.
*/
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class GeoPolygonTest {
@Test
public void testPolygonPointWithin() {
GeoMembershipShape c;
GeoPoint gp;
List<GeoPoint> points;
points = new ArrayList<GeoPoint>();
points.add(new GeoPoint(-0.1, -0.5));
points.add(new GeoPoint(0.0, -0.6));
points.add(new GeoPoint(0.1, -0.5));
points.add(new GeoPoint(0.0, -0.4));
c = GeoPolygonFactory.makeGeoPolygon(points, 0);
// Sample some points within
gp = new GeoPoint(0.0, -0.5);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.55);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.45);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(-0.05, -0.5);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.05, -0.5);
assertTrue(c.isWithin(gp));
// Sample some nearby points outside
gp = new GeoPoint(0.0, -0.65);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.35);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(-0.15, -0.5);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(0.15, -0.5);
assertFalse(c.isWithin(gp));
// Random points outside
gp = new GeoPoint(0.0, 0.0);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(Math.PI * 0.5, 0.0);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(0.0, Math.PI);
assertFalse(c.isWithin(gp));
points = new ArrayList<GeoPoint>();
points.add(new GeoPoint(-0.1, -0.5));
points.add(new GeoPoint(-0.01, -0.6));
points.add(new GeoPoint(-0.1, -0.7));
points.add(new GeoPoint(0.0, -0.8));
points.add(new GeoPoint(0.1, -0.7));
points.add(new GeoPoint(0.01, -0.6));
points.add(new GeoPoint(0.1, -0.5));
points.add(new GeoPoint(0.0, -0.4));
/*
System.out.println("Points: ");
for (GeoPoint p : points) {
System.out.println(" "+p);
}
*/
c = GeoPolygonFactory.makeGeoPolygon(points, 0);
// Sample some points within
gp = new GeoPoint(0.0, -0.5);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.55);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.45);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(-0.05, -0.5);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.05, -0.5);
assertTrue(c.isWithin(gp));
gp = new GeoPoint(0.0, -0.7);
assertTrue(c.isWithin(gp));
// Sample some nearby points outside
gp = new GeoPoint(0.0, -0.35);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(-0.15, -0.5);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(0.15, -0.5);
assertFalse(c.isWithin(gp));
// Random points outside
gp = new GeoPoint(0.0, 0.0);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(Math.PI * 0.5, 0.0);
assertFalse(c.isWithin(gp));
gp = new GeoPoint(0.0, Math.PI);
assertFalse(c.isWithin(gp));
}
@Test
public void testPolygonBounds() {
GeoMembershipShape c;
Bounds b;
List<GeoPoint> points;
points = new ArrayList<GeoPoint>();
points.add(new GeoPoint(-0.1, -0.5));
points.add(new GeoPoint(0.0, -0.6));
points.add(new GeoPoint(0.1, -0.5));
points.add(new GeoPoint(0.0, -0.4));
c = GeoPolygonFactory.makeGeoPolygon(points, 0);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(-0.6, b.getLeftLongitude(), 0.000001);
assertEquals(-0.4, b.getRightLongitude(), 0.000001);
assertEquals(-0.1, b.getMinLatitude(), 0.000001);
assertEquals(0.1, b.getMaxLatitude(), 0.000001);
}
}

View File

@ -0,0 +1,65 @@
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.
*/
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Test basic plane functionality.
*/
public class PlaneTest {
@Test
public void testIdenticalPlanes() {
final GeoPoint p = new GeoPoint(0.123, -0.456);
final Plane plane1 = new Plane(p, 0.0);
final Plane plane2 = new Plane(p, 0.0);
assertTrue(plane1.isNumericallyIdentical(plane2));
final Plane plane3 = new Plane(p, 0.1);
assertFalse(plane1.isNumericallyIdentical(plane3));
final Vector v1 = new Vector(0.1, -0.732, 0.9);
final double constant = 0.432;
final Vector v2 = new Vector(v1.x * constant, v1.y * constant, v1.z * constant);
final Plane p1 = new Plane(v1, 0.2);
final Plane p2 = new Plane(v2, 0.2 * constant);
assertTrue(p1.isNumericallyIdentical(p2));
}
@Test
public void testInterpolation() {
// [X=0.35168818443386646, Y=-0.19637966197066342, Z=0.9152870857244183],
// [X=0.5003343189532654, Y=0.522128543226148, Z=0.6906861469771293],
final GeoPoint start = new GeoPoint(0.35168818443386646, -0.19637966197066342, 0.9152870857244183);
final GeoPoint end = new GeoPoint(0.5003343189532654, 0.522128543226148, 0.6906861469771293);
// [A=-0.6135342247741855, B=0.21504338363863665, C=0.28188192383666794, D=0.0, side=-1.0] internal? false;
final Plane p = new Plane(-0.6135342247741855, 0.21504338363863665, 0.28188192383666794, 0.0);
final GeoPoint[] points = p.interpolate(start, end, new double[]{0.25, 0.50, 0.75});
for (GeoPoint point : points) {
assertTrue(p.evaluateIsZero(point));
}
}
}