LUCENE-6699: add geo3d + BKD for fast, accurate earth-surface point-in-shape queries

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1700883 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Michael McCandless 2015-09-02 19:59:31 +00:00
parent 21bd68a443
commit 03e5bcfa73
76 changed files with 7344 additions and 785 deletions

View File

@ -69,6 +69,10 @@ New Features
* LUCENE-6737: Add DecimalDigitFilter which folds unicode digits to basic latin.
(Robert Muir)
* LUCENE-6699: Add integration of BKD tree and geo3d APIs to give
fast, very accurate query to find all indexed points within an
earth-surface shape (Karl Wright, Mike McCandless)
Optimizations
* LUCENE-6708: TopFieldCollector does not compute the score several times on the

View File

@ -21,21 +21,19 @@ import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.ToStringUtils;
import java.io.IOException;
import java.util.Set;
/** Finds all previously indexed points that fall within the specified boundings box.
*
@ -79,38 +77,7 @@ public class BKDPointInBBoxQuery extends Query {
// I don't use RandomAccessWeight here: it's no good to approximate with "match all docs"; this is an inverted structure and should be
// used in the first pass:
return new Weight(this) {
private float queryNorm;
private float queryWeight;
@Override
public void extractTerms(Set<Term> terms) {
}
@Override
public float getValueForNormalization() throws IOException {
queryWeight = getBoost();
return queryWeight * queryWeight;
}
@Override
public void normalize(float norm, float topLevelBoost) {
queryNorm = norm * topLevelBoost;
queryWeight *= queryNorm;
}
@Override
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
final Scorer s = scorer(context);
final boolean exists = s != null && s.advance(doc) == doc;
if (exists) {
return Explanation.match(queryWeight, BKDPointInBBoxQuery.this.toString() + ", product of:",
Explanation.match(getBoost(), "boost"), Explanation.match(queryNorm, "queryNorm"));
} else {
return Explanation.noMatch(BKDPointInBBoxQuery.this.toString() + " doesn't match id " + doc);
}
}
return new ConstantScoreWeight(this) {
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
@ -127,42 +94,11 @@ public class BKDPointInBBoxQuery extends Query {
BKDTreeSortedNumericDocValues treeDV = (BKDTreeSortedNumericDocValues) sdv;
BKDTreeReader tree = treeDV.getBKDTreeReader();
DocIdSet result = tree.intersect(minLat, maxLat, minLon, maxLon, treeDV.delegate);
DocIdSet result = tree.intersect(minLat, maxLat, minLon, maxLon, null, treeDV.delegate);
final DocIdSetIterator disi = result.iterator();
return new Scorer(this) {
@Override
public float score() throws IOException {
return queryWeight;
}
@Override
public int freq() throws IOException {
return 1;
}
@Override
public int docID() {
return disi.docID();
}
@Override
public int nextDoc() throws IOException {
return disi.nextDoc();
}
@Override
public int advance(int target) throws IOException {
return disi.advance(target);
}
@Override
public long cost() {
return disi.cost();
}
};
return new ConstantScoreScorer(this, score(), disi);
}
};
}

View File

@ -28,7 +28,6 @@ import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.GeoUtils;
import org.apache.lucene.util.ToStringUtils;
@ -158,8 +157,6 @@ public class BKDPointInPolygonQuery extends Query {
BKDTreeSortedNumericDocValues treeDV = (BKDTreeSortedNumericDocValues) sdv;
BKDTreeReader tree = treeDV.getBKDTreeReader();
// TODO: make this more efficient: as we recurse the BKD tree we should check whether the
// bbox we are recursing into intersects our shape; Apache SIS may have (non-GPL!) code to do this?
DocIdSet result = tree.intersect(minLat, maxLat, minLon, maxLon,
new BKDTreeReader.LatLonFilter() {
@Override
@ -172,13 +169,13 @@ public class BKDPointInPolygonQuery extends Query {
if (GeoUtils.rectWithinPoly(cellLonMin, cellLatMin, cellLonMax, cellLatMax,
polyLons, polyLats,
minLon, minLat, maxLon, maxLat)) {
return BKDTreeReader.Relation.INSIDE;
return BKDTreeReader.Relation.CELL_INSIDE_SHAPE;
} else if (GeoUtils.rectCrossesPoly(cellLonMin, cellLatMin, cellLonMax, cellLatMax,
polyLons, polyLats,
minLon, minLat, maxLon, maxLat)) {
return BKDTreeReader.Relation.CROSSES;
return BKDTreeReader.Relation.SHAPE_CROSSES_CELL;
} else {
return BKDTreeReader.Relation.OUTSIDE;
return BKDTreeReader.Relation.SHAPE_OUTSIDE_CELL;
}
}
}, treeDV.delegate);

View File

@ -38,10 +38,12 @@ final class BKDTreeReader implements Accountable {
final int maxDoc;
final IndexInput in;
enum Relation {INSIDE, CROSSES, OUTSIDE};
enum Relation {CELL_INSIDE_SHAPE, SHAPE_CROSSES_CELL, SHAPE_OUTSIDE_CELL};
interface LatLonFilter {
// TODO: move DVs/encoding out on top: this method should just take a docID
boolean accept(double lat, double lon);
// TODO: move DVs/encoding out on top: this method should take ints and do its own decode
Relation compare(double latMin, double latMax, double lonMin, double lonMax);
}
@ -93,10 +95,7 @@ final class BKDTreeReader implements Accountable {
}
}
public DocIdSet intersect(double latMin, double latMax, double lonMin, double lonMax, SortedNumericDocValues sndv) throws IOException {
return intersect(latMin, latMax, lonMin, lonMax, null, sndv);
}
// TODO: move DVs/encoding out on top: this method should take ints, and encode should be done up above
public DocIdSet intersect(double latMin, double latMax, double lonMin, double lonMax, LatLonFilter filter, SortedNumericDocValues sndv) throws IOException {
if (BKDTreeWriter.validLat(latMin) == false) {
throw new IllegalArgumentException("invalid latMin: " + latMin);
@ -116,10 +115,6 @@ final class BKDTreeReader implements Accountable {
int lonMinEnc = BKDTreeWriter.encodeLon(lonMin);
int lonMaxEnc = BKDTreeWriter.encodeLon(lonMax);
// TODO: we should use a sparse bit collector here, but BitDocIdSet.Builder is 2.4X slower than straight FixedBitSet.
// Maybe we should use simple int[] (not de-duping) up until size X, then cutover. Or maybe SentinelIntSet when it's
// small.
QueryState state = new QueryState(in.clone(), maxDoc,
latMinEnc, latMaxEnc,
lonMinEnc, lonMaxEnc,
@ -158,9 +153,7 @@ final class BKDTreeReader implements Accountable {
// Dead end node (adversary case):
return 0;
}
//IndexInput in = leafDISI.in;
state.in.seek(fp);
//allLeafDISI.reset(fp);
//System.out.println(" seek to leafFP=" + fp);
// How many points are stored in this leaf cell:
@ -171,8 +164,6 @@ final class BKDTreeReader implements Accountable {
state.docs.add(docID);
}
//bits.or(allLeafDISI);
//return allLeafDISI.getHitCount();
return count;
} else {
int splitValue = splitValues[nodeID];
@ -199,27 +190,29 @@ final class BKDTreeReader implements Accountable {
// 2.06 sec -> 1.52 sec for 225 OSM London queries:
if (state.latLonFilter != null) {
if (cellLatMinEnc > state.latMinEnc ||
cellLatMaxEnc < state.latMaxEnc ||
cellLonMinEnc > state.lonMinEnc ||
cellLonMaxEnc < state.lonMaxEnc) {
// Only call the filter when the current cell does not fully contain the bbox:
if (cellLatMinEnc > state.latMinEnc || cellLatMaxEnc < state.latMaxEnc ||
cellLonMinEnc > state.lonMinEnc || cellLonMaxEnc < state.lonMaxEnc) {
Relation r = state.latLonFilter.compare(BKDTreeWriter.decodeLat(cellLatMinEnc),
BKDTreeWriter.decodeLat(cellLatMaxEnc),
BKDTreeWriter.decodeLon(cellLonMinEnc),
BKDTreeWriter.decodeLon(cellLonMaxEnc));
//System.out.println("BKD.intersect cellLat=" + BKDTreeWriter.decodeLat(cellLatMinEnc) + " TO " + BKDTreeWriter.decodeLat(cellLatMaxEnc) + ", cellLon=" + BKDTreeWriter.decodeLon(cellLonMinEnc) + " TO " + BKDTreeWriter.decodeLon(cellLonMaxEnc) + " compare=" + r);
if (r == Relation.OUTSIDE) {
// System.out.println("BKD.intersect cellLat=" + BKDTreeWriter.decodeLat(cellLatMinEnc) + " TO " + BKDTreeWriter.decodeLat(cellLatMaxEnc) + ", cellLon=" + BKDTreeWriter.decodeLon(cellLonMinEnc) + " TO " + BKDTreeWriter.decodeLon(cellLonMaxEnc) + " compare=" + r);
if (r == Relation.SHAPE_OUTSIDE_CELL) {
// This cell is fully outside of the query shape: stop recursing
return 0;
} else if (r == Relation.INSIDE) {
} else if (r == Relation.CELL_INSIDE_SHAPE) {
// This cell is fully inside of the query shape: recursively add all points in this cell without filtering
return addAll(state, nodeID);
} else {
// The cell crosses the shape boundary, so we fall through and do full filtering
}
}
// TODO: clean this up: the bbox case should also just be a filter, and we should assert filter != null at the start
} else if (state.latMinEnc <= cellLatMinEnc && state.latMaxEnc >= cellLatMaxEnc && state.lonMinEnc <= cellLonMinEnc && state.lonMaxEnc >= cellLonMaxEnc) {
// Optimize the case when the query fully contains this cell: we can
// Bbox query: optimize the case when the query fully contains this cell: we can
// recursively add all points without checking if they match the query:
return addAll(state, nodeID);
}
@ -241,7 +234,6 @@ final class BKDTreeReader implements Accountable {
//System.out.println(" intersect leaf nodeID=" + nodeID + " vs leafNodeOffset=" + leafNodeOffset + " fp=" + leafBlockFPs[nodeID-leafNodeOffset]);
int hitCount = 0;
//IndexInput in = leafDISI.in;
long fp = leafBlockFPs[nodeID-leafNodeOffset];
if (fp == 0) {
// Dead end node (adversary case):
@ -290,12 +282,6 @@ final class BKDTreeReader implements Accountable {
return hitCount;
// this (using BitDocIdSet.Builder) is 3.4X slower!
/*
//bits.or(leafDISI);
//return leafDISI.getHitCount();
*/
} else {
int splitValue = splitValues[nodeID];

View File

@ -365,7 +365,7 @@ class BKDTreeWriter {
long innerNodeCount = 1;
while (countPerLeaf > maxPointsInLeafNode) {
countPerLeaf /= 2;
countPerLeaf = (countPerLeaf+1)/2;
innerNodeCount *= 2;
}
@ -592,6 +592,8 @@ class BKDTreeWriter {
long latRange = (long) maxLatEnc - (long) minLatEnc;
long lonRange = (long) maxLonEnc - (long) minLonEnc;
assert lastLatSorted.count == lastLonSorted.count;
// Compute which dim we should split on at this level:
int splitDim;
if (latRange >= lonRange) {
@ -629,11 +631,10 @@ class BKDTreeWriter {
//System.out.println("\nleaf:\n lat range: " + ((long) maxLatEnc-minLatEnc));
//System.out.println(" lon range: " + ((long) maxLonEnc-minLonEnc));
assert count == source.count: "count=" + count + " vs source.count=" + source.count;
// Sort by docID in the leaf so we can .or(DISI) at search time:
// Sort by docID in the leaf so we get sequentiality at search time (may not matter?):
LatLonReader reader = source.writer.getReader(source.start);
// TODO: we can reuse this
int[] docIDs = new int[(int) count];
boolean success = false;
@ -697,13 +698,12 @@ class BKDTreeWriter {
//long endFP = out.getFilePointer();
//System.out.println(" bytes/doc: " + ((endFP - startFP) / count));
} else {
// Inner node: sort, partition/recurse
// Inner node: partition/recurse
assert nodeID < splitValues.length: "nodeID=" + nodeID + " splitValues.length=" + splitValues.length;
int[] splitValueArray = new int[1];
assert source.count == count;
long leftCount = markLeftTree(splitDim, source, bitSet, splitValueArray,
minLatEnc, maxLatEnc, minLonEnc, maxLonEnc);
int splitValue = splitValueArray[0];
@ -723,15 +723,14 @@ class BKDTreeWriter {
try {
leftWriter = getWriter(leftCount);
rightWriter = getWriter(nextSource.count - leftCount);
rightWriter = getWriter(count - leftCount);
//if (DEBUG) System.out.println(" partition:\n splitValueEnc=" + splitValue + "\n " + nextSource + "\n --> leftSorted=" + leftWriter + "\n --> rightSorted=" + rightWriter + ")");
assert nextSource.count == count;
reader = nextSource.writer.getReader(nextSource.start);
// TODO: we could compute the split value here for each sub-tree and save an O(N) pass on recursion, but makes code hairier and only
// changes the constant factor of building, not the big-oh:
for (int i=0;i<nextSource.count;i++) {
for (int i=0;i<count;i++) {
boolean result = reader.next();
assert result;
int latEnc = reader.latEnc();
@ -767,7 +766,6 @@ class BKDTreeWriter {
}
assert leftCount == nextLeftCount: "leftCount=" + leftCount + " nextLeftCount=" + nextLeftCount;
assert count == nextSource.count: "count=" + count + " nextSource.count=" + count;
success = false;
try {

View File

@ -319,7 +319,7 @@ class RangeTreeWriter {
long innerNodeCount = 1;
while (countPerLeaf > maxValuesInLeafNode) {
countPerLeaf /= 2;
countPerLeaf = (countPerLeaf+1)/2;
innerNodeCount *= 2;
}

View File

@ -24,7 +24,7 @@ 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.geo3d.Bounds;
import org.apache.lucene.geo3d.LatLonBounds;
import org.apache.lucene.geo3d.GeoArea;
import org.apache.lucene.geo3d.GeoAreaFactory;
import org.apache.lucene.geo3d.GeoPoint;
@ -107,7 +107,8 @@ public class Geo3dShape implements Shape {
public Rectangle getBoundingBox() {
Rectangle bbox = this.boundingBox;//volatile read once
if (bbox == null) {
Bounds bounds = shape.getBounds(null);
LatLonBounds bounds = new LatLonBounds();
shape.getBounds(bounds);
double leftLon;
double rightLon;
if (bounds.checkNoLongitudeBound()) {

View File

@ -26,7 +26,7 @@ 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 org.apache.lucene.geo3d.Bounds;
import org.apache.lucene.geo3d.LatLonBounds;
import org.apache.lucene.geo3d.GeoBBox;
import org.apache.lucene.geo3d.GeoBBoxFactory;
import org.apache.lucene.geo3d.GeoCircle;
@ -58,30 +58,31 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest
}
protected GeoBBox getBoundingBox(final GeoShape path) {
Bounds bounds = path.getBounds(null);
LatLonBounds bounds = new LatLonBounds();
path.getBounds(bounds);
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(planetModel, maxLat, minLat, leftLon, rightLon);
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(planetModel, maxLat, minLat, leftLon, rightLon);
}
abstract class Geo3dRectIntersectionTestHelper extends RectIntersectionTestHelper<Geo3dShape> {

View File

@ -0,0 +1,343 @@
package org.apache.lucene.bkdtree3d;
/*
* 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 org.apache.lucene.search.DocIdSet;
import org.apache.lucene.store.ByteArrayDataInput;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.util.Accountable;
import org.apache.lucene.util.DocIdSetBuilder;
import org.apache.lucene.util.RamUsageEstimator;
/** Handles intersection of a shape with a BKD tree previously written with {@link BKD3DTreeWriter}.
*
* @lucene.experimental */
final class BKD3DTreeReader implements Accountable {
final private int[] splitValues;
final private int leafNodeOffset;
final private long[] leafBlockFPs;
final int maxDoc;
final IndexInput in;
enum Relation {CELL_INSIDE_SHAPE, SHAPE_CROSSES_CELL, SHAPE_OUTSIDE_CELL, SHAPE_INSIDE_CELL};
interface ValueFilter {
boolean accept(int docID);
Relation compare(int cellXMin, int cellXMax, int cellYMin, int cellYMax, int cellZMin, int cellZMax);
}
public BKD3DTreeReader(IndexInput in, int maxDoc) throws IOException {
// Read index:
int numLeaves = in.readVInt();
leafNodeOffset = numLeaves;
// Tree is fully balanced binary tree, so number of nodes = numLeaves-1, except our nodeIDs are 1-based (splitValues[0] is unused):
splitValues = new int[numLeaves];
for(int i=0;i<numLeaves;i++) {
splitValues[i] = in.readInt();
}
leafBlockFPs = new long[numLeaves];
for(int i=0;i<numLeaves;i++) {
leafBlockFPs[i] = in.readVLong();
}
this.maxDoc = maxDoc;
this.in = in;
}
private static final class QueryState {
final IndexInput in;
byte[] scratch = new byte[16];
final ByteArrayDataInput scratchReader = new ByteArrayDataInput(scratch);
final DocIdSetBuilder docs;
final int xMin;
final int xMax;
final int yMin;
final int yMax;
final int zMin;
final int zMax;
final ValueFilter valueFilter;
public QueryState(IndexInput in, int maxDoc,
int xMin, int xMax,
int yMin, int yMax,
int zMin, int zMax,
ValueFilter valueFilter) {
this.in = in;
this.docs = new DocIdSetBuilder(maxDoc);
this.xMin = xMin;
this.xMax = xMax;
this.yMin = yMin;
this.yMax = yMax;
this.zMin = zMin;
this.zMax = zMax;
this.valueFilter = valueFilter;
}
}
public DocIdSet intersect(ValueFilter filter) throws IOException {
return intersect(Integer.MIN_VALUE, Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MAX_VALUE,
filter);
}
/** Optimized intersect which takes the 3D bbox for the query and uses that to avoid filter.compare calls
* when cells are clearly outside the bbox. */
public DocIdSet intersect(int xMin, int xMax, int yMin, int yMax, int zMin, int zMax, ValueFilter filter) throws IOException {
QueryState state = new QueryState(in.clone(), maxDoc,
xMin, xMax,
yMin, yMax,
zMin, zMax,
filter);
int hitCount = intersect(state, 1,
Integer.MIN_VALUE, Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MAX_VALUE);
// NOTE: hitCount is an over-estimate in the multi-valued case:
return state.docs.build(hitCount);
}
/** Fast path: this is called when the query rect fully encompasses all cells under this node. */
private int addAll(QueryState state, int nodeID) throws IOException {
//System.out.println(" addAll nodeID=" + nodeID + " leafNodeOffset=" + leafNodeOffset);
if (nodeID >= leafNodeOffset) {
/*
System.out.println("A: " + BKDTreeWriter.decodeLat(cellLatMinEnc)
+ " " + BKDTreeWriter.decodeLat(cellLatMaxEnc)
+ " " + BKDTreeWriter.decodeLon(cellLonMinEnc)
+ " " + BKDTreeWriter.decodeLon(cellLonMaxEnc));
*/
// Leaf node
long fp = leafBlockFPs[nodeID-leafNodeOffset];
//System.out.println(" leaf fp=" + fp);
state.in.seek(fp);
//System.out.println(" seek to leafFP=" + fp);
// How many points are stored in this leaf cell:
int count = state.in.readVInt();
//System.out.println(" count=" + count);
state.docs.grow(count);
for(int i=0;i<count;i++) {
int docID = state.in.readInt();
state.docs.add(docID);
// Up above in the recursion we asked valueFilter to relate our cell, and it returned Relation.CELL_INSIDE_SHAPE
// so all docs inside this cell better be accepted by the filter:
// NOTE: this is too anal, because we lost precision in the pack/unpack (8 bytes to 4 bytes), a point that's a bit above/below the
// earth's surface due to that quantization may incorrectly evaluate as not inside the shape:
// assert state.valueFilter.accept(docID);
}
return count;
} else {
int count = addAll(state, 2*nodeID);
count += addAll(state, 2*nodeID+1);
return count;
}
}
private int intersect(QueryState state,
int nodeID,
int cellXMin, int cellXMax,
int cellYMin, int cellYMax,
int cellZMin, int cellZMax)
throws IOException {
//System.out.println("BKD3D.intersect nodeID=" + nodeID + " cellX=" + cellXMin + " TO " + cellXMax + ", cellY=" + cellYMin + " TO " + cellYMax + ", cellZ=" + cellZMin + " TO " + cellZMax);
if (cellXMin >= state.xMin ||
cellXMax <= state.xMax ||
cellYMin >= state.yMin ||
cellYMax <= state.yMax ||
cellZMin >= state.zMin ||
cellZMax <= state.zMax) {
// Only call the filter when the current cell does not fully contain the bbox:
Relation r = state.valueFilter.compare(cellXMin, cellXMax,
cellYMin, cellYMax,
cellZMin, cellZMax);
//System.out.println(" relation: " + r);
if (r == Relation.SHAPE_OUTSIDE_CELL) {
// This cell is fully outside of the query shape: stop recursing
return 0;
} else if (r == Relation.CELL_INSIDE_SHAPE) {
// This cell is fully inside of the query shape: recursively add all points in this cell without filtering
/*
System.out.println(Thread.currentThread() + ": switch to addAll at cell" +
" x=" + Geo3DDocValuesFormat.decodeValue(cellXMin) + " to " + Geo3DDocValuesFormat.decodeValue(cellXMax) +
" y=" + Geo3DDocValuesFormat.decodeValue(cellYMin) + " to " + Geo3DDocValuesFormat.decodeValue(cellYMax) +
" z=" + Geo3DDocValuesFormat.decodeValue(cellZMin) + " to " + Geo3DDocValuesFormat.decodeValue(cellZMax));
*/
return addAll(state, nodeID);
} else {
// The cell crosses the shape boundary, so we fall through and do full filtering
}
} else {
// The whole point of the incoming bbox (state.xMin/xMax/etc.) is that it is
// supposed to fully enclose the shape, so this cell we are visiting, which
// fully contains the query's bbox, better in turn fully contain the shape!
assert state.valueFilter.compare(cellXMin, cellXMax, cellYMin, cellYMax, cellZMin, cellZMax) == Relation.SHAPE_INSIDE_CELL: "got " + state.valueFilter.compare(cellXMin, cellXMax, cellYMin, cellYMax, cellZMin, cellZMax);
}
//System.out.println("\nintersect node=" + nodeID + " vs " + leafNodeOffset);
if (nodeID >= leafNodeOffset) {
//System.out.println(" leaf");
// Leaf node; scan and filter all points in this block:
//System.out.println(" intersect leaf nodeID=" + nodeID + " vs leafNodeOffset=" + leafNodeOffset + " fp=" + leafBlockFPs[nodeID-leafNodeOffset]);
int hitCount = 0;
long fp = leafBlockFPs[nodeID-leafNodeOffset];
/*
System.out.println("I: " + BKDTreeWriter.decodeLat(cellLatMinEnc)
+ " " + BKDTreeWriter.decodeLat(cellLatMaxEnc)
+ " " + BKDTreeWriter.decodeLon(cellLonMinEnc)
+ " " + BKDTreeWriter.decodeLon(cellLonMaxEnc));
*/
state.in.seek(fp);
// How many points are stored in this leaf cell:
int count = state.in.readVInt();
state.docs.grow(count);
//System.out.println(" count=" + count);
for(int i=0;i<count;i++) {
int docID = state.in.readInt();
//System.out.println(" check docID=" + docID);
if (state.valueFilter.accept(docID)) {
state.docs.add(docID);
hitCount++;
}
}
return hitCount;
} else {
//System.out.println(" non-leaf");
int splitDim = BKD3DTreeWriter.getSplitDim(cellXMin, cellXMax,
cellYMin, cellYMax,
cellZMin, cellZMax);
int splitValue = splitValues[nodeID];
int count = 0;
if (splitDim == 0) {
//System.out.println(" split on lat=" + splitValue);
// Inner node split on x:
// Left node:
if (state.xMin <= splitValue) {
//System.out.println(" recurse left");
count += intersect(state,
2*nodeID,
cellXMin, splitValue,
cellYMin, cellYMax,
cellZMin, cellZMax);
}
// Right node:
if (state.xMax >= splitValue) {
//System.out.println(" recurse right");
count += intersect(state,
2*nodeID+1,
splitValue, cellXMax,
cellYMin, cellYMax,
cellZMin, cellZMax);
}
} else if (splitDim == 1) {
// Inner node split on y:
// System.out.println(" split on lon=" + splitValue);
// Left node:
if (state.yMin <= splitValue) {
// System.out.println(" recurse left");
count += intersect(state,
2*nodeID,
cellXMin, cellXMax,
cellYMin, splitValue,
cellZMin, cellZMax);
}
// Right node:
if (state.yMax >= splitValue) {
// System.out.println(" recurse right");
count += intersect(state,
2*nodeID+1,
cellXMin, cellXMax,
splitValue, cellYMax,
cellZMin, cellZMax);
}
} else {
// Inner node split on z:
// System.out.println(" split on lon=" + splitValue);
// Left node:
if (state.zMin <= splitValue) {
// System.out.println(" recurse left");
count += intersect(state,
2*nodeID,
cellXMin, cellXMax,
cellYMin, cellYMax,
cellZMin, splitValue);
}
// Right node:
if (state.zMax >= splitValue) {
// System.out.println(" recurse right");
count += intersect(state,
2*nodeID+1,
cellXMin, cellXMax,
cellYMin, cellYMax,
splitValue, cellZMax);
}
}
return count;
}
}
@Override
public long ramBytesUsed() {
return splitValues.length * RamUsageEstimator.NUM_BYTES_INT +
leafBlockFPs.length * RamUsageEstimator.NUM_BYTES_LONG;
}
}

View File

@ -0,0 +1,940 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Comparator;
import org.apache.lucene.store.ByteArrayDataInput;
import org.apache.lucene.store.ByteArrayDataOutput;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.InPlaceMergeSorter;
import org.apache.lucene.util.LongBitSet;
import org.apache.lucene.util.OfflineSorter.ByteSequencesWriter;
import org.apache.lucene.util.OfflineSorter;
import org.apache.lucene.util.RamUsageEstimator;
// TODO
// - we could also index "auto-prefix terms" here, and use better compression, and maybe only use for the "fully contained" case so we'd
// only index docIDs
// - the index could be efficiently encoded as an FST, so we don't have wasteful
// (monotonic) long[] leafBlockFPs; or we could use MonotonicLongValues ... but then
// the index is already plenty small: 60M OSM points --> 1.1 MB with 128 points
// per leaf, and you can reduce that by putting more points per leaf
// - we can quantize the split values to 2 bytes (short): http://people.csail.mit.edu/tmertens/papers/qkdtree.pdf
// - we could use threads while building; the higher nodes are very parallelizable
// - generalize to N dimenions? i think there are reasonable use cases here, e.g.
// 2 dimensional points to store houses, plus e.g. 3rd dimension for "household income"
/** Recursively builds a BKD tree to assign all incoming points to smaller
* and smaller rectangles until the number of points in a given
* rectangle is &lt= the <code>maxPointsInLeafNode</code>. The tree is
* fully balanced, which means the leaf nodes will have between 50% and 100% of
* the requested <code>maxPointsInLeafNode</code>, except for the adversarial case
* of indexing exactly the same point many times.
*
* <p>
* See <a href="https://www.cs.duke.edu/~pankaj/publications/papers/bkd-sstd.pdf">this paper</a> for details.
*
* <p>This consumes heap during writing: it allocates a <code>LongBitSet(numPoints)</code>,
* and for any nodes with fewer than <code>maxPointsSortInHeap</code>, it holds
* the points in memory as simple java arrays.
*
* <p>
* <b>NOTE</b>: This can write at most Integer.MAX_VALUE * <code>maxPointsInLeafNode</code> total points.
*
* @lucene.experimental */
class BKD3DTreeWriter {
// x (int), y (int), z (int) + ord (long) + docID (int)
static final int BYTES_PER_DOC = RamUsageEstimator.NUM_BYTES_LONG + 4 * RamUsageEstimator.NUM_BYTES_INT;
//static final boolean DEBUG = false;
public static final int DEFAULT_MAX_POINTS_IN_LEAF_NODE = 1024;
/** This works out to max of ~10 MB peak heap tied up during writing: */
public static final int DEFAULT_MAX_POINTS_SORT_IN_HEAP = 128*1024;;
private final byte[] scratchBytes = new byte[BYTES_PER_DOC];
private final ByteArrayDataOutput scratchBytesOutput = new ByteArrayDataOutput(scratchBytes);
private OfflineSorter.ByteSequencesWriter writer;
private GrowingHeapWriter heapWriter;
private Path tempInput;
private Path tempDir;
private final int maxPointsInLeafNode;
private final int maxPointsSortInHeap;
private long pointCount;
private final int[] scratchDocIDs;
public BKD3DTreeWriter() throws IOException {
this(DEFAULT_MAX_POINTS_IN_LEAF_NODE, DEFAULT_MAX_POINTS_SORT_IN_HEAP);
}
// TODO: instead of maxPointsSortInHeap, change to maxMBHeap ... the mapping is non-obvious:
public BKD3DTreeWriter(int maxPointsInLeafNode, int maxPointsSortInHeap) throws IOException {
verifyParams(maxPointsInLeafNode, maxPointsSortInHeap);
this.maxPointsInLeafNode = maxPointsInLeafNode;
this.maxPointsSortInHeap = maxPointsSortInHeap;
scratchDocIDs = new int[maxPointsInLeafNode];
// We write first maxPointsSortInHeap in heap, then cutover to offline for additional points:
heapWriter = new GrowingHeapWriter(maxPointsSortInHeap);
}
public static void verifyParams(int maxPointsInLeafNode, int maxPointsSortInHeap) {
if (maxPointsInLeafNode <= 0) {
throw new IllegalArgumentException("maxPointsInLeafNode must be > 0; got " + maxPointsInLeafNode);
}
if (maxPointsInLeafNode > ArrayUtil.MAX_ARRAY_LENGTH) {
throw new IllegalArgumentException("maxPointsInLeafNode must be <= ArrayUtil.MAX_ARRAY_LENGTH (= " + ArrayUtil.MAX_ARRAY_LENGTH + "); got " + maxPointsInLeafNode);
}
if (maxPointsSortInHeap < maxPointsInLeafNode) {
throw new IllegalArgumentException("maxPointsSortInHeap must be >= maxPointsInLeafNode; got " + maxPointsSortInHeap + " vs maxPointsInLeafNode="+ maxPointsInLeafNode);
}
if (maxPointsSortInHeap > ArrayUtil.MAX_ARRAY_LENGTH) {
throw new IllegalArgumentException("maxPointsSortInHeap must be <= ArrayUtil.MAX_ARRAY_LENGTH (= " + ArrayUtil.MAX_ARRAY_LENGTH + "); got " + maxPointsSortInHeap);
}
}
/** If the current segment has too many points then we switchover to temp files / offline sort. */
private void switchToOffline() throws IOException {
// OfflineSorter isn't thread safe, but our own private tempDir works around this:
tempDir = Files.createTempDirectory(OfflineSorter.defaultTempDir(), BKD3DTreeWriter.class.getSimpleName());
// For each .add we just append to this input file, then in .finish we sort this input and resursively build the tree:
tempInput = tempDir.resolve("in");
writer = new OfflineSorter.ByteSequencesWriter(tempInput);
for(int i=0;i<pointCount;i++) {
scratchBytesOutput.reset(scratchBytes);
scratchBytesOutput.writeInt(heapWriter.xs[i]);
scratchBytesOutput.writeInt(heapWriter.ys[i]);
scratchBytesOutput.writeInt(heapWriter.zs[i]);
scratchBytesOutput.writeVInt(heapWriter.docIDs[i]);
scratchBytesOutput.writeVLong(i);
// TODO: can/should OfflineSorter optimize the fixed-width case?
writer.write(scratchBytes, 0, scratchBytes.length);
}
heapWriter = null;
}
public void add(int x, int y, int z, int docID) throws IOException {
if (pointCount >= maxPointsSortInHeap) {
if (writer == null) {
switchToOffline();
}
scratchBytesOutput.reset(scratchBytes);
scratchBytesOutput.writeInt(x);
scratchBytesOutput.writeInt(y);
scratchBytesOutput.writeInt(z);
scratchBytesOutput.writeVInt(docID);
scratchBytesOutput.writeVLong(pointCount);
writer.write(scratchBytes, 0, scratchBytes.length);
} else {
// Not too many points added yet, continue using heap:
heapWriter.append(x, y, z, pointCount, docID);
}
pointCount++;
}
/** Changes incoming {@link ByteSequencesWriter} file to to fixed-width-per-entry file, because we need to be able to slice
* as we recurse in {@link #build}. */
private Writer convertToFixedWidth(Path in) throws IOException {
BytesRefBuilder scratch = new BytesRefBuilder();
scratch.grow(BYTES_PER_DOC);
BytesRef bytes = scratch.get();
ByteArrayDataInput dataReader = new ByteArrayDataInput();
OfflineSorter.ByteSequencesReader reader = null;
Writer sortedWriter = null;
boolean success = false;
try {
reader = new OfflineSorter.ByteSequencesReader(in);
sortedWriter = getWriter(pointCount);
for (long i=0;i<pointCount;i++) {
boolean result = reader.read(scratch);
assert result;
dataReader.reset(bytes.bytes, bytes.offset, bytes.length);
int x = dataReader.readInt();
int y = dataReader.readInt();
int z = dataReader.readInt();
int docID = dataReader.readVInt();
long ord = dataReader.readVLong();
assert docID >= 0: "docID=" + docID;
sortedWriter.append(x, y, z, ord, docID);
}
success = true;
} finally {
if (success) {
IOUtils.close(sortedWriter, reader);
} else {
IOUtils.closeWhileHandlingException(sortedWriter, reader);
try {
sortedWriter.destroy();
} catch (Throwable t) {
// Suppress to keep throwing original exc
}
}
}
return sortedWriter;
}
/** dim: 0=x, 1=y, 2=z */
private Writer sort(int dim) throws IOException {
if (heapWriter != null) {
assert pointCount < Integer.MAX_VALUE;
// All buffered points are still in heap
new InPlaceMergeSorter() {
@Override
protected void swap(int i, int j) {
int docID = heapWriter.docIDs[i];
heapWriter.docIDs[i] = heapWriter.docIDs[j];
heapWriter.docIDs[j] = docID;
long ord = heapWriter.ords[i];
heapWriter.ords[i] = heapWriter.ords[j];
heapWriter.ords[j] = ord;
int x = heapWriter.xs[i];
heapWriter.xs[i] = heapWriter.xs[j];
heapWriter.xs[j] = x;
int y = heapWriter.ys[i];
heapWriter.ys[i] = heapWriter.ys[j];
heapWriter.ys[j] = y;
int z = heapWriter.zs[i];
heapWriter.zs[i] = heapWriter.zs[j];
heapWriter.zs[j] = z;
}
@Override
protected int compare(int i, int j) {
int cmp;
if (dim == 0) {
cmp = Integer.compare(heapWriter.xs[i], heapWriter.xs[j]);
} else if (dim == 1) {
cmp = Integer.compare(heapWriter.ys[i], heapWriter.ys[j]);
} else {
cmp = Integer.compare(heapWriter.zs[i], heapWriter.zs[j]);
}
if (cmp != 0) {
return cmp;
}
// Tie-break
cmp = Integer.compare(heapWriter.docIDs[i], heapWriter.docIDs[j]);
if (cmp != 0) {
return cmp;
}
return Long.compare(heapWriter.ords[i], heapWriter.ords[j]);
}
}.sort(0, (int) pointCount);
HeapWriter sorted = new HeapWriter((int) pointCount);
//System.out.println("sorted dim=" + dim);
for(int i=0;i<pointCount;i++) {
/*
System.out.println(" docID=" + heapWriter.docIDs[i] +
" x=" + heapWriter.xs[i] +
" y=" + heapWriter.ys[i] +
" z=" + heapWriter.zs[i]);
*/
sorted.append(heapWriter.xs[i],
heapWriter.ys[i],
heapWriter.zs[i],
heapWriter.ords[i],
heapWriter.docIDs[i]);
}
sorted.close();
return sorted;
} else {
// Offline sort:
assert tempDir != null;
final ByteArrayDataInput reader = new ByteArrayDataInput();
Comparator<BytesRef> cmp = new Comparator<BytesRef>() {
private final ByteArrayDataInput readerB = new ByteArrayDataInput();
@Override
public int compare(BytesRef a, BytesRef b) {
reader.reset(a.bytes, a.offset, a.length);
final int xa = reader.readInt();
final int ya = reader.readInt();
final int za = reader.readInt();
final int docIDA = reader.readVInt();
final long ordA = reader.readVLong();
reader.reset(b.bytes, b.offset, b.length);
final int xb = reader.readInt();
final int yb = reader.readInt();
final int zb = reader.readInt();
final int docIDB = reader.readVInt();
final long ordB = reader.readVLong();
int cmp;
if (dim == 0) {
cmp = Integer.compare(xa, xb);
} else if (dim == 1) {
cmp = Integer.compare(ya, yb);
} else {
cmp = Integer.compare(za, zb);
}
if (cmp != 0) {
return cmp;
}
// Tie-break
cmp = Integer.compare(docIDA, docIDB);
if (cmp != 0) {
return cmp;
}
return Long.compare(ordA, ordB);
}
};
Path sorted = tempDir.resolve("sorted");
boolean success = false;
try {
OfflineSorter sorter = new OfflineSorter(cmp, OfflineSorter.BufferSize.automatic(), tempDir, OfflineSorter.MAX_TEMPFILES);
sorter.sort(tempInput, sorted);
Writer writer = convertToFixedWidth(sorted);
success = true;
return writer;
} finally {
if (success) {
IOUtils.rm(sorted);
} else {
IOUtils.deleteFilesIgnoringExceptions(sorted);
}
}
}
}
/** Writes the BKD tree to the provided {@link IndexOutput} and returns the file offset where index was written. */
public long finish(IndexOutput out) throws IOException {
//System.out.println("\nBKDTreeWriter.finish pointCount=" + pointCount + " out=" + out + " heapWriter=" + heapWriter + " maxPointsInLeafNode=" + maxPointsInLeafNode);
if (writer != null) {
writer.close();
}
LongBitSet bitSet = new LongBitSet(pointCount);
long countPerLeaf = pointCount;
long innerNodeCount = 1;
while (countPerLeaf > maxPointsInLeafNode) {
countPerLeaf = (countPerLeaf+1)/2;
innerNodeCount *= 2;
}
//System.out.println("innerNodeCount=" + innerNodeCount + " countPerLeaf=" + countPerLeaf);
if (1+2*innerNodeCount >= Integer.MAX_VALUE) {
throw new IllegalStateException("too many nodes; increase maxPointsInLeafNode (currently " + maxPointsInLeafNode + ") and reindex");
}
innerNodeCount--;
int numLeaves = (int) (innerNodeCount+1);
//System.out.println(" numLeaves=" + numLeaves);
// Indexed by nodeID, but first (root) nodeID is 1
int[] splitValues = new int[numLeaves];
// +1 because leaf count is power of 2 (e.g. 8), and innerNodeCount is power of 2 minus 1 (e.g. 7)
long[] leafBlockFPs = new long[numLeaves];
// Make sure the math above "worked":
assert pointCount / splitValues.length <= maxPointsInLeafNode: "pointCount=" + pointCount + " splitValues.length=" + splitValues.length + " maxPointsInLeafNode=" + maxPointsInLeafNode;
//System.out.println(" avg pointsPerLeaf=" + (pointCount/splitValues.length));
// Sort all docs once by x, once by y, once by z:
Writer xSortedWriter = null;
Writer ySortedWriter = null;
Writer zSortedWriter = null;
boolean success = false;
try {
xSortedWriter = sort(0);
ySortedWriter = sort(1);
zSortedWriter = sort(2);
heapWriter = null;
build(1, numLeaves,
new PathSlice(xSortedWriter, 0, pointCount),
new PathSlice(ySortedWriter, 0, pointCount),
new PathSlice(zSortedWriter, 0, pointCount),
bitSet, out,
Integer.MIN_VALUE, Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MAX_VALUE,
splitValues,
leafBlockFPs);
success = true;
} finally {
if (success) {
xSortedWriter.destroy();
ySortedWriter.destroy();
zSortedWriter.destroy();
IOUtils.rm(tempInput);
} else {
try {
xSortedWriter.destroy();
} catch (Throwable t) {
// Suppress to keep throwing original exc
}
try {
ySortedWriter.destroy();
} catch (Throwable t) {
// Suppress to keep throwing original exc
}
try {
zSortedWriter.destroy();
} catch (Throwable t) {
// Suppress to keep throwing original exc
}
IOUtils.deleteFilesIgnoringExceptions(tempInput);
}
}
//System.out.println("Total nodes: " + innerNodeCount);
// Write index:
long indexFP = out.getFilePointer();
//System.out.println("indexFP=" + indexFP);
out.writeVInt(numLeaves);
// NOTE: splitValues[0] is unused, because nodeID is 1-based:
for (int i=0;i<splitValues.length;i++) {
out.writeInt(splitValues[i]);
}
for (int i=0;i<leafBlockFPs.length;i++) {
out.writeVLong(leafBlockFPs[i]);
}
if (tempDir != null) {
// If we had to go offline, we should have removed all temp files we wrote:
assert directoryIsEmpty(tempDir);
IOUtils.rm(tempDir);
}
return indexFP;
}
// Called only from assert
private boolean directoryIsEmpty(Path in) {
try (DirectoryStream<Path> dir = Files.newDirectoryStream(in)) {
for (Path path : dir) {
assert false: "dir=" + in + " still has file=" + path;
return false;
}
} catch (IOException ioe) {
// Just ignore: we are only called from assert
}
return true;
}
/** Sliced reference to points in an OfflineSorter.ByteSequencesWriter file. */
private static final class PathSlice {
final Writer writer;
final long start;
final long count;
public PathSlice(Writer writer, long start, long count) {
this.writer = writer;
this.start = start;
this.count = count;
}
@Override
public String toString() {
return "PathSlice(start=" + start + " count=" + count + " writer=" + writer + ")";
}
}
/** Marks bits for the ords (points) that belong in the left sub tree. */
private int markLeftTree(int splitDim, PathSlice source, LongBitSet bitSet,
int minX, int maxX,
int minY, int maxY,
int minZ, int maxZ) throws IOException {
// This is the size of our left tree
long leftCount = source.count / 2;
// Read the split value:
//if (DEBUG) System.out.println(" leftCount=" + leftCount + " vs " + source.count);
Reader reader = source.writer.getReader(source.start + leftCount);
boolean success = false;
int splitValue;
try {
boolean result = reader.next();
assert result;
int x = reader.x();
assert x >= minX && x <= maxX: "x=" + x + " minX=" + minX + " maxX=" + maxX;
int y = reader.y();
assert y >= minY && y <= maxY: "y=" + y + " minY=" + minY + " maxY=" + maxY;
int z = reader.z();
assert z >= minZ && z <= maxZ: "z=" + z + " minZ=" + minZ + " maxZ=" + maxZ;
if (splitDim == 0) {
splitValue = x;
} else if (splitDim == 1) {
splitValue = y;
} else {
splitValue = z;
}
success = true;
} finally {
if (success) {
IOUtils.close(reader);
} else {
IOUtils.closeWhileHandlingException(reader);
}
}
// Mark ords that fall into the left half, and also handle the == boundary case:
assert bitSet.cardinality() == 0: "cardinality=" + bitSet.cardinality();
success = false;
reader = source.writer.getReader(source.start);
try {
int lastValue = Integer.MIN_VALUE;
for (int i=0;i<leftCount;i++) {
boolean result = reader.next();
assert result;
int x = reader.x();
int y = reader.y();
int z = reader.z();
int value;
if (splitDim == 0) {
value = x;
} else if (splitDim == 1) {
value = y;
} else {
value = z;
}
// Our input source is supposed to be sorted on the incoming dimension:
assert value >= lastValue;
lastValue = value;
assert value <= splitValue: "i=" + i + " value=" + value + " vs splitValue=" + splitValue;
long ord = reader.ord();
int docID = reader.docID();
assert docID >= 0: "docID=" + docID + " reader=" + reader;
// We should never see dup ords:
assert bitSet.get(ord) == false;
bitSet.set(ord);
}
success = true;
} finally {
if (success) {
IOUtils.close(reader);
} else {
IOUtils.closeWhileHandlingException(reader);
}
}
assert leftCount == bitSet.cardinality(): "leftCount=" + leftCount + " cardinality=" + bitSet.cardinality();
return splitValue;
}
// Split on the dim with the largest range:
static int getSplitDim(int minX, int maxX, int minY, int maxY, int minZ, int maxZ) {
long xRange = (long) maxX - (long) minX;
long yRange = (long) maxY - (long) minY;
long zRange = (long) maxZ - (long) minZ;
if (xRange > yRange) {
if (xRange > zRange) {
return 0;
} else {
return 2;
}
} else if (yRange > zRange) {
return 1;
} else {
return 2;
}
}
/** The incoming PathSlice for the dim we will split is already partitioned/sorted. */
private void build(int nodeID, int leafNodeOffset,
PathSlice lastXSorted,
PathSlice lastYSorted,
PathSlice lastZSorted,
LongBitSet bitSet,
IndexOutput out,
int minX, int maxX,
int minY, int maxY,
int minZ, int maxZ,
int[] splitValues,
long[] leafBlockFPs) throws IOException {
long count = lastXSorted.count;
assert count > 0;
assert count <= ArrayUtil.MAX_ARRAY_LENGTH;
assert count == lastYSorted.count;
assert count == lastZSorted.count;
//if (DEBUG) System.out.println("\nBUILD: nodeID=" + nodeID + " leafNodeOffset=" + leafNodeOffset + "\n lastXSorted=" + lastXSorted + "\n lastYSorted=" + lastYSorted + "\n lastZSorted=" + lastZSorted + "\n count=" + lastXSorted.count + " x=" + minX + " TO " + maxX + " y=" + minY + " TO " + maxY + " z=" + minZ + " TO " + maxZ);
if (nodeID >= leafNodeOffset) {
// Leaf node: write block
//if (DEBUG) System.out.println(" leaf");
assert maxX >= minX;
assert maxY >= minY;
assert maxZ >= minZ;
//System.out.println("\nleaf:\n lat range: " + ((long) maxLatEnc-minLatEnc));
//System.out.println(" lon range: " + ((long) maxLonEnc-minLonEnc));
// Sort by docID in the leaf so we get sequentiality at search time (may not matter?):
Reader reader = lastXSorted.writer.getReader(lastXSorted.start);
assert count <= scratchDocIDs.length: "count=" + count + " scratchDocIDs.length=" + scratchDocIDs.length;
boolean success = false;
try {
for (int i=0;i<count;i++) {
// NOTE: we discard ord at this point; we only needed it temporarily
// during building to uniquely identify each point to properly handle
// the multi-valued case (one docID having multiple values):
// We also discard lat/lon, since at search time, we reside on the
// wrapped doc values for this:
boolean result = reader.next();
assert result;
scratchDocIDs[i] = reader.docID();
}
success = true;
} finally {
if (success) {
IOUtils.close(reader);
} else {
IOUtils.closeWhileHandlingException(reader);
}
}
Arrays.sort(scratchDocIDs, 0, (int) count);
// Dedup docIDs: for the multi-valued case where more than one value for the doc
// wound up in this leaf cell, we only need to store the docID once:
int lastDocID = -1;
int uniqueCount = 0;
for(int i=0;i<count;i++) {
int docID = scratchDocIDs[i];
if (docID != lastDocID) {
uniqueCount++;
lastDocID = docID;
}
}
assert uniqueCount <= count;
long startFP = out.getFilePointer();
out.writeVInt(uniqueCount);
// Save the block file pointer:
leafBlockFPs[nodeID - leafNodeOffset] = startFP;
//System.out.println(" leafFP=" + startFP);
lastDocID = -1;
for (int i=0;i<count;i++) {
// Absolute int encode; with "vInt of deltas" encoding, the .kdd size dropped from
// 697 MB -> 539 MB, but query time for 225 queries went from 1.65 sec -> 2.64 sec.
// I think if we also indexed prefix terms here we could do less costly compression
// on those lists:
int docID = scratchDocIDs[i];
if (docID != lastDocID) {
out.writeInt(docID);
//System.out.println(" write docID=" + docID);
lastDocID = docID;
}
}
//long endFP = out.getFilePointer();
//System.out.println(" bytes/doc: " + ((endFP - startFP) / count));
} else {
int splitDim = getSplitDim(minX, maxX, minY, maxY, minZ, maxZ);
//System.out.println(" splitDim=" + splitDim);
PathSlice source;
if (splitDim == 0) {
source = lastXSorted;
} else if (splitDim == 1) {
source = lastYSorted;
} else {
source = lastZSorted;
}
// We let ties go to either side, so we should never get down to count == 0, even
// in adversarial case (all values are the same):
assert count > 0;
// Inner node: partition/recurse
//if (DEBUG) System.out.println(" non-leaf");
assert nodeID < splitValues.length: "nodeID=" + nodeID + " splitValues.length=" + splitValues.length;
int splitValue = markLeftTree(splitDim, source, bitSet,
minX, maxX,
minY, maxY,
minZ, maxZ);
long leftCount = count/2;
// TODO: we could save split value in here so we don't have to re-open file later:
// Partition the other (not split) dims into sorted left and right sets, so we can recurse.
// This is somewhat hairy: we partition the next X, Y set according to how we had just
// partitioned the Z set, etc.
Writer[] leftWriters = new Writer[3];
Writer[] rightWriters = new Writer[3];
for(int dim=0;dim<3;dim++) {
if (dim == splitDim) {
continue;
}
Writer leftWriter = null;
Writer rightWriter = null;
Reader reader = null;
boolean success = false;
int nextLeftCount = 0;
PathSlice nextSource;
if (dim == 0) {
nextSource = lastXSorted;
} else if (dim == 1) {
nextSource = lastYSorted;
} else {
nextSource = lastZSorted;
}
try {
leftWriter = getWriter(leftCount);
rightWriter = getWriter(nextSource.count - leftCount);
assert nextSource.count == count;
reader = nextSource.writer.getReader(nextSource.start);
// TODO: we could compute the split value here for each sub-tree and save an O(N) pass on recursion, but makes code hairier and only
// changes the constant factor of building, not the big-oh:
for (int i=0;i<count;i++) {
boolean result = reader.next();
assert result;
int x = reader.x();
int y = reader.y();
int z = reader.z();
long ord = reader.ord();
int docID = reader.docID();
assert docID >= 0: "docID=" + docID + " reader=" + reader;
//System.out.println(" i=" + i + " x=" + x + " ord=" + ord + " docID=" + docID);
if (bitSet.get(ord)) {
if (splitDim == 0) {
assert x <= splitValue: "x=" + x + " splitValue=" + splitValue;
} else if (splitDim == 1) {
assert y <= splitValue: "y=" + y + " splitValue=" + splitValue;
} else {
assert z <= splitValue: "z=" + z + " splitValue=" + splitValue;
}
leftWriter.append(x, y, z, ord, docID);
nextLeftCount++;
} else {
if (splitDim == 0) {
assert x >= splitValue: "x=" + x + " splitValue=" + splitValue;
} else if (splitDim == 1) {
assert y >= splitValue: "y=" + y + " splitValue=" + splitValue;
} else {
assert z >= splitValue: "z=" + z + " splitValue=" + splitValue;
}
rightWriter.append(x, y, z, ord, docID);
}
}
success = true;
} finally {
if (success) {
IOUtils.close(reader, leftWriter, rightWriter);
} else {
IOUtils.closeWhileHandlingException(reader, leftWriter, rightWriter);
}
}
assert leftCount == nextLeftCount: "leftCount=" + leftCount + " nextLeftCount=" + nextLeftCount;
leftWriters[dim] = leftWriter;
rightWriters[dim] = rightWriter;
}
bitSet.clear(0, pointCount);
long rightCount = count - leftCount;
boolean success = false;
try {
if (splitDim == 0) {
build(2*nodeID, leafNodeOffset,
new PathSlice(source.writer, source.start, leftCount),
new PathSlice(leftWriters[1], 0, leftCount),
new PathSlice(leftWriters[2], 0, leftCount),
bitSet,
out,
minX, splitValue,
minY, maxY,
minZ, maxZ,
splitValues, leafBlockFPs);
leftWriters[1].destroy();
leftWriters[2].destroy();
build(2*nodeID+1, leafNodeOffset,
new PathSlice(source.writer, source.start+leftCount, rightCount),
new PathSlice(rightWriters[1], 0, rightCount),
new PathSlice(rightWriters[2], 0, rightCount),
bitSet,
out,
splitValue, maxX,
minY, maxY,
minZ, maxZ,
splitValues, leafBlockFPs);
rightWriters[1].destroy();
rightWriters[2].destroy();
} else if (splitDim == 1) {
build(2*nodeID, leafNodeOffset,
new PathSlice(leftWriters[0], 0, leftCount),
new PathSlice(source.writer, source.start, leftCount),
new PathSlice(leftWriters[2], 0, leftCount),
bitSet,
out,
minX, maxX,
minY, splitValue,
minZ, maxZ,
splitValues, leafBlockFPs);
leftWriters[0].destroy();
leftWriters[2].destroy();
build(2*nodeID+1, leafNodeOffset,
new PathSlice(rightWriters[0], 0, rightCount),
new PathSlice(source.writer, source.start+leftCount, rightCount),
new PathSlice(rightWriters[2], 0, rightCount),
bitSet,
out,
minX, maxX,
splitValue, maxY,
minZ, maxZ,
splitValues, leafBlockFPs);
rightWriters[0].destroy();
rightWriters[2].destroy();
} else {
build(2*nodeID, leafNodeOffset,
new PathSlice(leftWriters[0], 0, leftCount),
new PathSlice(leftWriters[1], 0, leftCount),
new PathSlice(source.writer, source.start, leftCount),
bitSet,
out,
minX, maxX,
minY, maxY,
minZ, splitValue,
splitValues, leafBlockFPs);
leftWriters[0].destroy();
leftWriters[1].destroy();
build(2*nodeID+1, leafNodeOffset,
new PathSlice(rightWriters[0], 0, rightCount),
new PathSlice(rightWriters[1], 0, rightCount),
new PathSlice(source.writer, source.start+leftCount, rightCount),
bitSet,
out,
minX, maxX,
minY, maxY,
splitValue, maxZ,
splitValues, leafBlockFPs);
rightWriters[0].destroy();
rightWriters[1].destroy();
}
success = true;
} finally {
if (success == false) {
for(Writer writer : leftWriters) {
if (writer != null) {
try {
writer.destroy();
} catch (Throwable t) {
// Suppress to keep throwing original exc
}
}
}
for(Writer writer : rightWriters) {
if (writer != null) {
try {
writer.destroy();
} catch (Throwable t) {
// Suppress to keep throwing original exc
}
}
}
}
}
splitValues[nodeID] = splitValue;
}
}
Writer getWriter(long count) throws IOException {
if (count < maxPointsSortInHeap) {
return new HeapWriter((int) count);
} else {
return new OfflineWriter(tempDir, count);
}
}
}

View File

@ -0,0 +1,42 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.util.BytesRef;
class Geo3DBinaryDocValues extends BinaryDocValues {
final BKD3DTreeReader bkdTreeReader;
final BinaryDocValues delegate;
final double planetMax;
public Geo3DBinaryDocValues(BKD3DTreeReader bkdTreeReader, BinaryDocValues delegate, double planetMax) {
this.bkdTreeReader = bkdTreeReader;
this.delegate = delegate;
this.planetMax = planetMax;
}
public BKD3DTreeReader getBKD3DTreeReader() {
return bkdTreeReader;
}
@Override
public BytesRef get(int docID) {
return delegate.get(docID);
}
}

View File

@ -0,0 +1,139 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.apache.lucene.codecs.CodecUtil;
import org.apache.lucene.codecs.DocValuesConsumer;
import org.apache.lucene.geo3d.PlanetModel;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.IndexFileNames;
import org.apache.lucene.index.SegmentWriteState;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.IOUtils;
import java.io.Closeable;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
class Geo3DDocValuesConsumer extends DocValuesConsumer implements Closeable {
final DocValuesConsumer delegate;
final int maxPointsInLeafNode;
final int maxPointsSortInHeap;
final IndexOutput out;
final Map<Integer,Long> fieldIndexFPs = new HashMap<>();
final SegmentWriteState state;
public Geo3DDocValuesConsumer(PlanetModel planetModel, DocValuesConsumer delegate, SegmentWriteState state, int maxPointsInLeafNode, int maxPointsSortInHeap) throws IOException {
BKD3DTreeWriter.verifyParams(maxPointsInLeafNode, maxPointsSortInHeap);
this.delegate = delegate;
this.maxPointsInLeafNode = maxPointsInLeafNode;
this.maxPointsSortInHeap = maxPointsSortInHeap;
this.state = state;
String datFileName = IndexFileNames.segmentFileName(state.segmentInfo.name, state.segmentSuffix, Geo3DDocValuesFormat.DATA_EXTENSION);
out = state.directory.createOutput(datFileName, state.context);
CodecUtil.writeIndexHeader(out, Geo3DDocValuesFormat.DATA_CODEC_NAME, Geo3DDocValuesFormat.DATA_VERSION_CURRENT,
state.segmentInfo.getId(), state.segmentSuffix);
// We write the max for this PlanetModel into the index so we know we are decoding correctly at search time, and so we can also do
// best-effort check that the search time PlanetModel "matches":
out.writeLong(Double.doubleToLongBits(planetModel.getMaximumMagnitude()));
}
@Override
public void close() throws IOException {
boolean success = false;
try {
CodecUtil.writeFooter(out);
success = true;
} finally {
if (success) {
IOUtils.close(delegate, out);
} else {
IOUtils.closeWhileHandlingException(delegate, out);
}
}
String metaFileName = IndexFileNames.segmentFileName(state.segmentInfo.name, state.segmentSuffix, Geo3DDocValuesFormat.META_EXTENSION);
IndexOutput metaOut = state.directory.createOutput(metaFileName, state.context);
success = false;
try {
CodecUtil.writeIndexHeader(metaOut, Geo3DDocValuesFormat.META_CODEC_NAME, Geo3DDocValuesFormat.META_VERSION_CURRENT,
state.segmentInfo.getId(), state.segmentSuffix);
metaOut.writeVInt(fieldIndexFPs.size());
for(Map.Entry<Integer,Long> ent : fieldIndexFPs.entrySet()) {
metaOut.writeVInt(ent.getKey());
metaOut.writeVLong(ent.getValue());
}
CodecUtil.writeFooter(metaOut);
success = true;
} finally {
if (success) {
IOUtils.close(metaOut);
} else {
IOUtils.closeWhileHandlingException(metaOut);
}
}
}
@Override
public void addSortedNumericField(FieldInfo field, Iterable<Number> docToValueCount, Iterable<Number> values) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void addNumericField(FieldInfo field, Iterable<Number> values) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void addBinaryField(FieldInfo field, Iterable<BytesRef> values) throws IOException {
delegate.addBinaryField(field, values);
BKD3DTreeWriter writer = new BKD3DTreeWriter(maxPointsInLeafNode, maxPointsSortInHeap);
Iterator<BytesRef> valuesIt = values.iterator();
for (int docID=0;docID<state.segmentInfo.maxDoc();docID++) {
assert valuesIt.hasNext();
BytesRef value = valuesIt.next();
// TODO: we should allow multi-valued here, just appended into the BDV
// 3 ints packed into byte[]
if (value != null) {
assert value.length == 12;
int x = Geo3DDocValuesFormat.readInt(value.bytes, value.offset);
int y = Geo3DDocValuesFormat.readInt(value.bytes, value.offset+4);
int z = Geo3DDocValuesFormat.readInt(value.bytes, value.offset+8);
writer.add(x, y, z, docID);
}
}
long indexStartFP = writer.finish(out);
fieldIndexFPs.put(field.number, indexStartFP);
}
@Override
public void addSortedField(FieldInfo field, Iterable<BytesRef> values, Iterable<Number> docToOrd) {
throw new UnsupportedOperationException();
}
@Override
public void addSortedSetField(FieldInfo field, Iterable<BytesRef> values, Iterable<Number> docToOrdCount, Iterable<Number> ords) {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,168 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.apache.lucene.codecs.DocValuesConsumer;
import org.apache.lucene.codecs.DocValuesFormat;
import org.apache.lucene.codecs.DocValuesProducer;
import org.apache.lucene.codecs.lucene50.Lucene50DocValuesFormat;
import org.apache.lucene.geo3d.PlanetModel;
import org.apache.lucene.geo3d.Vector;
import org.apache.lucene.index.SegmentReadState;
import org.apache.lucene.index.SegmentWriteState;
import java.io.IOException;
/**
* A {@link DocValuesFormat} to efficiently index geo-spatial 3D x,y,z points
* from {@link Geo3DPointField} for fast shape intersection queries using
* ({@link PointInGeo3DShapeQuery})
*
* <p>This wraps {@link Lucene50DocValuesFormat}, but saves its own BKD tree
* structures to disk for fast query-time intersection. See <a
* href="https://www.cs.duke.edu/~pankaj/publications/papers/bkd-sstd.pdf">this paper</a>
* for details.
*
* <p>The BKD tree slices up 3D x,y,z space into smaller and
* smaller 3D rectangles, until the smallest rectangles have approximately
* between X/2 and X (X default is 1024) points in them, at which point
* such leaf cells are written as a block to disk, while the index tree
* structure records how space was sub-divided is loaded into HEAP
* at search time. At search time, the tree is recursed based on whether
* each of left or right child overlap with the query shape, and once
* a leaf block is reached, all documents in that leaf block are collected
* if the cell is fully enclosed by the query shape, or filtered and then
* collected, if not.
*
* <p>The index is also quite compact, because docs only appear once in
* the tree (no "prefix terms").
*
* <p>In addition to the files written by {@link Lucene50DocValuesFormat}, this format writes:
* <ol>
* <li><tt>.kd3d</tt>: BKD leaf data and index</li>
* <li><tt>.kd3m</tt>: BKD metadata</li>
* </ol>
*
* <p>The disk format is experimental and free to change suddenly, and this code
* likely has new and exciting bugs!
*
* @lucene.experimental */
public class Geo3DDocValuesFormat extends DocValuesFormat {
static final String DATA_CODEC_NAME = "Geo3DData";
static final int DATA_VERSION_START = 0;
static final int DATA_VERSION_CURRENT = DATA_VERSION_START;
static final String DATA_EXTENSION = "g3dd";
static final String META_CODEC_NAME = "Geo3DMeta";
static final int META_VERSION_START = 0;
static final int META_VERSION_CURRENT = META_VERSION_START;
static final String META_EXTENSION = "g3dm";
private final int maxPointsInLeafNode;
private final int maxPointsSortInHeap;
private final DocValuesFormat delegate = new Lucene50DocValuesFormat();
private final PlanetModel planetModel;
/** Default constructor */
public Geo3DDocValuesFormat() {
this(PlanetModel.WGS84, BKD3DTreeWriter.DEFAULT_MAX_POINTS_IN_LEAF_NODE, BKD3DTreeWriter.DEFAULT_MAX_POINTS_SORT_IN_HEAP);
}
/** Creates this with custom configuration.
*
* @param planetModel the {@link PlanetModel} to use; this is only used when writing
* @param maxPointsInLeafNode Maximum number of points in each leaf cell. Smaller values create a deeper tree with larger in-heap index and possibly
* faster searching. The default is 1024.
* @param maxPointsSortInHeap Maximum number of points where in-heap sort can be used. When the number of points exceeds this, a (slower)
* offline sort is used. The default is 128 * 1024.
*
* @lucene.experimental */
public Geo3DDocValuesFormat(PlanetModel planetModel, int maxPointsInLeafNode, int maxPointsSortInHeap) {
super("BKD3DTree");
BKD3DTreeWriter.verifyParams(maxPointsInLeafNode, maxPointsSortInHeap);
this.maxPointsInLeafNode = maxPointsInLeafNode;
this.maxPointsSortInHeap = maxPointsSortInHeap;
this.planetModel = planetModel;
}
@Override
public DocValuesConsumer fieldsConsumer(final SegmentWriteState state) throws IOException {
return new Geo3DDocValuesConsumer(planetModel, delegate.fieldsConsumer(state), state, maxPointsInLeafNode, maxPointsSortInHeap);
}
@Override
public DocValuesProducer fieldsProducer(SegmentReadState state) throws IOException {
return new Geo3DDocValuesProducer(delegate.fieldsProducer(state), state);
}
/** Clips the incoming value to the allowed min/max range before encoding, instead of throwing an exception. */
static int encodeValueLenient(double planetMax, double x) {
if (x > planetMax) {
x = planetMax;
} else if (x < -planetMax) {
x = -planetMax;
}
return encodeValue(planetMax, x);
}
static int encodeValue(double planetMax, double x) {
if (x > planetMax) {
throw new IllegalArgumentException("value=" + x + " is out-of-bounds (greater than planetMax=" + planetMax + ")");
}
if (x < -planetMax) {
throw new IllegalArgumentException("value=" + x + " is out-of-bounds (less than than -planetMax=" + -planetMax + ")");
}
long y = Math.round (x * (Integer.MAX_VALUE / planetMax));
assert y >= Integer.MIN_VALUE;
assert y <= Integer.MAX_VALUE;
return (int) y;
}
/** Center decode */
static double decodeValueCenter(double planetMax, int x) {
return x * (planetMax / Integer.MAX_VALUE);
}
/** More negative decode, at bottom of cell */
static double decodeValueMin(double planetMax, int x) {
return (((double)x) - 0.5) * (planetMax / Integer.MAX_VALUE);
}
/** More positive decode, at top of cell */
static double decodeValueMax(double planetMax, int x) {
return (((double)x) + 0.5) * (planetMax / Integer.MAX_VALUE);
}
static int readInt(byte[] bytes, int offset) {
return ((bytes[offset] & 0xFF) << 24) | ((bytes[offset+1] & 0xFF) << 16)
| ((bytes[offset+2] & 0xFF) << 8) | (bytes[offset+3] & 0xFF);
}
static void writeInt(int value, byte[] bytes, int offset) {
bytes[offset] = (byte) ((value >> 24) & 0xff);
bytes[offset+1] = (byte) ((value >> 16) & 0xff);
bytes[offset+2] = (byte) ((value >> 8) & 0xff);
bytes[offset+3] = (byte) (value & 0xff);
}
}

View File

@ -0,0 +1,177 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.lucene.codecs.CodecUtil;
import org.apache.lucene.codecs.DocValuesProducer;
import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.IndexFileNames;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.SegmentReadState;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.store.ChecksumIndexInput;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.util.Accountable;
import org.apache.lucene.util.Accountables;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.RamUsageEstimator;
class Geo3DDocValuesProducer extends DocValuesProducer {
private final Map<String,BKD3DTreeReader> treeReaders = new HashMap<>();
private final Map<Integer,Long> fieldToIndexFPs = new HashMap<>();
private final IndexInput datIn;
private final AtomicLong ramBytesUsed;
private final int maxDoc;
private final DocValuesProducer delegate;
private final boolean merging;
private final double planetMax;
public Geo3DDocValuesProducer(DocValuesProducer delegate, SegmentReadState state) throws IOException {
String metaFileName = IndexFileNames.segmentFileName(state.segmentInfo.name, state.segmentSuffix, Geo3DDocValuesFormat.META_EXTENSION);
ChecksumIndexInput metaIn = state.directory.openChecksumInput(metaFileName, state.context);
CodecUtil.checkIndexHeader(metaIn, Geo3DDocValuesFormat.META_CODEC_NAME, Geo3DDocValuesFormat.META_VERSION_START, Geo3DDocValuesFormat.META_VERSION_CURRENT,
state.segmentInfo.getId(), state.segmentSuffix);
int fieldCount = metaIn.readVInt();
for(int i=0;i<fieldCount;i++) {
int fieldNumber = metaIn.readVInt();
long indexFP = metaIn.readVLong();
fieldToIndexFPs.put(fieldNumber, indexFP);
}
CodecUtil.checkFooter(metaIn);
metaIn.close();
String datFileName = IndexFileNames.segmentFileName(state.segmentInfo.name, state.segmentSuffix, Geo3DDocValuesFormat.DATA_EXTENSION);
datIn = state.directory.openInput(datFileName, state.context);
CodecUtil.checkIndexHeader(datIn, Geo3DDocValuesFormat.DATA_CODEC_NAME, Geo3DDocValuesFormat.DATA_VERSION_START, Geo3DDocValuesFormat.DATA_VERSION_CURRENT,
state.segmentInfo.getId(), state.segmentSuffix);
planetMax = Double.longBitsToDouble(datIn.readLong());
ramBytesUsed = new AtomicLong(RamUsageEstimator.shallowSizeOfInstance(getClass()));
maxDoc = state.segmentInfo.maxDoc();
this.delegate = delegate;
merging = false;
}
// clone for merge: we don't hang onto the Geo3Ds we load
Geo3DDocValuesProducer(Geo3DDocValuesProducer orig) throws IOException {
assert Thread.holdsLock(orig);
datIn = orig.datIn.clone();
ramBytesUsed = new AtomicLong(orig.ramBytesUsed.get());
delegate = orig.delegate.getMergeInstance();
fieldToIndexFPs.putAll(orig.fieldToIndexFPs);
treeReaders.putAll(orig.treeReaders);
merging = true;
maxDoc = orig.maxDoc;
planetMax = orig.planetMax;
}
@Override
public SortedNumericDocValues getSortedNumeric(FieldInfo field) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void close() throws IOException {
IOUtils.close(datIn, delegate);
}
@Override
public void checkIntegrity() throws IOException {
CodecUtil.checksumEntireFile(datIn);
}
@Override
public NumericDocValues getNumeric(FieldInfo field) {
throw new UnsupportedOperationException();
}
@Override
public synchronized BinaryDocValues getBinary(FieldInfo field) throws IOException {
BKD3DTreeReader treeReader = treeReaders.get(field.name);
if (treeReader == null) {
// Lazy load
Long fp = fieldToIndexFPs.get(field.number);
if (fp == null) {
throw new IllegalArgumentException("this field was not indexed as a BKDPointField");
}
// LUCENE-6697: never do real IOPs with the original IndexInput because search
// threads can be concurrently cloning it:
IndexInput clone = datIn.clone();
clone.seek(fp);
treeReader = new BKD3DTreeReader(clone, maxDoc);
// Only hang onto the reader when we are not merging:
if (merging == false) {
treeReaders.put(field.name, treeReader);
ramBytesUsed.addAndGet(treeReader.ramBytesUsed());
}
}
return new Geo3DBinaryDocValues(treeReader, delegate.getBinary(field), planetMax);
}
@Override
public SortedDocValues getSorted(FieldInfo field) {
throw new UnsupportedOperationException();
}
@Override
public SortedSetDocValues getSortedSet(FieldInfo field) {
throw new UnsupportedOperationException();
}
@Override
public Bits getDocsWithField(FieldInfo field) throws IOException {
return delegate.getDocsWithField(field);
}
@Override
public synchronized Collection<Accountable> getChildResources() {
List<Accountable> resources = new ArrayList<>();
for(Map.Entry<String,BKD3DTreeReader> ent : treeReaders.entrySet()) {
resources.add(Accountables.namedAccountable("field " + ent.getKey(), ent.getValue()));
}
resources.add(Accountables.namedAccountable("delegate", delegate));
return resources;
}
@Override
public synchronized DocValuesProducer getMergeInstance() throws IOException {
return new Geo3DDocValuesProducer(this);
}
@Override
public long ramBytesUsed() {
return ramBytesUsed.get() + delegate.ramBytesUsed();
}
}

View File

@ -0,0 +1,70 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.apache.lucene.geo3d.PlanetModel;
import org.apache.lucene.geo3d.GeoPoint;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.util.BytesRef;
// TODO: allow multi-valued, packing all points into a single BytesRef
/** Add this to a document to index lat/lon point, but be sure to use {@link Geo3DDocValuesFormat} for the field.
* @lucene.experimental */
public final class Geo3DPointField extends Field {
/** Indexing {@link FieldType}. */
public static final FieldType TYPE = new FieldType();
static {
TYPE.setDocValuesType(DocValuesType.BINARY);
TYPE.freeze();
}
/**
* Creates a new Geo3DPointField field with the specified lat, lon (in radians), given a planet model.
*
* @throws IllegalArgumentException if the field name is null or lat or lon are out of bounds
*/
public Geo3DPointField(String name, PlanetModel planetModel, double lat, double lon) {
super(name, TYPE);
// Translate lat/lon to x,y,z:
final GeoPoint point = new GeoPoint(planetModel, lat, lon);
fillFieldsData(planetModel.getMaximumMagnitude(), point.x, point.y, point.z);
}
/**
* Creates a new Geo3DPointField field with the specified x,y,z.
*
* @throws IllegalArgumentException if the field name is null or lat or lon are out of bounds
*/
public Geo3DPointField(String name, PlanetModel planetModel, double x, double y, double z) {
super(name, TYPE);
fillFieldsData(planetModel.getMaximumMagnitude(), x, y, z);
}
private void fillFieldsData(double planetMax, double x, double y, double z) {
byte[] bytes = new byte[12];
Geo3DDocValuesFormat.writeInt(Geo3DDocValuesFormat.encodeValue(planetMax, x), bytes, 0);
Geo3DDocValuesFormat.writeInt(Geo3DDocValuesFormat.encodeValue(planetMax, y), bytes, 4);
Geo3DDocValuesFormat.writeInt(Geo3DDocValuesFormat.encodeValue(planetMax, z), bytes, 8);
fieldsData = new BytesRef(bytes);
}
}

View File

@ -0,0 +1,92 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.RamUsageEstimator;
final class GrowingHeapWriter implements Writer {
int[] xs;
int[] ys;
int[] zs;
int[] docIDs;
long[] ords;
private int nextWrite;
final int maxSize;
public GrowingHeapWriter(int maxSize) {
xs = new int[16];
ys = new int[16];
zs = new int[16];
docIDs = new int[16];
ords = new long[16];
this.maxSize = maxSize;
}
private int[] growExact(int[] arr, int size) {
assert size > arr.length;
int[] newArr = new int[size];
System.arraycopy(arr, 0, newArr, 0, arr.length);
return newArr;
}
private long[] growExact(long[] arr, int size) {
assert size > arr.length;
long[] newArr = new long[size];
System.arraycopy(arr, 0, newArr, 0, arr.length);
return newArr;
}
@Override
public void append(int x, int y, int z, long ord, int docID) {
assert ord == nextWrite;
if (xs.length == nextWrite) {
int nextSize = Math.min(maxSize, ArrayUtil.oversize(nextWrite+1, RamUsageEstimator.NUM_BYTES_INT));
assert nextSize > nextWrite: "nextSize=" + nextSize + " vs nextWrite=" + nextWrite;
xs = growExact(xs, nextSize);
ys = growExact(ys, nextSize);
zs = growExact(zs, nextSize);
ords = growExact(ords, nextSize);
docIDs = growExact(docIDs, nextSize);
}
xs[nextWrite] = x;
ys[nextWrite] = y;
zs[nextWrite] = z;
ords[nextWrite] = ord;
docIDs[nextWrite] = docID;
nextWrite++;
}
@Override
public Reader getReader(long start) {
return new HeapReader(xs, ys, zs, ords, docIDs, (int) start, nextWrite);
}
@Override
public void close() {
}
@Override
public void destroy() {
}
@Override
public String toString() {
return "GrowingHeapWriter(count=" + nextWrite + " alloc=" + xs.length + ")";
}
}

View File

@ -0,0 +1,73 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.
*/
final class HeapReader implements Reader {
private int curRead;
final int[] xs;
final int[] ys;
final int[] zs;
final long[] ords;
final int[] docIDs;
final int end;
HeapReader(int[] xs, int[] ys, int[] zs, long[] ords, int[] docIDs, int start, int end) {
this.xs = xs;
this.ys = ys;
this.zs = zs;
this.ords = ords;
this.docIDs = docIDs;
curRead = start-1;
this.end = end;
}
@Override
public boolean next() {
curRead++;
return curRead < end;
}
@Override
public int x() {
return xs[curRead];
}
@Override
public int y() {
return ys[curRead];
}
@Override
public int z() {
return zs[curRead];
}
@Override
public int docID() {
return docIDs[curRead];
}
@Override
public long ord() {
return ords[curRead];
}
@Override
public void close() {
}
}

View File

@ -0,0 +1,69 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.
*/
final class HeapWriter implements Writer {
final int[] xs;
final int[] ys;
final int[] zs;
final int[] docIDs;
final long[] ords;
private int nextWrite;
private boolean closed;
public HeapWriter(int count) {
xs = new int[count];
ys = new int[count];
zs = new int[count];
docIDs = new int[count];
ords = new long[count];
}
@Override
public void append(int x, int y, int z, long ord, int docID) {
xs[nextWrite] = x;
ys[nextWrite] = y;
zs[nextWrite] = z;
ords[nextWrite] = ord;
docIDs[nextWrite] = docID;
nextWrite++;
}
@Override
public Reader getReader(long start) {
assert closed;
return new HeapReader(xs, ys, zs, ords, docIDs, (int) start, xs.length);
}
@Override
public void close() {
closed = true;
if (nextWrite != xs.length) {
throw new IllegalStateException("only wrote " + nextWrite + " values, but expected " + xs.length);
}
}
@Override
public void destroy() {
}
@Override
public String toString() {
return "HeapWriter(count=" + xs.length + ")";
}
}

View File

@ -0,0 +1,95 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.lucene.store.InputStreamDataInput;
final class OfflineReader implements Reader {
final InputStreamDataInput in;
long countLeft;
private int x;
private int y;
private int z;
private long ord;
private int docID;
OfflineReader(Path tempFile, long start, long count) throws IOException {
InputStream fis = Files.newInputStream(tempFile);
long seekFP = start * BKD3DTreeWriter.BYTES_PER_DOC;
long skipped = 0;
while (skipped < seekFP) {
long inc = fis.skip(seekFP - skipped);
skipped += inc;
if (inc == 0) {
throw new RuntimeException("skip returned 0");
}
}
in = new InputStreamDataInput(new BufferedInputStream(fis));
this.countLeft = count;
}
@Override
public boolean next() throws IOException {
if (countLeft == 0) {
return false;
}
countLeft--;
x = in.readInt();
y = in.readInt();
z = in.readInt();
ord = in.readLong();
docID = in.readInt();
return true;
}
@Override
public int x() {
return x;
}
@Override
public int y() {
return y;
}
@Override
public int z() {
return z;
}
@Override
public long ord() {
return ord;
}
@Override
public int docID() {
return docID;
}
@Override
public void close() throws IOException {
in.close();
}
}

View File

@ -0,0 +1,79 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.BufferedOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.lucene.store.ByteArrayDataOutput;
import org.apache.lucene.store.OutputStreamDataOutput;
import org.apache.lucene.util.IOUtils;
final class OfflineWriter implements Writer {
final Path tempFile;
final byte[] scratchBytes = new byte[BKD3DTreeWriter.BYTES_PER_DOC];
final ByteArrayDataOutput scratchBytesOutput = new ByteArrayDataOutput(scratchBytes);
final OutputStreamDataOutput out;
final long count;
private long countWritten;
private boolean closed;
public OfflineWriter(Path tempDir, long count) throws IOException {
tempFile = Files.createTempFile(tempDir, "size" + count + ".", "");
out = new OutputStreamDataOutput(new BufferedOutputStream(Files.newOutputStream(tempFile)));
this.count = count;
}
@Override
public void append(int x, int y, int z, long ord, int docID) throws IOException {
out.writeInt(x);
out.writeInt(y);
out.writeInt(z);
out.writeLong(ord);
out.writeInt(docID);
countWritten++;
}
@Override
public Reader getReader(long start) throws IOException {
assert closed;
return new OfflineReader(tempFile, start, count-start);
}
@Override
public void close() throws IOException {
closed = true;
out.close();
if (count != countWritten) {
throw new IllegalStateException("wrote " + countWritten + " values, but expected " + count);
}
}
@Override
public void destroy() throws IOException {
IOUtils.rm(tempFile);
}
@Override
public String toString() {
return "OfflineWriter(count=" + count + " tempFile=" + tempFile + ")";
}
}

View File

@ -0,0 +1,224 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.apache.lucene.geo3d.GeoArea;
import org.apache.lucene.geo3d.GeoAreaFactory;
import org.apache.lucene.geo3d.GeoShape;
import org.apache.lucene.geo3d.PlanetModel;
import org.apache.lucene.geo3d.XYZBounds;
import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.ToStringUtils;
import java.io.IOException;
/** Finds all previously indexed points that fall within the specified polygon.
*
* <p>The field must be indexed with {@link Geo3DDocValuesFormat}, and {@link Geo3DPointField} added per document.
*
* <p>Because this implementation cannot intersect each cell with the polygon, it will be costly especially for large polygons, as every
* possible point must be checked.
*
* <p><b>NOTE</b>: for fastest performance, this allocates FixedBitSet(maxDoc) for each segment. The score of each hit is the query boost.
*
* @lucene.experimental */
public class PointInGeo3DShapeQuery extends Query {
final String field;
final PlanetModel planetModel;
final GeoShape shape;
/** The lats/lons must be clockwise or counter-clockwise. */
public PointInGeo3DShapeQuery(PlanetModel planetModel, String field, GeoShape shape) {
this.field = field;
this.planetModel = planetModel;
this.shape = shape;
}
@Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
// I don't use RandomAccessWeight here: it's no good to approximate with "match all docs"; this is an inverted structure and should be
// used in the first pass:
return new ConstantScoreWeight(this) {
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
LeafReader reader = context.reader();
BinaryDocValues bdv = reader.getBinaryDocValues(field);
if (bdv == null) {
// No docs in this segment had this field
return null;
}
if (bdv instanceof Geo3DBinaryDocValues == false) {
throw new IllegalStateException("field \"" + field + "\" was not indexed with Geo3DBinaryDocValuesFormat: got: " + bdv);
}
final Geo3DBinaryDocValues treeDV = (Geo3DBinaryDocValues) bdv;
BKD3DTreeReader tree = treeDV.getBKD3DTreeReader();
XYZBounds bounds = new XYZBounds();
shape.getBounds(bounds);
final double planetMax = planetModel.getMaximumMagnitude();
if (planetMax != treeDV.planetMax) {
throw new IllegalStateException(planetModel + " is not the same one used during indexing: planetMax=" + planetMax + " vs indexing planetMax=" + treeDV.planetMax);
}
/*
GeoArea xyzSolid = GeoAreaFactory.makeGeoArea(planetModel,
bounds.getMinimumX(),
bounds.getMaximumX(),
bounds.getMinimumY(),
bounds.getMaximumY(),
bounds.getMinimumZ(),
bounds.getMaximumZ());
assert xyzSolid.getRelationship(shape) == GeoArea.WITHIN || xyzSolid.getRelationship(shape) == GeoArea.OVERLAPS: "expected WITHIN (1) or OVERLAPS (2) but got " + xyzSolid.getRelationship(shape) + "; shape="+shape+"; XYZSolid="+xyzSolid;
*/
DocIdSet result = tree.intersect(Geo3DDocValuesFormat.encodeValueLenient(planetMax, bounds.getMinimumX()),
Geo3DDocValuesFormat.encodeValueLenient(planetMax, bounds.getMaximumX()),
Geo3DDocValuesFormat.encodeValueLenient(planetMax, bounds.getMinimumY()),
Geo3DDocValuesFormat.encodeValueLenient(planetMax, bounds.getMaximumY()),
Geo3DDocValuesFormat.encodeValueLenient(planetMax, bounds.getMinimumZ()),
Geo3DDocValuesFormat.encodeValueLenient(planetMax, bounds.getMaximumZ()),
new BKD3DTreeReader.ValueFilter() {
@Override
public boolean accept(int docID) {
//System.out.println(" accept? docID=" + docID);
BytesRef bytes = treeDV.get(docID);
if (bytes == null) {
//System.out.println(" false (null)");
return false;
}
assert bytes.length == 12;
double x = Geo3DDocValuesFormat.decodeValueCenter(treeDV.planetMax, Geo3DDocValuesFormat.readInt(bytes.bytes, bytes.offset));
double y = Geo3DDocValuesFormat.decodeValueCenter(treeDV.planetMax, Geo3DDocValuesFormat.readInt(bytes.bytes, bytes.offset+4));
double z = Geo3DDocValuesFormat.decodeValueCenter(treeDV.planetMax, Geo3DDocValuesFormat.readInt(bytes.bytes, bytes.offset+8));
// System.out.println(" accept docID=" + docID + " point: x=" + x + " y=" + y + " z=" + z);
// True if x,y,z is within shape
//System.out.println(" x=" + x + " y=" + y + " z=" + z);
//System.out.println(" ret: " + shape.isWithin(x, y, z));
return shape.isWithin(x, y, z);
}
@Override
public BKD3DTreeReader.Relation compare(int cellXMinEnc, int cellXMaxEnc, int cellYMinEnc, int cellYMaxEnc, int cellZMinEnc, int cellZMaxEnc) {
assert cellXMinEnc <= cellXMaxEnc;
assert cellYMinEnc <= cellYMaxEnc;
assert cellZMinEnc <= cellZMaxEnc;
// Because the BKD tree operates in quantized (64 bit -> 32 bit) space, and the cell bounds
// here are inclusive, we need to extend the bounds to the largest un-quantized values that
// could quantize into these bounds. The encoding (Geo3DDocValuesFormat.encodeValue) does
// a Math.round from double to long, so e.g. 1.4 -> 1, and -1.4 -> -1:
double cellXMin = Geo3DDocValuesFormat.decodeValueMin(treeDV.planetMax, cellXMinEnc);
double cellXMax = Geo3DDocValuesFormat.decodeValueMax(treeDV.planetMax, cellXMaxEnc);
double cellYMin = Geo3DDocValuesFormat.decodeValueMin(treeDV.planetMax, cellYMinEnc);
double cellYMax = Geo3DDocValuesFormat.decodeValueMax(treeDV.planetMax, cellYMaxEnc);
double cellZMin = Geo3DDocValuesFormat.decodeValueMin(treeDV.planetMax, cellZMinEnc);
double cellZMax = Geo3DDocValuesFormat.decodeValueMax(treeDV.planetMax, cellZMaxEnc);
//System.out.println(" compare: x=" + cellXMin + "-" + cellXMax + " y=" + cellYMin + "-" + cellYMax + " z=" + cellZMin + "-" + cellZMax);
GeoArea xyzSolid = GeoAreaFactory.makeGeoArea(planetModel, cellXMin, cellXMax, cellYMin, cellYMax, cellZMin, cellZMax);
switch(xyzSolid.getRelationship(shape)) {
case GeoArea.CONTAINS:
// Shape fully contains the cell
//System.out.println(" inside");
return BKD3DTreeReader.Relation.CELL_INSIDE_SHAPE;
case GeoArea.OVERLAPS:
// They do overlap but neither contains the other:
//System.out.println(" crosses1");
return BKD3DTreeReader.Relation.SHAPE_CROSSES_CELL;
case GeoArea.WITHIN:
// Cell fully contains the shape:
//System.out.println(" crosses2");
return BKD3DTreeReader.Relation.SHAPE_INSIDE_CELL;
case GeoArea.DISJOINT:
// They do not overlap at all
//System.out.println(" outside");
return BKD3DTreeReader.Relation.SHAPE_OUTSIDE_CELL;
default:
assert false;
return BKD3DTreeReader.Relation.SHAPE_CROSSES_CELL;
}
}
});
final DocIdSetIterator disi = result.iterator();
return new ConstantScoreScorer(this, score(), disi);
}
};
}
@Override
@SuppressWarnings({"unchecked","rawtypes"})
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
PointInGeo3DShapeQuery that = (PointInGeo3DShapeQuery) o;
return planetModel.equals(that.planetModel) && shape.equals(that.shape);
}
@Override
public final int hashCode() {
int result = super.hashCode();
result = 31 * result + planetModel.hashCode();
result = 31 * result + shape.hashCode();
return result;
}
@Override
public String toString(String field) {
final StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(':');
if (this.field.equals(field) == false) {
sb.append(" field=");
sb.append(this.field);
sb.append(':');
}
sb.append("PlanetModel: ");
sb.append(planetModel);
sb.append(" Shape: ");
sb.append(shape);
sb.append(ToStringUtils.boost(getBoost()));
return sb.toString();
}
}

View File

@ -0,0 +1,31 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.Closeable;
import java.io.IOException;
/** Abstracts away whether OfflineSorter or simple arrays in heap are used. */
interface Reader extends Closeable {
boolean next() throws IOException;
int x();
int y();
int z();
long ord();
int docID();
}

View File

@ -0,0 +1,29 @@
package org.apache.lucene.bkdtree3d;
/*
* 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.Closeable;
import java.io.IOException;
/** Abstracts away whether OfflineSorter or simple arrays in heap are used. */
interface Writer extends Closeable {
void append(int x, int y, int z, long ord, int docID) throws IOException;
Reader getReader(long start) throws IOException;
void destroy() throws IOException;
}

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.
*/
/**
* Fast "indexed point inside geo3d shape" query implementation.
*/
package org.apache.lucene.bkdtree3d;

View File

@ -0,0 +1,168 @@
package org.apache.lucene.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 class of a family of 3D rectangles, bounded on six sides by X,Y,Z limits
*
* @lucene.internal
*/
public abstract class BaseXYZSolid extends BasePlanetObject implements GeoArea {
/** Unit vector in x */
protected static final Vector xUnitVector = new Vector(1.0, 0.0, 0.0);
/** Unit vector in y */
protected static final Vector yUnitVector = new Vector(0.0, 1.0, 0.0);
/** Unit vector in z */
protected static final Vector zUnitVector = new Vector(0.0, 0.0, 1.0);
/** Vertical plane normal to x unit vector passing through origin */
protected static final Plane xVerticalPlane = new Plane(0.0, 1.0, 0.0, 0.0);
/** Vertical plane normal to y unit vector passing through origin */
protected static final Plane yVerticalPlane = new Plane(1.0, 0.0, 0.0, 0.0);
/** Empty point vector */
protected static final GeoPoint[] EMPTY_POINTS = new GeoPoint[0];
/**
* Base solid constructor.
*@param planetModel is the planet model.
*/
public BaseXYZSolid(final PlanetModel planetModel) {
super(planetModel);
}
/** Construct a single array from a number of individual arrays.
* @param pointArrays is the array of point arrays.
* @return the single unified array.
*/
protected static GeoPoint[] glueTogether(final GeoPoint[]... pointArrays) {
int count = 0;
for (final GeoPoint[] pointArray : pointArrays) {
count += pointArray.length;
}
final GeoPoint[] rval = new GeoPoint[count];
count = 0;
for (final GeoPoint[] pointArray : pointArrays) {
for (final GeoPoint point : pointArray) {
rval[count++] = point;
}
}
return rval;
}
@Override
public boolean isWithin(final Vector point) {
return isWithin(point.x, point.y, point.z);
}
@Override
public abstract boolean isWithin(final double x, final double y, final double z);
// Signals for relationship of edge points to shape
/** All edgepoints inside shape */
protected final static int ALL_INSIDE = 0;
/** Some edgepoints inside shape */
protected final static int SOME_INSIDE = 1;
/** No edgepoints inside shape */
protected final static int NONE_INSIDE = 2;
/** No edgepoints at all (means a shape that is the whole world) */
protected final static int NO_EDGEPOINTS = 3;
/** Determine the relationship between this area and the provided
* shape's edgepoints.
*@param path is the shape.
*@return the relationship.
*/
protected int isShapeInsideArea(final GeoShape path) {
final GeoPoint[] pathPoints = path.getEdgePoints();
if (pathPoints.length == 0)
return NO_EDGEPOINTS;
boolean foundOutside = false;
boolean foundInside = false;
for (final GeoPoint p : pathPoints) {
if (isWithin(p)) {
foundInside = true;
} else {
foundOutside = true;
}
if (foundInside && foundOutside) {
return SOME_INSIDE;
}
}
if (!foundInside && !foundOutside)
return NONE_INSIDE;
if (foundInside && !foundOutside)
return ALL_INSIDE;
if (foundOutside && !foundInside)
return NONE_INSIDE;
return SOME_INSIDE;
}
/** Determine the relationship between a shape and this area's
* edgepoints.
*@param path is the shape.
*@return the relationship.
*/
protected int isAreaInsideShape(final GeoShape path) {
final GeoPoint[] edgePoints = getEdgePoints();
if (edgePoints.length == 0) {
return NO_EDGEPOINTS;
}
boolean foundOutside = false;
boolean foundInside = false;
for (final GeoPoint p : edgePoints) {
if (path.isWithin(p)) {
foundInside = true;
} else {
foundOutside = true;
}
if (foundInside && foundOutside) {
return SOME_INSIDE;
}
}
if (!foundInside && !foundOutside)
return NONE_INSIDE;
if (foundInside && !foundOutside)
return ALL_INSIDE;
if (foundOutside && !foundInside)
return NONE_INSIDE;
return SOME_INSIDE;
}
/** Get the edge points for this shape.
*@return the edge points.
*/
protected abstract GeoPoint[] getEdgePoints();
@Override
public boolean equals(Object o) {
if (!(o instanceof BaseXYZSolid))
return false;
BaseXYZSolid other = (BaseXYZSolid) o;
return super.equals(other);
}
@Override
public int hashCode() {
return super.hashCode();
}
}

View File

@ -18,7 +18,7 @@ package org.apache.lucene.geo3d;
*/
/**
* An object for accumulating bounds information.
* An interface 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:
@ -30,356 +30,85 @@ package org.apache.lucene.geo3d;
*
* @lucene.experimental
*/
public class Bounds {
public interface Bounds {
/** Set to true if no longitude bounds can be stated */
protected boolean noLongitudeBound = false;
/** Set to true if no top latitude bound can be stated */
protected boolean noTopLatitudeBound = false;
/** Set to true if no bottom latitude bound can be stated */
protected boolean noBottomLatitudeBound = false;
/** If non-null, the minimum latitude bound */
protected Double minLatitude = null;
/** If non-null, the maximum latitude bound */
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".)
/** If non-null, the left longitude bound */
protected Double leftLongitude = null;
/** If non-null, the right longitude bound */
protected Double rightLongitude = null;
/** Construct an empty bounds object */
public Bounds() {
}
/** Get maximum latitude, if any.
*@return maximum latitude or null.
/** Add a general plane to the bounds description.
*@param planetModel is the planet model.
*@param plane is the plane.
*@param bounds are the membership bounds for points along the arc.
*/
public Double getMaxLatitude() {
return maxLatitude;
}
/** Get minimum latitude, if any.
*@return minimum latitude or null.
*/
public Double getMinLatitude() {
return minLatitude;
}
/** Get left longitude, if any.
*@return left longitude, or null.
*/
public Double getLeftLongitude() {
return leftLongitude;
}
/** Get right longitude, if any.
*@return right longitude, or null.
*/
public Double getRightLongitude() {
return rightLongitude;
}
/** Check if there's no longitude bound.
*@return true if no longitude bound.
*/
public boolean checkNoLongitudeBound() {
return noLongitudeBound;
}
/** Check if there's no top latitude bound.
*@return true if no top latitude bound.
*/
public boolean checkNoTopLatitudeBound() {
return noTopLatitudeBound;
}
/** Check if there's no bottom latitude bound.
*@return true if no bottom latitude bound.
*/
public boolean checkNoBottomLatitudeBound() {
return noBottomLatitudeBound;
}
/** Add a constraint representing a horizontal circle with a
* specified z value.
*@param z is the z value.
*@return the updated Bounds object.
*/
public Bounds addHorizontalCircle(double z) {
if (!noTopLatitudeBound || !noBottomLatitudeBound) {
// Compute a latitude value
double latitude = Math.asin(z);
addLatitudeBound(latitude);
}
return this;
}
/** Add a constraint representing a horizontal circle at
* a specific latitude.
public Bounds addPlane(final PlanetModel planetModel, final Plane plane, final Membership... bounds);
/** Add a horizontal plane to the bounds description.
* This method should EITHER use the supplied latitude, OR use the supplied
* plane, depending on what is most efficient.
*@param planetModel is the planet model.
*@param latitude is the latitude.
*@return the updated Bounds object.
*@param horizontalPlane is the plane.
*@param bounds are the constraints on the plane.
*@return updated Bounds object.
*/
public Bounds addLatitudeZone(double latitude) {
if (!noTopLatitudeBound || !noBottomLatitudeBound) {
addLatitudeBound(latitude);
}
return this;
}
/** Add a constraint representing a longitude slice.
*@param newLeftLongitude is the left longitude value.
*@param newRightLongitude is the right longitude value.
*@return the updated Bounds object.
public Bounds addHorizontalPlane(final PlanetModel planetModel,
final double latitude,
final Plane horizontalPlane,
final Membership... bounds);
/** Add a vertical plane to the bounds description.
* This method should EITHER use the supplied longitude, OR use the supplied
* plane, depending on what is most efficient.
*@param planetModel is the planet model.
*@param longitude is the longitude.
*@param verticalPlane is the plane.
*@param bounds are the constraints on the plane.
*@return updated Bounds object.
*/
public Bounds addLongitudeSlice(double newLeftLongitude, double newRightLongitude) {
if (!noLongitudeBound) {
addLongitudeBound(newLeftLongitude, newRightLongitude);
}
return this;
}
/** Update latitude bound.
*@param latitude is the latitude.
*/
protected void addLatitudeBound(double latitude) {
if (!noTopLatitudeBound && (maxLatitude == null || latitude > maxLatitude))
maxLatitude = latitude;
if (!noBottomLatitudeBound && (minLatitude == null || latitude < minLatitude))
minLatitude = latitude;
}
/** Update longitude bound.
*@param newLeftLongitude is the left longitude.
*@param newRightLongitude is the right longitude.
*/
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;
}
}
/** Update longitude bound.
*@param longitude is the new longitude value.
*/
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 addVerticalPlane(final PlanetModel planetModel,
final double longitude,
final Plane verticalPlane,
final Membership... bounds);
/** Add a single point.
*@param v is the point vector.
*@param point is the point.
*@return the updated Bounds object.
*/
public Bounds addPoint(final Vector v) {
return addPoint(v.x, v.y, v.z);
}
public Bounds addPoint(final GeoPoint point);
/** Add a single point.
*@param x is the point x.
*@param y is the point y.
*@param z is the point z.
/** Add an X value.
*@param point is the point to take the x value from.
*@return the updated object.
*/
public Bounds addXValue(final GeoPoint point);
/** Add a Y value.
*@param point is the point to take the y value from.
*@return the updated object.
*/
public Bounds addYValue(final GeoPoint point);
/** Add a Z value.
*@param point is the point to take the z value from.
*@return the updated object.
*/
public Bounds addZValue(final GeoPoint point);
/** Signal that the shape exceeds Math.PI in longitude.
*@return the updated Bounds object.
*/
public Bounds addPoint(final double x, final double y, final 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/Math.sqrt(z * z + x * x + y * y));
addLatitudeBound(latitude);
}
return this;
}
/** Add a single point.
*@param latitude is the point's latitude.
*@param longitude is the point's longitude.
*@return the updated Bounds object.
*/
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 isWide();
/** Signal that there is no longitude bound.
*@return the updated Bounds object.
*/
public Bounds noLongitudeBound() {
noLongitudeBound = true;
leftLongitude = null;
rightLongitude = null;
return this;
}
public Bounds noLongitudeBound();
/** Signal that there is no top latitude bound.
*@return the updated Bounds object.
*/
public Bounds noTopLatitudeBound() {
noTopLatitudeBound = true;
maxLatitude = null;
return this;
}
public Bounds noTopLatitudeBound();
/** Signal that there is no bottom latitude bound.
*@return the updated Bounds object.
*/
public Bounds noBottomLatitudeBound() {
noBottomLatitudeBound = true;
minLatitude = null;
return this;
}
public Bounds noBottomLatitudeBound();
}

View File

@ -33,13 +33,13 @@ public interface GeoArea extends Membership {
// Relationship values for "getRelationship()"
/** The referenced shape CONTAINS this shape */
/** The referenced shape CONTAINS this area */
public static final int CONTAINS = 0;
/** The referenced shape IS WITHIN this shape */
/** The referenced shape IS WITHIN this area */
public static final int WITHIN = 1;
/** The referenced shape OVERLAPS this shape */
/** The referenced shape OVERLAPS this area */
public static final int OVERLAPS = 2;
/** The referenced shape has no relation to this shape */
/** The referenced shape has no relation to this area */
public static final int DISJOINT = 3;
/**
@ -48,10 +48,17 @@ public interface GeoArea extends Membership {
* 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.
*
* It is permissible to return OVERLAPS instead of WITHIN if the shape
* intersects with the area at even a single point. So, a circle inscribed in
* a rectangle could return either OVERLAPS or WITHIN, depending on
* implementation. It is not permissible to return CONTAINS or DISJOINT
* in this circumstance, however.
*
* Similarly, it is permissible to return OVERLAPS instead of CONTAINS
* under conditions where the shape consists of multiple independent overlapping
* subshapes, and the area overlaps one of the subshapes. It is not permissible
* to return WITHIN or DISJOINT in this circumstance, however.
*
* @param shape is the shape to consider.
* @return the relationship, from the perspective of the shape.

View File

@ -28,7 +28,7 @@ public class GeoAreaFactory {
/**
* Create a GeoArea of the right kind given the specified bounds.
*
* @param planetModel is the planet model
* @param topLat is the top latitude
* @param bottomLat is the bottom latitude
* @param leftLon is the left longitude
@ -39,4 +39,43 @@ public class GeoAreaFactory {
return GeoBBoxFactory.makeGeoBBox(planetModel, topLat, bottomLat, leftLon, rightLon);
}
/**
* Create a GeoArea of the right kind given (x,y,z) bounds.
* @param planetModel is the planet model
* @param minX is the min X boundary
* @param maxX is the max X boundary
* @param minY is the min Y boundary
* @param maxY is the max Y boundary
* @param minZ is the min Z boundary
* @param maxZ is the max Z boundary
*/
public static GeoArea makeGeoArea(final PlanetModel planetModel, final double minX, final double maxX, final double minY, final double maxY, final double minZ, final double maxZ) {
if (Math.abs(maxX - minX) < Vector.MINIMUM_RESOLUTION) {
if (Math.abs(maxY - minY) < Vector.MINIMUM_RESOLUTION) {
if (Math.abs(maxZ - minZ) < Vector.MINIMUM_RESOLUTION) {
return new dXdYdZSolid(planetModel, (minX+maxX) * 0.5, (minY+maxY) * 0.5, minZ);
} else {
return new dXdYZSolid(planetModel, (minX+maxX) * 0.5, (minY+maxY) * 0.5, minZ, maxZ);
}
} else {
if (Math.abs(maxZ - minZ) < Vector.MINIMUM_RESOLUTION) {
return new dXYdZSolid(planetModel, (minX+maxX) * 0.5, minY, maxY, (minZ+maxZ) * 0.5);
} else {
return new dXYZSolid(planetModel, (minX+maxX) * 0.5, minY, maxY, minZ, maxZ);
}
}
}
if (Math.abs(maxY - minY) < Vector.MINIMUM_RESOLUTION) {
if (Math.abs(maxZ - minZ) < Vector.MINIMUM_RESOLUTION) {
return new XdYdZSolid(planetModel, minX, maxX, (minY+maxY) * 0.5, (minZ+maxZ) * 0.5);
} else {
return new XdYZSolid(planetModel, minX, maxX, (minY+maxY) * 0.5, minZ, maxZ);
}
}
if (Math.abs(maxZ - minZ) < Vector.MINIMUM_RESOLUTION) {
return new XYdZSolid(planetModel, minX, maxX, minY, maxY, (minZ+maxZ) * 0.5);
}
return new XYZSolid(planetModel, minX, maxX, minY, maxY, minZ, maxZ);
}
}

View File

@ -32,16 +32,27 @@ public abstract class GeoBaseShape extends BasePlanetObject implements GeoShape
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
public void getBounds(Bounds bounds) {
if (isWithin(planetModel.NORTH_POLE)) {
bounds.noTopLatitudeBound().noLongitudeBound();
bounds.noTopLatitudeBound().noLongitudeBound()
.addPoint(planetModel.NORTH_POLE);
}
if (isWithin(planetModel.SOUTH_POLE)) {
bounds.noBottomLatitudeBound().noLongitudeBound();
bounds.noBottomLatitudeBound().noLongitudeBound()
.addPoint(planetModel.SOUTH_POLE);
}
if (isWithin(planetModel.MIN_X_POLE)) {
bounds.addPoint(planetModel.MIN_X_POLE);
}
if (isWithin(planetModel.MAX_X_POLE)) {
bounds.addPoint(planetModel.MAX_X_POLE);
}
if (isWithin(planetModel.MIN_Y_POLE)) {
bounds.addPoint(planetModel.MIN_Y_POLE);
}
if (isWithin(planetModel.MAX_Y_POLE)) {
bounds.addPoint(planetModel.MAX_Y_POLE);
}
return bounds;
}
}

View File

@ -46,8 +46,10 @@ public class GeoCircle extends GeoBaseDistanceShape implements GeoSizeable {
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)
if (cutoffAngle < 0.0 || cutoffAngle > Math.PI)
throw new IllegalArgumentException("Cutoff angle out of bounds");
if (cutoffAngle < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("Cutoff angle cannot be effectively zero");
this.center = new GeoPoint(planetModel, lat, lon);
// In an ellipsoidal world, cutoff distances make no sense, unfortunately. Only membership
// can be used to make in/out determination.
@ -78,12 +80,15 @@ public class GeoCircle extends GeoBaseDistanceShape implements GeoSizeable {
this.edgePoints = new GeoPoint[0];
} else {
// Construct normal plane
final Plane normalPlane = Plane.constructNormalizedVerticalPlane(upperPoint, lowerPoint, center);
final Plane normalPlane = Plane.constructNormalizedZPlane(upperPoint, lowerPoint, center);
// Construct a sided plane that goes through the two points and whose normal is in the normalPlane.
this.circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(center, normalPlane, upperPoint, lowerPoint);
if (circlePlane == null)
throw new RuntimeException("Couldn't construct circle plane. Cutoff angle = "+cutoffAngle+"; upperPoint = "+upperPoint+"; lowerPoint = "+lowerPoint);
this.edgePoints = new GeoPoint[]{upperPoint};
throw new IllegalArgumentException("Couldn't construct circle plane, probably too small? Cutoff angle = "+cutoffAngle+"; upperPoint = "+upperPoint+"; lowerPoint = "+lowerPoint);
final GeoPoint recomputedIntersectionPoint = circlePlane.getSampleIntersectionPoint(planetModel, normalPlane);
if (recomputedIntersectionPoint == null)
throw new IllegalArgumentException("Couldn't construct intersection point, probably circle too small? Plane = "+circlePlane);
this.edgePoints = new GeoPoint[]{recomputedIntersectionPoint};
}
}
@ -130,16 +135,14 @@ public class GeoCircle extends GeoBaseDistanceShape implements GeoSizeable {
}
@Override
public Bounds getBounds(Bounds bounds) {
bounds = super.getBounds(bounds);
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
if (circlePlane == null) {
// Entire world
bounds.noTopLatitudeBound().noBottomLatitudeBound().noLongitudeBound();
return bounds;
// Entire world; should already be covered
return;
}
bounds.addPoint(center);
circlePlane.recordBounds(planetModel, bounds);
return bounds;
bounds.addPlane(planetModel, circlePlane);
}
@Override

View File

@ -71,13 +71,10 @@ public class GeoCompositeMembershipShape implements GeoMembershipShape {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
public void getBounds(Bounds bounds) {
for (GeoMembershipShape shape : shapes) {
bounds = shape.getBounds(bounds);
shape.getBounds(bounds);
}
return bounds;
}
@Override

View File

@ -213,8 +213,8 @@ public class GeoConvexPolygon extends GeoBaseMembershipShape {
}
@Override
public Bounds getBounds(Bounds bounds) {
bounds = super.getBounds(bounds);
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
// Add all the points
for (final GeoPoint point : points) {
@ -232,14 +232,8 @@ public class GeoConvexPolygon extends GeoBaseMembershipShape {
membershipBounds[count++] = edges[otherIndex];
}
}
edge.recordBounds(planetModel, bounds, membershipBounds);
bounds.addPlane(planetModel, edge, 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

View File

@ -87,8 +87,8 @@ public class GeoDegenerateHorizontalLine extends GeoBaseBBox {
final double cosRightLon = Math.cos(rightLon);
// Now build the two points
this.LHC = new GeoPoint(planetModel, sinLatitude, sinLeftLon, cosLatitude, cosLeftLon);
this.RHC = new GeoPoint(planetModel, sinLatitude, sinRightLon, cosLatitude, cosRightLon);
this.LHC = new GeoPoint(planetModel, sinLatitude, sinLeftLon, cosLatitude, cosLeftLon, latitude, leftLon);
this.RHC = new GeoPoint(planetModel, sinLatitude, sinRightLon, cosLatitude, cosRightLon, latitude, rightLon);
this.plane = new Plane(planetModel, sinLatitude);
@ -156,11 +156,10 @@ public class GeoDegenerateHorizontalLine extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.addLatitudeZone(latitude).addLongitudeSlice(leftLon, rightLon);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds.addHorizontalPlane(planetModel, latitude, plane, leftPlane, rightPlane)
.addPoint(LHC).addPoint(RHC);
}
@Override

View File

@ -86,21 +86,11 @@ public class GeoDegenerateLatitudeZone extends GeoBaseBBox {
return p.intersects(planetModel, 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;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds.noLongitudeBound()
.addHorizontalPlane(planetModel, latitude, plane);
}
@Override

View File

@ -98,12 +98,11 @@ public class GeoDegenerateLongitudeSlice extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.noTopLatitudeBound().noBottomLatitudeBound();
bounds.addLongitudeSlice(longitude, longitude);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds
.addVerticalPlane(planetModel, longitude, plane, boundingPlane)
.addPoint(planetModel.NORTH_POLE).addPoint(planetModel.SOUTH_POLE);
}
@Override

View File

@ -74,11 +74,8 @@ public class GeoDegeneratePoint extends GeoPoint implements GeoBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.addPoint(latitude, longitude);
return bounds;
public void getBounds(Bounds bounds) {
bounds.addPoint(this);
}
@Override

View File

@ -77,8 +77,8 @@ public class GeoDegenerateVerticalLine extends GeoBaseBBox {
final double cosLongitude = Math.cos(longitude);
// Now build the two points
this.UHC = new GeoPoint(planetModel, sinTopLat, sinLongitude, cosTopLat, cosLongitude);
this.LHC = new GeoPoint(planetModel, sinBottomLat, sinLongitude, cosBottomLat, cosLongitude);
this.UHC = new GeoPoint(planetModel, sinTopLat, sinLongitude, cosTopLat, cosLongitude, topLat, longitude);
this.LHC = new GeoPoint(planetModel, sinBottomLat, sinLongitude, cosBottomLat, cosLongitude, bottomLat, longitude);
this.plane = new Plane(cosLongitude, sinLongitude);
@ -146,12 +146,10 @@ public class GeoDegenerateVerticalLine extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.addLatitudeZone(topLat).addLatitudeZone(bottomLat)
.addLongitudeSlice(longitude, longitude);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds.addVerticalPlane(planetModel, longitude, plane, boundingPlane, topPlane, bottomPlane)
.addPoint(UHC).addPoint(LHC);
}
@Override

View File

@ -121,11 +121,11 @@ public class GeoLatitudeZone extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.noLongitudeBound().addLatitudeZone(topLat).addLatitudeZone(bottomLat);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds.noLongitudeBound()
.addHorizontalPlane(planetModel, topLat, topPlane)
.addHorizontalPlane(planetModel, bottomLat, bottomPlane);
}
@Override

View File

@ -129,12 +129,13 @@ public class GeoLongitudeSlice extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.noTopLatitudeBound().noBottomLatitudeBound();
bounds.addLongitudeSlice(leftLon, rightLon);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds
.addVerticalPlane(planetModel, leftLon, leftPlane, rightPlane)
.addVerticalPlane(planetModel, rightLon, rightPlane, leftPlane)
.addPoint(planetModel.NORTH_POLE)
.addPoint(planetModel.SOUTH_POLE);
}
@Override

View File

@ -100,11 +100,10 @@ public class GeoNorthLatitudeZone extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.noLongitudeBound().noTopLatitudeBound().addLatitudeZone(bottomLat);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds
.addHorizontalPlane(planetModel, bottomLat, bottomPlane);
}
@Override

View File

@ -90,8 +90,8 @@ public class GeoNorthRectangle extends GeoBaseBBox {
final double cosRightLon = Math.cos(rightLon);
// Now build the points
this.LRHC = new GeoPoint(planetModel, sinBottomLat, sinRightLon, cosBottomLat, cosRightLon);
this.LLHC = new GeoPoint(planetModel, sinBottomLat, sinLeftLon, cosBottomLat, cosLeftLon);
this.LRHC = new GeoPoint(planetModel, sinBottomLat, sinRightLon, cosBottomLat, cosRightLon, bottomLat, rightLon);
this.LLHC = new GeoPoint(planetModel, sinBottomLat, sinLeftLon, cosBottomLat, cosLeftLon, bottomLat, leftLon);
final double middleLat = (Math.PI * 0.5 + bottomLat) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
@ -176,12 +176,13 @@ public class GeoNorthRectangle extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.noTopLatitudeBound().addLatitudeZone(bottomLat)
.addLongitudeSlice(leftLon, rightLon);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds
.addHorizontalPlane(planetModel, bottomLat, bottomPlane, leftPlane, rightPlane)
.addVerticalPlane(planetModel, leftLon, leftPlane, bottomPlane, rightPlane)
.addVerticalPlane(planetModel, rightLon, rightPlane, bottomPlane, leftPlane)
.addPoint(LLHC).addPoint(LRHC).addPoint(planetModel.NORTH_POLE);
}
@Override

View File

@ -140,10 +140,14 @@ public class GeoPath extends GeoBaseDistanceShape {
}
final GeoPoint upperPoint = new GeoPoint(planetModel, upperLat, upperLon);
final GeoPoint lowerPoint = new GeoPoint(planetModel, lowerLat, lowerLon);
final GeoPoint point = points.get(0);
// Construct normal plane
final Plane normalPlane = Plane.constructNormalizedZPlane(upperPoint, lowerPoint, point);
final SegmentEndpoint onlyEndpoint = new SegmentEndpoint(points.get(0), upperPoint, lowerPoint);
final SegmentEndpoint onlyEndpoint = new SegmentEndpoint(point, normalPlane, upperPoint, lowerPoint);
endPoints.add(onlyEndpoint);
this.edgePoints = new GeoPoint[]{upperPoint};
this.edgePoints = new GeoPoint[]{onlyEndpoint.circlePlane.getSampleIntersectionPoint(planetModel, normalPlane)};
return;
}
@ -278,8 +282,8 @@ public class GeoPath extends GeoBaseDistanceShape {
}
@Override
public Bounds getBounds(Bounds bounds) {
bounds = super.getBounds(bounds);
public void getBounds(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.
@ -289,7 +293,6 @@ public class GeoPath extends GeoBaseDistanceShape {
for (SegmentEndpoint pathPoint : endPoints) {
pathPoint.getBounds(planetModel, bounds);
}
return bounds;
}
@Override
@ -339,7 +342,9 @@ public class GeoPath extends GeoBaseDistanceShape {
public final GeoPoint[] notablePoints;
/** No notable points from the circle itself */
public final static GeoPoint[] circlePoints = new GeoPoint[0];
/** Null membership */
public final static Membership[] NO_MEMBERSHIP = new Membership[0];
/** Base case. Does nothing at all.
*/
public SegmentEndpoint(final GeoPoint point) {
@ -355,14 +360,12 @@ public class GeoPath extends GeoBaseDistanceShape {
*@param upperPoint is a point that must be on the circle plane.
*@param lowerPoint is another point that must be on the circle plane.
*/
public SegmentEndpoint(final GeoPoint point, final GeoPoint upperPoint, final GeoPoint lowerPoint) {
public SegmentEndpoint(final GeoPoint point, final Plane normalPlane, final GeoPoint upperPoint, final GeoPoint lowerPoint) {
this.point = point;
// Construct normal plane
final Plane normalPlane = Plane.constructNormalizedVerticalPlane(upperPoint, lowerPoint, point);
// Construct a sided plane that goes through the two points and whose normal is in the normalPlane.
this.circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(point, normalPlane, upperPoint, lowerPoint);
this.cutoffPlanes = new Membership[0];
this.notablePoints = new GeoPoint[0];
this.cutoffPlanes = NO_MEMBERSHIP;
this.notablePoints = circlePoints;
}
/** Constructor for case (2).
@ -533,7 +536,7 @@ public class GeoPath extends GeoBaseDistanceShape {
bounds.addPoint(point);
if (circlePlane == null)
return;
circlePlane.recordBounds(planetModel, bounds);
bounds.addPlane(planetModel, circlePlane);
}
@Override
@ -769,15 +772,11 @@ public class GeoPath extends GeoBaseDistanceShape {
*/
public void getBounds(final PlanetModel planetModel, Bounds bounds) {
// We need to do all bounding planes as well as corner points
bounds.addPoint(start).addPoint(end);
upperConnectingPlane.recordBounds(planetModel, startCutoffPlane, bounds, lowerConnectingPlane, endCutoffPlane);
startCutoffPlane.recordBounds(planetModel, lowerConnectingPlane, bounds, endCutoffPlane, upperConnectingPlane);
lowerConnectingPlane.recordBounds(planetModel, endCutoffPlane, bounds, upperConnectingPlane, startCutoffPlane);
endCutoffPlane.recordBounds(planetModel, upperConnectingPlane, bounds, startCutoffPlane, lowerConnectingPlane);
upperConnectingPlane.recordBounds(planetModel, bounds, lowerConnectingPlane, startCutoffPlane, endCutoffPlane);
lowerConnectingPlane.recordBounds(planetModel, bounds, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
startCutoffPlane.recordBounds(planetModel, bounds, endCutoffPlane, upperConnectingPlane, lowerConnectingPlane);
endCutoffPlane.recordBounds(planetModel, bounds, startCutoffPlane, upperConnectingPlane, lowerConnectingPlane);
bounds.addPoint(start).addPoint(end).addPoint(ULHC).addPoint(URHC).addPoint(LRHC).addPoint(LLHC);
bounds.addPlane(planetModel, upperConnectingPlane, lowerConnectingPlane, startCutoffPlane, endCutoffPlane);
bounds.addPlane(planetModel, lowerConnectingPlane, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
bounds.addPlane(planetModel, startCutoffPlane, endCutoffPlane, upperConnectingPlane, lowerConnectingPlane);
bounds.addPlane(planetModel, endCutoffPlane, startCutoffPlane, upperConnectingPlane, lowerConnectingPlane);
}
}

View File

@ -85,10 +85,12 @@ public class GeoPoint extends Vector {
public GeoPoint(final double magnitude, final double x, final double y, final double z, double lat, double lon) {
super(x * magnitude, y * magnitude, z * magnitude);
this.magnitude = magnitude;
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");
if (lat > Math.PI * 0.5 || lat < -Math.PI * 0.5) {
throw new IllegalArgumentException("Latitude " + lat + " is out of range: must range from -Math.PI/2 to Math.PI/2");
}
if (lon < -Math.PI || lon > Math.PI) {
throw new IllegalArgumentException("Longitude " + lon + " is out of range: must range from -Math.PI to Math.PI");
}
this.latitude = lat;
this.longitude = lon;
}
@ -169,4 +171,24 @@ public class GeoPoint extends Vector {
}
return mag;
}
/** Compute whether point matches another.
*@param x is the x value
*@param y is the y value
*@param z is the z value
*@return true if the same.
*/
public boolean isIdentical(final double x, final double y, final double z) {
return Math.abs(this.x - x) < MINIMUM_RESOLUTION &&
Math.abs(this.y - y) < MINIMUM_RESOLUTION &&
Math.abs(this.z - z) < MINIMUM_RESOLUTION;
}
@Override
public String toString() {
if (this.longitude == Double.NEGATIVE_INFINITY) {
return super.toString();
}
return "[lat="+getLatitude()+", lon="+getLongitude()+"]";
}
}

View File

@ -112,10 +112,10 @@ public class GeoRectangle extends GeoBaseBBox {
final double cosRightLon = Math.cos(rightLon);
// Now build the four points
this.ULHC = new GeoPoint(planetModel, sinTopLat, sinLeftLon, cosTopLat, cosLeftLon);
this.URHC = new GeoPoint(planetModel, sinTopLat, sinRightLon, cosTopLat, cosRightLon);
this.LRHC = new GeoPoint(planetModel, sinBottomLat, sinRightLon, cosBottomLat, cosRightLon);
this.LLHC = new GeoPoint(planetModel, sinBottomLat, sinLeftLon, cosBottomLat, cosLeftLon);
this.ULHC = new GeoPoint(planetModel, sinTopLat, sinLeftLon, cosTopLat, cosLeftLon, topLat, leftLon);
this.URHC = new GeoPoint(planetModel, sinTopLat, sinRightLon, cosTopLat, cosRightLon, topLat, rightLon);
this.LRHC = new GeoPoint(planetModel, sinBottomLat, sinRightLon, cosBottomLat, cosRightLon, bottomLat, rightLon);
this.LLHC = new GeoPoint(planetModel, sinBottomLat, sinLeftLon, cosBottomLat, cosLeftLon, bottomLat, leftLon);
final double middleLat = (topLat + bottomLat) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
@ -198,12 +198,13 @@ public class GeoRectangle extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.addLatitudeZone(topLat).addLatitudeZone(bottomLat)
.addLongitudeSlice(leftLon, rightLon);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds.addHorizontalPlane(planetModel, topLat, topPlane, bottomPlane, leftPlane, rightPlane)
.addVerticalPlane(planetModel, rightLon, rightPlane, topPlane, bottomPlane, leftPlane)
.addHorizontalPlane(planetModel, bottomLat, bottomPlane, topPlane, leftPlane, rightPlane)
.addVerticalPlane(planetModel, leftLon, leftPlane, topPlane, bottomPlane, rightPlane)
.addPoint(ULHC).addPoint(URHC).addPoint(LLHC).addPoint(LRHC);
}
@Override

View File

@ -54,14 +54,11 @@ public interface GeoShape extends Membership {
public boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds);
/**
* Compute longitude/latitude bounds for the shape.
* Compute 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.
* @param bounds is the input bounds object.
* The input object will be modified.
*/
public Bounds getBounds(final Bounds bounds);
public void getBounds(final Bounds bounds);
}

View File

@ -102,21 +102,11 @@ public class GeoSouthLatitudeZone extends GeoBaseBBox {
return p.intersects(planetModel, 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;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds
.addHorizontalPlane(planetModel, topLat, topPlane);
}
@Override

View File

@ -94,8 +94,8 @@ public class GeoSouthRectangle extends GeoBaseBBox {
final double cosRightLon = Math.cos(rightLon);
// Now build the four points
this.ULHC = new GeoPoint(planetModel, sinTopLat, sinLeftLon, cosTopLat, cosLeftLon);
this.URHC = new GeoPoint(planetModel, sinTopLat, sinRightLon, cosTopLat, cosRightLon);
this.ULHC = new GeoPoint(planetModel, sinTopLat, sinLeftLon, cosTopLat, cosLeftLon, topLat, leftLon);
this.URHC = new GeoPoint(planetModel, sinTopLat, sinRightLon, cosTopLat, cosRightLon, topLat, rightLon);
final double middleLat = (topLat - Math.PI * 0.5) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
@ -174,12 +174,13 @@ public class GeoSouthRectangle extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.addLatitudeZone(topLat).noBottomLatitudeBound()
.addLongitudeSlice(leftLon, rightLon);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds
.addHorizontalPlane(planetModel, topLat, topPlane, leftPlane, rightPlane)
.addVerticalPlane(planetModel, leftLon, leftPlane, topPlane, rightPlane)
.addVerticalPlane(planetModel, rightLon, rightPlane, topPlane, leftPlane)
.addPoint(URHC).addPoint(ULHC).addPoint(planetModel.SOUTH_POLE);
}
@Override

View File

@ -90,8 +90,8 @@ public class GeoWideDegenerateHorizontalLine extends GeoBaseBBox {
final double cosRightLon = Math.cos(rightLon);
// Now build the two points
this.LHC = new GeoPoint(planetModel, sinLatitude, sinLeftLon, cosLatitude, cosLeftLon);
this.RHC = new GeoPoint(planetModel, sinLatitude, sinRightLon, cosLatitude, cosRightLon);
this.LHC = new GeoPoint(planetModel, sinLatitude, sinLeftLon, cosLatitude, cosLeftLon, latitude, leftLon);
this.RHC = new GeoPoint(planetModel, sinLatitude, sinRightLon, cosLatitude, cosRightLon, latitude, rightLon);
this.plane = new Plane(planetModel, sinLatitude);
@ -167,12 +167,12 @@ public class GeoWideDegenerateHorizontalLine extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.addLatitudeZone(latitude)
.addLongitudeSlice(leftLon, rightLon);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds.isWide()
.addHorizontalPlane(planetModel, latitude, plane, eitherBound)
.addPoint(LHC)
.addPoint(RHC);
}
@Override

View File

@ -135,12 +135,13 @@ public class GeoWideLongitudeSlice extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.noTopLatitudeBound().noBottomLatitudeBound();
bounds.addLongitudeSlice(leftLon, rightLon);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds.isWide()
.addVerticalPlane(planetModel, leftLon, leftPlane)
.addVerticalPlane(planetModel, rightLon, rightPlane)
.addPoint(planetModel.NORTH_POLE)
.addPoint(planetModel.SOUTH_POLE);
}
@Override

View File

@ -94,8 +94,8 @@ public class GeoWideNorthRectangle extends GeoBaseBBox {
final double cosRightLon = Math.cos(rightLon);
// Now build the four points
this.LRHC = new GeoPoint(planetModel, sinBottomLat, sinRightLon, cosBottomLat, cosRightLon);
this.LLHC = new GeoPoint(planetModel, sinBottomLat, sinLeftLon, cosBottomLat, cosLeftLon);
this.LRHC = new GeoPoint(planetModel, sinBottomLat, sinRightLon, cosBottomLat, cosRightLon, bottomLat, rightLon);
this.LLHC = new GeoPoint(planetModel, sinBottomLat, sinLeftLon, cosBottomLat, cosLeftLon, bottomLat, leftLon);
final double middleLat = (Math.PI * 0.5 + bottomLat) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
@ -178,12 +178,13 @@ public class GeoWideNorthRectangle extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.noTopLatitudeBound().addLatitudeZone(bottomLat)
.addLongitudeSlice(leftLon, rightLon);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds.isWide()
.addHorizontalPlane(planetModel, bottomLat, bottomPlane, eitherBound)
.addVerticalPlane(planetModel, leftLon, leftPlane, bottomPlane)
.addVerticalPlane(planetModel, rightLon, rightPlane, bottomPlane)
.addPoint(LLHC).addPoint(LRHC).addPoint(planetModel.NORTH_POLE);
}
@Override

View File

@ -111,10 +111,10 @@ public class GeoWideRectangle extends GeoBaseBBox {
final double cosRightLon = Math.cos(rightLon);
// Now build the four points
this.ULHC = new GeoPoint(planetModel, sinTopLat, sinLeftLon, cosTopLat, cosLeftLon);
this.URHC = new GeoPoint(planetModel, sinTopLat, sinRightLon, cosTopLat, cosRightLon);
this.LRHC = new GeoPoint(planetModel, sinBottomLat, sinRightLon, cosBottomLat, cosRightLon);
this.LLHC = new GeoPoint(planetModel, sinBottomLat, sinLeftLon, cosBottomLat, cosLeftLon);
this.ULHC = new GeoPoint(planetModel, sinTopLat, sinLeftLon, cosTopLat, cosLeftLon, topLat, leftLon);
this.URHC = new GeoPoint(planetModel, sinTopLat, sinRightLon, cosTopLat, cosRightLon, topLat, rightLon);
this.LRHC = new GeoPoint(planetModel, sinBottomLat, sinRightLon, cosBottomLat, cosRightLon, bottomLat, rightLon);
this.LLHC = new GeoPoint(planetModel, sinBottomLat, sinLeftLon, cosBottomLat, cosLeftLon, bottomLat, leftLon);
final double middleLat = (topLat + bottomLat) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
@ -206,12 +206,14 @@ public class GeoWideRectangle extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.addLatitudeZone(topLat).addLatitudeZone(bottomLat)
.addLongitudeSlice(leftLon, rightLon);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds.isWide()
.addHorizontalPlane(planetModel, topLat, topPlane, bottomPlane, eitherBound)
.addVerticalPlane(planetModel, rightLon, rightPlane, topPlane, bottomPlane)
.addHorizontalPlane(planetModel, bottomLat, bottomPlane, topPlane, eitherBound)
.addVerticalPlane(planetModel, leftLon, leftPlane, topPlane, bottomPlane)
.addPoint(ULHC).addPoint(URHC).addPoint(LRHC).addPoint(LLHC);
}
@Override

View File

@ -94,8 +94,8 @@ public class GeoWideSouthRectangle extends GeoBaseBBox {
final double cosRightLon = Math.cos(rightLon);
// Now build the four points
this.ULHC = new GeoPoint(planetModel, sinTopLat, sinLeftLon, cosTopLat, cosLeftLon);
this.URHC = new GeoPoint(planetModel, sinTopLat, sinRightLon, cosTopLat, cosRightLon);
this.ULHC = new GeoPoint(planetModel, sinTopLat, sinLeftLon, cosTopLat, cosLeftLon, topLat, leftLon);
this.URHC = new GeoPoint(planetModel, sinTopLat, sinRightLon, cosTopLat, cosRightLon, topLat, rightLon);
final double middleLat = (topLat - Math.PI * 0.5) * 0.5;
final double sinMiddleLat = Math.sin(middleLat);
@ -177,12 +177,13 @@ public class GeoWideSouthRectangle extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.addLatitudeZone(topLat).noBottomLatitudeBound()
.addLongitudeSlice(leftLon, rightLon);
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
bounds.isWide()
.addHorizontalPlane(planetModel, topLat, topPlane, eitherBound)
.addVerticalPlane(planetModel, rightLon, rightPlane, topPlane)
.addVerticalPlane(planetModel, leftLon, leftPlane, topPlane)
.addPoint(ULHC).addPoint(URHC).addPoint(planetModel.SOUTH_POLE);
}
@Override

View File

@ -68,11 +68,10 @@ public class GeoWorld extends GeoBaseBBox {
}
@Override
public Bounds getBounds(Bounds bounds) {
if (bounds == null)
bounds = new Bounds();
bounds.noLongitudeBound().noTopLatitudeBound().noBottomLatitudeBound();
return bounds;
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
// Unnecessary
//bounds.noLongitudeBound().noTopLatitudeBound().noBottomLatitudeBound();
}
@Override

View File

@ -0,0 +1,323 @@
package org.apache.lucene.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 latitude/longitude bounds information.
*
* @lucene.experimental
*/
public class LatLonBounds implements Bounds {
/** Set to true if no longitude bounds can be stated */
protected boolean noLongitudeBound = false;
/** Set to true if no top latitude bound can be stated */
protected boolean noTopLatitudeBound = false;
/** Set to true if no bottom latitude bound can be stated */
protected boolean noBottomLatitudeBound = false;
/** If non-null, the minimum latitude bound */
protected Double minLatitude = null;
/** If non-null, the maximum latitude bound */
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".)
/** If non-null, the left longitude bound */
protected Double leftLongitude = null;
/** If non-null, the right longitude bound */
protected Double rightLongitude = null;
/** Construct an empty bounds object */
public LatLonBounds() {
}
// Accessor methods
/** Get maximum latitude, if any.
*@return maximum latitude or null.
*/
public Double getMaxLatitude() {
return maxLatitude;
}
/** Get minimum latitude, if any.
*@return minimum latitude or null.
*/
public Double getMinLatitude() {
return minLatitude;
}
/** Get left longitude, if any.
*@return left longitude, or null.
*/
public Double getLeftLongitude() {
return leftLongitude;
}
/** Get right longitude, if any.
*@return right longitude, or null.
*/
public Double getRightLongitude() {
return rightLongitude;
}
// Degenerate case check
/** Check if there's no longitude bound.
*@return true if no longitude bound.
*/
public boolean checkNoLongitudeBound() {
return noLongitudeBound;
}
/** Check if there's no top latitude bound.
*@return true if no top latitude bound.
*/
public boolean checkNoTopLatitudeBound() {
return noTopLatitudeBound;
}
/** Check if there's no bottom latitude bound.
*@return true if no bottom latitude bound.
*/
public boolean checkNoBottomLatitudeBound() {
return noBottomLatitudeBound;
}
// Modification methods
@Override
public Bounds addPlane(final PlanetModel planetModel, final Plane plane, final Membership... bounds) {
plane.recordBounds(planetModel, this, bounds);
return this;
}
@Override
public Bounds addHorizontalPlane(final PlanetModel planetModel,
final double latitude,
final Plane horizontalPlane,
final Membership... bounds) {
if (!noTopLatitudeBound || !noBottomLatitudeBound) {
addLatitudeBound(latitude);
}
return this;
}
@Override
public Bounds addVerticalPlane(final PlanetModel planetModel,
final double longitude,
final Plane verticalPlane,
final Membership... bounds) {
if (!noLongitudeBound) {
addLongitudeBound(longitude);
}
return this;
}
@Override
public Bounds isWide() {
return noLongitudeBound();
}
@Override
public Bounds addXValue(final GeoPoint point) {
if (!noLongitudeBound) {
// Get a longitude value
addLongitudeBound(point.getLongitude());
}
return this;
}
@Override
public Bounds addYValue(final GeoPoint point) {
if (!noLongitudeBound) {
// Get a longitude value
addLongitudeBound(point.getLongitude());
}
return this;
}
@Override
public Bounds addZValue(final GeoPoint point) {
if (!noTopLatitudeBound || !noBottomLatitudeBound) {
// Compute a latitude value
double latitude = point.getLatitude();
addLatitudeBound(latitude);
}
return this;
}
@Override
public Bounds addPoint(GeoPoint point) {
if (!noLongitudeBound) {
// Get a longitude value
addLongitudeBound(point.getLongitude());
}
if (!noTopLatitudeBound || !noBottomLatitudeBound) {
// Compute a latitude value
addLatitudeBound(point.getLatitude());
}
return this;
}
@Override
public Bounds noLongitudeBound() {
noLongitudeBound = true;
leftLongitude = null;
rightLongitude = null;
return this;
}
@Override
public Bounds noTopLatitudeBound() {
noTopLatitudeBound = true;
maxLatitude = null;
return this;
}
@Override
public Bounds noBottomLatitudeBound() {
noBottomLatitudeBound = true;
minLatitude = null;
return this;
}
// Protected methods
/** Update latitude bound.
*@param latitude is the latitude.
*/
protected void addLatitudeBound(double latitude) {
if (!noTopLatitudeBound && (maxLatitude == null || latitude > maxLatitude))
maxLatitude = latitude;
if (!noBottomLatitudeBound && (minLatitude == null || latitude < minLatitude))
minLatitude = latitude;
}
/** Update longitude bound.
*@param longitude is the new longitude value.
*/
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;
}
}
}

View File

@ -28,6 +28,13 @@ public class Plane extends Vector {
protected final static GeoPoint[] NO_POINTS = new GeoPoint[0];
/** An array with no bounds in it */
protected final static Membership[] NO_BOUNDS = new Membership[0];
/** A vertical plane normal to the Y axis */
protected final static Plane normalYPlane = new Plane(0.0,1.0,0.0,0.0);
/** A vertical plane normal to the X axis */
protected final static Plane normalXPlane = new Plane(1.0,0.0,0.0,0.0);
/** A vertical plane normal to the Z axis */
protected final static Plane normalZPlane = new Plane(0.0,0.0,1.0,0.0);
/** Ax + By + Cz + D = 0 */
public final double D;
@ -88,12 +95,12 @@ public class Plane extends Vector {
this.D = D;
}
/** Construct the most accurate normalized, vertical plane given a set of points. If none of the points can determine
* the plane, return null.
* @param planePoints is a set of points to choose from. The best one for constructing the most precise normal plane is picked.
* @return the normal plane
/** Construct the most accurate normalized plane through an x-y point and including the Z axis.
* If none of the points can determine the plane, return null.
* @param planePoints is a set of points to choose from. The best one for constructing the most precise plane is picked.
* @return the plane
*/
public static Plane constructNormalizedVerticalPlane(final Vector... planePoints) {
public static Plane constructNormalizedZPlane(final Vector... planePoints) {
// Pick the best one (with the greatest x-y distance)
double bestDistance = 0.0;
Vector bestPoint = null;
@ -104,19 +111,86 @@ public class Plane extends Vector {
bestPoint = point;
}
}
return constructNormalizedVerticalPlane(bestPoint.x, bestPoint.y);
return constructNormalizedZPlane(bestPoint.x, bestPoint.y);
}
/** Construct a normalized, vertical plane through an x-y point. If the x-y point is at (0,0), return null.
/** Construct the most accurate normalized plane through an x-z point and including the Y axis.
* If none of the points can determine the plane, return null.
* @param planePoints is a set of points to choose from. The best one for constructing the most precise plane is picked.
* @return the plane
*/
public static Plane constructNormalizedYPlane(final Vector... planePoints) {
// Pick the best one (with the greatest x-z distance)
double bestDistance = 0.0;
Vector bestPoint = null;
for (final Vector point : planePoints) {
final double pointDist = point.x * point.x + point.z * point.z;
if (pointDist > bestDistance) {
bestDistance = pointDist;
bestPoint = point;
}
}
return constructNormalizedYPlane(bestPoint.x, bestPoint.z, 0.0);
}
/** Construct the most accurate normalized plane through an y-z point and including the X axis.
* If none of the points can determine the plane, return null.
* @param planePoints is a set of points to choose from. The best one for constructing the most precise plane is picked.
* @return the plane
*/
public static Plane constructNormalizedXPlane(final Vector... planePoints) {
// Pick the best one (with the greatest y-z distance)
double bestDistance = 0.0;
Vector bestPoint = null;
for (final Vector point : planePoints) {
final double pointDist = point.y * point.y + point.z * point.z;
if (pointDist > bestDistance) {
bestDistance = pointDist;
bestPoint = point;
}
}
return constructNormalizedXPlane(bestPoint.y, bestPoint.z, 0.0);
}
/** Construct a normalized plane through an x-y point and including the Z axis.
* If the x-y point is at (0,0), return null.
* @param x is the x value.
* @param y is the y value.
* @return a vertical plane passing through the center and (x,y,0).
* @return a plane passing through the Z axis and (x,y,0).
*/
public static Plane constructNormalizedVerticalPlane(final double x, final double y) {
public static Plane constructNormalizedZPlane(final double x, final double y) {
if (Math.abs(x) < MINIMUM_RESOLUTION && Math.abs(y) < MINIMUM_RESOLUTION)
return null;
final double denom = 1.0 / Math.sqrt(x*x + y*y);
return new Plane(x * denom, y * denom);
return new Plane(y * denom, -x * denom, 0.0, 0.0);
}
/** Construct a normalized plane through an x-z point and parallel to the Y axis.
* If the x-z point is at (0,0), return null.
* @param x is the x value.
* @param z is the z value.
* @param DValue is the offset from the origin for the plane.
* @return a plane parallel to the Y axis and perpendicular to the x and z values given.
*/
public static Plane constructNormalizedYPlane(final double x, final double z, final double DValue) {
if (Math.abs(x) < MINIMUM_RESOLUTION && Math.abs(z) < MINIMUM_RESOLUTION)
return null;
final double denom = 1.0 / Math.sqrt(x*x + z*z);
return new Plane(z * denom, 0.0, -x * denom, DValue);
}
/** Construct a normalized plane through a y-z point and parallel to the X axis.
* If the y-z point is at (0,0), return null.
* @param y is the y value.
* @param z is the z value.
* @param DValue is the offset from the origin for the plane.
* @return a plane parallel to the X axis and perpendicular to the y and z values given.
*/
public static Plane constructNormalizedXPlane(final double y, final double z, final double DValue) {
if (Math.abs(y) < MINIMUM_RESOLUTION && Math.abs(z) < MINIMUM_RESOLUTION)
return null;
final double denom = 1.0 / Math.sqrt(y*y + z*z);
return new Plane(0.0, z * denom, -y * denom, DValue);
}
/**
@ -695,35 +769,149 @@ public class Plane extends Vector {
throw new RuntimeException("Intersection point not on ellipsoid; point="+point);
}
*/
/**
* Accumulate bounds information for this plane, intersected with another plane
* and with the unit sphere.
* Updates both latitude and longitude information, using max/min points found
* Accumulate (x,y,z) bounds information for this plane, intersected with the unit sphere.
* Updates min/max information, using max/min points found
* within the specified bounds.
*
* @param planetModel is the planet model to use to determine bounding points
* @param q is the plane to intersect with.
* @param boundsInfo is the info to update with additional bounding information.
* @param planetModel is the planet model to use in determining bounds.
* @param boundsInfo is the xyz info to update with additional bounding information.
* @param bounds are the surfaces delineating what's inside the shape.
*/
public void recordBounds(final PlanetModel planetModel, final Plane q, final Bounds boundsInfo, final Membership... bounds) {
final GeoPoint[] intersectionPoints = findIntersections(planetModel, q, bounds, NO_BOUNDS);
for (GeoPoint intersectionPoint : intersectionPoints) {
boundsInfo.addPoint(intersectionPoint);
public void recordBounds(final PlanetModel planetModel, final XYZBounds boundsInfo, final Membership... bounds) {
// Basic plan is to do three intersections of the plane and the planet.
// For min/max x, we intersect a vertical plane such that y = 0.
// For min/max y, we intersect a vertical plane such that x = 0.
// For min/max z, we intersect a vertical plane that is chosen to go through the high point of the arc.
// For clarity, load local variables with good names
final double A = this.x;
final double B = this.y;
final double C = this.z;
// For the X and Y values, we need a D value, which is the AVERAGE D value
// for two planes that pass through the two Z points determined here, for the axis in question.
final GeoPoint[] zPoints;
if (!boundsInfo.isSmallestMinX(planetModel) || !boundsInfo.isLargestMaxX(planetModel) ||
!boundsInfo.isSmallestMinY(planetModel) || !boundsInfo.isLargestMaxY(planetModel)) {
if ((Math.abs(A) >= MINIMUM_RESOLUTION || Math.abs(B) >= MINIMUM_RESOLUTION)) {
// We need unconstrained values in order to compute D
zPoints = findIntersections(planetModel, constructNormalizedZPlane(A,B), NO_BOUNDS, NO_BOUNDS);
if (zPoints.length == 0) {
// No intersections, so plane never intersects world.
//System.err.println(" plane never intersects world");
return;
}
//for (final GeoPoint p : zPoints) {
// System.err.println("zPoint: "+p);
//}
} else {
zPoints = null;
}
} else {
zPoints = null;
}
// Do Z.
if (!boundsInfo.isSmallestMinZ(planetModel) || !boundsInfo.isLargestMaxZ(planetModel)) {
//System.err.println(" computing Z bound");
// Compute Z bounds for this arc
// With ellipsoids, we really have only one viable way to do this computation.
// Specifically, we compute an appropriate vertical plane, based on the current plane's x-y orientation, and
// then intersect it with this one and with the ellipsoid. This gives us zero, one, or two points to use
// as bounds.
// There is one special case: horizontal circles. These require TWO vertical planes: one for the x, and one for
// the y, and we use all four resulting points in the bounds computation.
if ((Math.abs(A) >= MINIMUM_RESOLUTION || Math.abs(B) >= MINIMUM_RESOLUTION)) {
// NOT a degenerate case
//System.err.println(" not degenerate");
final Plane normalizedZPlane = constructNormalizedZPlane(A,B);
final GeoPoint[] points = findIntersections(planetModel, normalizedZPlane, bounds, NO_BOUNDS);
for (final GeoPoint point : points) {
assert planetModel.pointOnSurface(point);
//System.err.println(" Point = "+point+"; this.evaluate(point)="+this.evaluate(point)+"; normalizedZPlane.evaluate(point)="+normalizedZPlane.evaluate(point));
addPoint(boundsInfo, bounds, point);
}
} else {
// Since a==b==0, any plane including the Z axis suffices.
//System.err.println(" Perpendicular to z");
final GeoPoint[] points = findIntersections(planetModel, normalYPlane, NO_BOUNDS, NO_BOUNDS);
boundsInfo.addZValue(points[0]);
}
}
if (!boundsInfo.isSmallestMinX(planetModel) || !boundsInfo.isLargestMaxX(planetModel)) {
//System.err.println(" computing X bound");
if ((Math.abs(B) >= MINIMUM_RESOLUTION || Math.abs(C) >= MINIMUM_RESOLUTION)) {
// NOT a degenerate case. Compute D.
//System.err.println(" not degenerate; B="+B+"; C="+C);
final Plane originPlane = constructNormalizedXPlane(B,C,0.0);
double DValue = 0.0;
if (zPoints != null) {
for (final GeoPoint p : zPoints) {
final double zValue = originPlane.evaluate(p);
//System.err.println(" originPlane.evaluate(zpoint)="+zValue);
DValue += zValue;
}
DValue /= (double)zPoints.length;
}
final Plane normalizedXPlane = constructNormalizedXPlane(B,C,-DValue);
final GeoPoint[] points = findIntersections(planetModel, normalizedXPlane, bounds, NO_BOUNDS);
for (final GeoPoint point : points) {
assert planetModel.pointOnSurface(point);
//System.err.println(" Point = "+point+"; this.evaluate(point)="+this.evaluate(point)+"; normalizedXPlane.evaluate(point)="+normalizedXPlane.evaluate(point));
addPoint(boundsInfo, bounds, point);
}
} else {
// Since b==c==0, any plane including the X axis suffices.
//System.err.println(" Perpendicular to x");
final GeoPoint[] points = findIntersections(planetModel, normalZPlane, NO_BOUNDS, NO_BOUNDS);
boundsInfo.addXValue(points[0]);
}
}
// Do Y
if (!boundsInfo.isSmallestMinY(planetModel) || !boundsInfo.isLargestMaxY(planetModel)) {
//System.err.println(" computing Y bound");
if ((Math.abs(A) >= MINIMUM_RESOLUTION || Math.abs(C) >= MINIMUM_RESOLUTION)) {
// NOT a degenerate case. Compute D.
//System.err.println(" not degenerate");
final Plane originPlane = constructNormalizedYPlane(A,C,0.0);
double DValue = 0.0;
if (zPoints != null) {
for (final GeoPoint p : zPoints) {
DValue += originPlane.evaluate(p);
}
DValue /= (double)zPoints.length;
}
final Plane normalizedYPlane = constructNormalizedYPlane(A,C,-DValue);
final GeoPoint[] points = findIntersections(planetModel, normalizedYPlane, bounds, NO_BOUNDS);
for (final GeoPoint point : points) {
assert planetModel.pointOnSurface(point);
//System.err.println(" Point = "+point+"; this.evaluate(point)="+this.evaluate(point)+"; normalizedYPlane.evaluate(point)="+normalizedYPlane.evaluate(point));
addPoint(boundsInfo, bounds, point);
}
} else {
// Since a==c==0, any plane including the Y axis suffices.
// It doesn't matter that we may discard the point due to bounds, because if there are bounds, we'll have endpoints
// that will be tallied separately.
//System.err.println(" Perpendicular to y");
final GeoPoint[] points = findIntersections(planetModel, normalXPlane, NO_BOUNDS, NO_BOUNDS);
boundsInfo.addYValue(points[0]);
}
}
}
/**
* Accumulate bounds information for this plane, intersected with the unit sphere.
* Updates both latitude and longitude information, using max/min points found
* within the specified bounds.
*
* @param planetModel is the planet model to use in determining bounds.
* @param boundsInfo is the info to update with additional bounding information.
* @param boundsInfo is the lat/lon info to update with additional bounding information.
* @param bounds are the surfaces delineating what's inside the shape.
*/
public void recordBounds(final PlanetModel planetModel, final Bounds boundsInfo, final Membership... bounds) {
public void recordBounds(final PlanetModel planetModel, final LatLonBounds boundsInfo, final Membership... bounds) {
// For clarity, load local variables with good names
final double A = this.x;
final double B = this.y;
@ -741,18 +929,15 @@ public class Plane extends Vector {
if ((Math.abs(A) >= MINIMUM_RESOLUTION || Math.abs(B) >= MINIMUM_RESOLUTION)) {
// NOT a horizontal circle!
//System.err.println(" Not a horizontal circle");
final Plane verticalPlane = constructNormalizedVerticalPlane(A,B);
final GeoPoint[] points = findIntersections(planetModel, verticalPlane, NO_BOUNDS, NO_BOUNDS);
final Plane verticalPlane = constructNormalizedZPlane(A,B);
final GeoPoint[] points = findIntersections(planetModel, verticalPlane, bounds, NO_BOUNDS);
for (final GeoPoint point : points) {
addPoint(boundsInfo, bounds, point.x, point.y, point.z);
addPoint(boundsInfo, bounds, point);
}
} else {
// Horizontal circle. Since a==b, one vertical plane suffices.
final Plane verticalPlane = new Plane(1.0,0.0);
final GeoPoint[] points = findIntersections(planetModel, verticalPlane, NO_BOUNDS, NO_BOUNDS);
// There will always be two points; we only need one.
final GeoPoint point = points[0];
boundsInfo.addHorizontalCircle(point.z/Math.sqrt(point.x * point.x + point.y * point.y + point.z * point.z));
// Horizontal circle. Since a==b, any vertical plane suffices.
final GeoPoint[] points = findIntersections(planetModel, normalXPlane, NO_BOUNDS, NO_BOUNDS);
boundsInfo.addZValue(points[0]);
}
//System.err.println("Done latitude bounds");
}
@ -808,7 +993,7 @@ public class Plane extends Vector {
double y0 = -b / (2.0 * a);
double x0 = (-D - B * y0) / A;
double z0 = 0.0;
addPoint(boundsInfo, bounds, x0, y0, z0);
addPoint(boundsInfo, bounds, new GeoPoint(x0, y0, z0));
} else if (sqrtClause > 0.0) {
double sqrtResult = Math.sqrt(sqrtClause);
double denom = 1.0 / (2.0 * a);
@ -823,8 +1008,8 @@ public class Plane extends Vector {
double z0a = 0.0;
double z0b = 0.0;
addPoint(boundsInfo, bounds, x0a, y0a, z0a);
addPoint(boundsInfo, bounds, x0b, y0b, z0b);
addPoint(boundsInfo, bounds, new GeoPoint(x0a, y0a, z0a));
addPoint(boundsInfo, bounds, new GeoPoint(x0b, y0b, z0b));
}
} else {
@ -841,7 +1026,7 @@ public class Plane extends Vector {
double x0 = -b / (2.0 * a);
double y0 = (-D - A * x0) / B;
double z0 = 0.0;
addPoint(boundsInfo, bounds, x0, y0, z0);
addPoint(boundsInfo, bounds, new GeoPoint(x0, y0, z0));
} else if (sqrtClause > 0.0) {
double sqrtResult = Math.sqrt(sqrtClause);
double denom = 1.0 / (2.0 * a);
@ -854,8 +1039,8 @@ public class Plane extends Vector {
double z0a = 0.0;
double z0b = 0.0;
addPoint(boundsInfo, bounds, x0a, y0a, z0a);
addPoint(boundsInfo, bounds, x0b, y0b, z0b);
addPoint(boundsInfo, bounds, new GeoPoint(x0a, y0a, z0a));
addPoint(boundsInfo, bounds, new GeoPoint(x0b, y0b, z0b));
}
}
}
@ -949,7 +1134,7 @@ public class Plane extends Vector {
double x0 = (-2.0 * J - I * y0) / H;
double z0 = (-A * x0 - B * y0 - D) / C;
addPoint(boundsInfo, bounds, x0, y0, z0);
addPoint(boundsInfo, bounds, new GeoPoint(x0, y0, z0));
} else if (sqrtClause > 0.0) {
//System.err.println(" Two solutions");
double sqrtResult = Math.sqrt(sqrtClause);
@ -964,8 +1149,8 @@ public class Plane extends Vector {
double z0a = (-A * x0a - B * y0a - D) * Cdenom;
double z0b = (-A * x0b - B * y0b - D) * Cdenom;
addPoint(boundsInfo, bounds, x0a, y0a, z0a);
addPoint(boundsInfo, bounds, x0b, y0b, z0b);
addPoint(boundsInfo, bounds, new GeoPoint(x0a, y0a, z0a));
addPoint(boundsInfo, bounds, new GeoPoint(x0b, y0b, z0b));
}
} else {
@ -1001,7 +1186,7 @@ public class Plane extends Vector {
double z0 = (-A * x0 - B * y0 - D) / C;
// Verify that x&y fulfill the equation
// 2Ex^2 + 2Fy^2 + 2Gxy + Hx + Iy = 0
addPoint(boundsInfo, bounds, x0, y0, z0);
addPoint(boundsInfo, bounds, new GeoPoint(x0, y0, z0));
} else if (sqrtClause > 0.0) {
//System.err.println(" Two solutions");
double sqrtResult = Math.sqrt(sqrtClause);
@ -1016,8 +1201,8 @@ public class Plane extends Vector {
double z0a = (-A * x0a - B * y0a - D) * Cdenom;
double z0b = (-A * x0b - B * y0b - D) * Cdenom;
addPoint(boundsInfo, bounds, x0a, y0a, z0a);
addPoint(boundsInfo, bounds, x0b, y0b, z0b);
addPoint(boundsInfo, bounds, new GeoPoint(x0a, y0a, z0a));
addPoint(boundsInfo, bounds, new GeoPoint(x0b, y0b, z0b));
}
}
}
@ -1026,6 +1211,21 @@ public class Plane extends Vector {
}
/** Add a point to boundsInfo if within a specifically bounded area.
* @param boundsInfo is the object to be modified.
* @param bounds is the area that the point must be within.
* @param point is the point.
*/
protected static void addPoint(final Bounds boundsInfo, final Membership[] bounds, final GeoPoint point) {
// Make sure the discovered point is within the bounds
for (Membership bound : bounds) {
if (!bound.isWithin(point))
return;
}
// Add the point
boundsInfo.addPoint(point);
}
/** Add a point to boundsInfo if within a specifically bounded area.
* @param boundsInfo is the object to be modified.
* @param bounds is the area that the point must be within.
@ -1033,6 +1233,7 @@ public class Plane extends Vector {
* @param y is the y value.
* @param z is the z value.
*/
/*
protected static void addPoint(final Bounds boundsInfo, final Membership[] bounds, final double x, final double y, final double z) {
//System.err.println(" Want to add point x="+x+" y="+y+" z="+z);
// Make sure the discovered point is within the bounds
@ -1045,6 +1246,7 @@ public class Plane extends Vector {
//System.out.println("Adding point x="+x+" y="+y+" z="+z);
boundsInfo.addPoint(x, y, z);
}
*/
/**
* Determine whether the plane intersects another plane within the

View File

@ -65,6 +65,14 @@ public class PlanetModel {
public final GeoPoint NORTH_POLE;
/** South pole */
public final GeoPoint SOUTH_POLE;
/** Min X pole */
public final GeoPoint MIN_X_POLE;
/** Max X pole */
public final GeoPoint MAX_X_POLE;
/** Min Y pole */
public final GeoPoint MIN_Y_POLE;
/** Max Y pole */
public final GeoPoint MAX_Y_POLE;
/** Constructor.
* @param ab is the x/y scaling factor.
@ -81,6 +89,10 @@ public class PlanetModel {
this.inverseCSquared = inverseC * inverseC;
this.NORTH_POLE = new GeoPoint(c, 0.0, 0.0, 1.0, Math.PI * 0.5, 0.0);
this.SOUTH_POLE = new GeoPoint(c, 0.0, 0.0, -1.0, -Math.PI * 0.5, 0.0);
this.MIN_X_POLE = new GeoPoint(ab, -1.0, 0.0, 0.0, 0.0, -Math.PI);
this.MAX_X_POLE = new GeoPoint(ab, 1.0, 0.0, 0.0, 0.0, 0.0);
this.MIN_Y_POLE = new GeoPoint(ab, 0.0, -1.0, 0.0, 0.0, -Math.PI * 0.5);
this.MAX_Y_POLE = new GeoPoint(ab, 0.0, 1.0, 0.0, 0.0, Math.PI * 0.5);
}
/** Find the minimum magnitude of all points on the ellipsoid.
@ -97,6 +109,86 @@ public class PlanetModel {
return Math.max(this.ab, this.c);
}
/** Find the minimum x value.
*@return the minimum X value.
*/
public double getMinimumXValue() {
return -this.ab;
}
/** Find the maximum x value.
*@return the maximum X value.
*/
public double getMaximumXValue() {
return this.ab;
}
/** Find the minimum y value.
*@return the minimum Y value.
*/
public double getMinimumYValue() {
return -this.ab;
}
/** Find the maximum y value.
*@return the maximum Y value.
*/
public double getMaximumYValue() {
return this.ab;
}
/** Find the minimum z value.
*@return the minimum Z value.
*/
public double getMinimumZValue() {
return -this.c;
}
/** Find the maximum z value.
*@return the maximum Z value.
*/
public double getMaximumZValue() {
return this.c;
}
/** Check if point is on surface.
* @param v is the point to check.
* @return true if the point is on the planet surface.
*/
public boolean pointOnSurface(final Vector v) {
return pointOnSurface(v.x, v.y, v.z);
}
/** Check if point is on surface.
* @param x is the x coord.
* @param y is the y coord.
* @param z is the z coord.
*/
public boolean pointOnSurface(final double x, final double y, final double z) {
// Equation of planet surface is:
// x^2 / a^2 + y^2 / b^2 + z^2 / c^2 - 1 = 0
return Math.abs((x * x + y * y) * inverseAb * inverseAb + z * z * inverseC * inverseC - 1.0) < Vector.MINIMUM_RESOLUTION;
}
/** Check if point is outside surface.
* @param v is the point to check.
* @return true if the point is outside the planet surface.
*/
public boolean pointOutside(final Vector v) {
return pointOutside(v.x, v.y, v.z);
}
/** Check if point is outside surface.
* @param x is the x coord.
* @param y is the y coord.
* @param z is the z coord.
*/
public boolean pointOutside(final double x, final double y, final double z) {
// Equation of planet surface is:
// x^2 / a^2 + y^2 / b^2 + z^2 / c^2 - 1 = 0
return (x * x + y * y) * inverseAb * inverseAb + z * z * inverseC * inverseC - 1.0 > Vector.MINIMUM_RESOLUTION;
}
/** Compute surface distance between two points.
* @param p1 is the first point.
* @param p2 is the second point.

View File

@ -48,6 +48,8 @@ public class SidedPlane extends Plane implements Membership {
public SidedPlane(Vector p, Vector A, Vector B) {
super(A, B);
sigNum = Math.signum(evaluate(p));
if (sigNum == 0.0)
throw new IllegalArgumentException("Cannot determine sidedness because check point is on plane.");
}
/**
@ -60,6 +62,8 @@ public class SidedPlane extends Plane implements Membership {
public SidedPlane(Vector p, final PlanetModel planetModel, double sinLat) {
super(planetModel, sinLat);
sigNum = Math.signum(evaluate(p));
if (sigNum == 0.0)
throw new IllegalArgumentException("Cannot determine sidedness because check point is on plane.");
}
/**
@ -72,6 +76,8 @@ public class SidedPlane extends Plane implements Membership {
public SidedPlane(Vector p, double x, double y) {
super(x, y);
sigNum = Math.signum(evaluate(p));
if (sigNum == 0.0)
throw new IllegalArgumentException("Cannot determine sidedness because check point is on plane.");
}
/**
@ -84,6 +90,24 @@ public class SidedPlane extends Plane implements Membership {
public SidedPlane(Vector p, Vector v, double D) {
super(v, D);
sigNum = Math.signum(evaluate(p));
if (sigNum == 0.0)
throw new IllegalArgumentException("Cannot determine sidedness because check point is on plane.");
}
/**
* Construct a sided plane with a normal vector and offset.
*
* @param pX X coord of point to evaluate.
* @param pY Y coord of point to evaluate.
* @param pZ Z coord of point to evaluate.
* @param v is the normal vector.
* @param D is the origin offset for the plan.
*/
public SidedPlane(double pX, double pY, double pZ, Vector v, double D) {
super(v, D);
sigNum = Math.signum(evaluate(pX,pY,pZ));
if (sigNum == 0.0)
throw new IllegalArgumentException("Cannot determine sidedness because check point is on plane.");
}
/** Construct a sided plane from two points and a third normal vector.

View File

@ -28,7 +28,7 @@ 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;
public static final double MINIMUM_RESOLUTION = 5.0e-13;
/**
* For squared quantities, the bound is squared too.
*/

View File

@ -0,0 +1,268 @@
package org.apache.lucene.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 XYZ bounds information.
*
* @lucene.experimental
*/
public class XYZBounds implements Bounds {
/** A 'fudge factor', which is added to maximums and subtracted from minimums,
* in order to compensate for potential error deltas. This would not be necessary
* except that our 'bounds' is defined as always equaling or exceeding the boundary
* of the shape, and we cannot guarantee that without making MINIMUM_RESOLUTION
* unacceptably large.
*/
protected static final double FUDGE_FACTOR = Vector.MINIMUM_RESOLUTION * 2e6;
/** Minimum x */
protected Double minX = null;
/** Maximum x */
protected Double maxX = null;
/** Minimum y */
protected Double minY = null;
/** Maximum y */
protected Double maxY = null;
/** Minimum z */
protected Double minZ = null;
/** Maximum z */
protected Double maxZ = null;
/** Set to true if no longitude bounds can be stated */
protected boolean noLongitudeBound = false;
/** Set to true if no top latitude bound can be stated */
protected boolean noTopLatitudeBound = false;
/** Set to true if no bottom latitude bound can be stated */
protected boolean noBottomLatitudeBound = false;
/** Construct an empty bounds object */
public XYZBounds() {
}
// Accessor methods
/** Return the minimum X value.
*@return minimum X value.
*/
public Double getMinimumX() {
return minX;
}
/** Return the maximum X value.
*@return maximum X value.
*/
public Double getMaximumX() {
return maxX;
}
/** Return the minimum Y value.
*@return minimum Y value.
*/
public Double getMinimumY() {
return minY;
}
/** Return the maximum Y value.
*@return maximum Y value.
*/
public Double getMaximumY() {
return maxY;
}
/** Return the minimum Z value.
*@return minimum Z value.
*/
public Double getMinimumZ() {
return minZ;
}
/** Return the maximum Z value.
*@return maximum Z value.
*/
public Double getMaximumZ() {
return maxZ;
}
/** Return true if minX is as small as the planet model allows.
*@return true if minX has reached its bound.
*/
public boolean isSmallestMinX(final PlanetModel planetModel) {
if (minX == null)
return false;
return minX - planetModel.getMinimumXValue() < Vector.MINIMUM_RESOLUTION;
}
/** Return true if maxX is as large as the planet model allows.
*@return true if maxX has reached its bound.
*/
public boolean isLargestMaxX(final PlanetModel planetModel) {
if (maxX == null)
return false;
return planetModel.getMaximumXValue() - maxX < Vector.MINIMUM_RESOLUTION;
}
/** Return true if minY is as small as the planet model allows.
*@return true if minY has reached its bound.
*/
public boolean isSmallestMinY(final PlanetModel planetModel) {
if (minY == null)
return false;
return minY - planetModel.getMinimumYValue() < Vector.MINIMUM_RESOLUTION;
}
/** Return true if maxY is as large as the planet model allows.
*@return true if maxY has reached its bound.
*/
public boolean isLargestMaxY(final PlanetModel planetModel) {
if (maxY == null)
return false;
return planetModel.getMaximumYValue() - maxY < Vector.MINIMUM_RESOLUTION;
}
/** Return true if minZ is as small as the planet model allows.
*@return true if minZ has reached its bound.
*/
public boolean isSmallestMinZ(final PlanetModel planetModel) {
if (minZ == null)
return false;
return minZ - planetModel.getMinimumZValue() < Vector.MINIMUM_RESOLUTION;
}
/** Return true if maxZ is as large as the planet model allows.
*@return true if maxZ has reached its bound.
*/
public boolean isLargestMaxZ(final PlanetModel planetModel) {
if (maxZ == null)
return false;
return planetModel.getMaximumZValue() - maxZ < Vector.MINIMUM_RESOLUTION;
}
// Modification methods
@Override
public Bounds addPlane(final PlanetModel planetModel, final Plane plane, final Membership... bounds) {
plane.recordBounds(planetModel, this, bounds);
return this;
}
/** Add a horizontal plane to the bounds description.
* This method should EITHER use the supplied latitude, OR use the supplied
* plane, depending on what is most efficient.
*@param planetModel is the planet model.
*@param latitude is the latitude.
*@param horizontalPlane is the plane.
*@param bounds are the constraints on the plane.
*@return updated Bounds object.
*/
public Bounds addHorizontalPlane(final PlanetModel planetModel,
final double latitude,
final Plane horizontalPlane,
final Membership... bounds) {
return addPlane(planetModel, horizontalPlane, bounds);
}
/** Add a vertical plane to the bounds description.
* This method should EITHER use the supplied longitude, OR use the supplied
* plane, depending on what is most efficient.
*@param planetModel is the planet model.
*@param longitude is the longitude.
*@param verticalPlane is the plane.
*@param bounds are the constraints on the plane.
*@return updated Bounds object.
*/
public Bounds addVerticalPlane(final PlanetModel planetModel,
final double longitude,
final Plane verticalPlane,
final Membership... bounds) {
return addPlane(planetModel, verticalPlane, bounds);
}
@Override
public Bounds addXValue(final GeoPoint point) {
final double x = point.x;
final double small = x - FUDGE_FACTOR;
if (minX == null || minX > small) {
minX = new Double(small);
}
final double large = x + FUDGE_FACTOR;
if (maxX == null || maxX < large) {
maxX = new Double(large);
}
return this;
}
@Override
public Bounds addYValue(final GeoPoint point) {
final double y = point.y;
final double small = y - FUDGE_FACTOR;
if (minY == null || minY > small) {
minY = new Double(small);
}
final double large = y + FUDGE_FACTOR;
if (maxY == null || maxY < large) {
maxY = new Double(large);
}
return this;
}
@Override
public Bounds addZValue(final GeoPoint point) {
final double z = point.z;
final double small = z - FUDGE_FACTOR;
if (minZ == null || minZ > small) {
minZ = new Double(small);
}
final double large = z + FUDGE_FACTOR;
if (maxZ == null || maxZ < large) {
maxZ = new Double(large);
}
return this;
}
@Override
public Bounds addPoint(final GeoPoint point) {
return addXValue(point).addYValue(point).addZValue(point);
}
@Override
public Bounds isWide() {
// No specific thing we need to do.
return this;
}
@Override
public Bounds noLongitudeBound() {
// No specific thing we need to do.
return this;
}
@Override
public Bounds noTopLatitudeBound() {
// No specific thing we need to do.
return this;
}
@Override
public Bounds noBottomLatitudeBound() {
// No specific thing we need to do.
return this;
}
}

View File

@ -0,0 +1,377 @@
package org.apache.lucene.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.
*/
/**
* 3D rectangle, bounded on six sides by X,Y,Z limits
*
* @lucene.internal
*/
public class XYZSolid extends BaseXYZSolid {
/** Whole world? */
protected final boolean isWholeWorld;
/** Min-X plane */
protected final SidedPlane minXPlane;
/** Max-X plane */
protected final SidedPlane maxXPlane;
/** Min-Y plane */
protected final SidedPlane minYPlane;
/** Max-Y plane */
protected final SidedPlane maxYPlane;
/** Min-Z plane */
protected final SidedPlane minZPlane;
/** Max-Z plane */
protected final SidedPlane maxZPlane;
/** These are the edge points of the shape, which are defined to be at least one point on
* each surface area boundary. In the case of a solid, this includes points which represent
* the intersection of XYZ bounding planes and the planet, as well as points representing
* the intersection of single bounding planes with the planet itself.
*/
protected final GeoPoint[] edgePoints;
/** Notable points for minXPlane */
protected final GeoPoint[] notableMinXPoints;
/** Notable points for maxXPlane */
protected final GeoPoint[] notableMaxXPoints;
/** Notable points for minYPlane */
protected final GeoPoint[] notableMinYPoints;
/** Notable points for maxYPlane */
protected final GeoPoint[] notableMaxYPoints;
/** Notable points for minZPlane */
protected final GeoPoint[] notableMinZPoints;
/** Notable points for maxZPlane */
protected final GeoPoint[] notableMaxZPoints;
/**
* Sole constructor
*
*@param planetModel is the planet model.
*@param minX is the minimum X value.
*@param maxX is the maximum X value.
*@param minY is the minimum Y value.
*@param maxY is the maximum Y value.
*@param minZ is the minimum Z value.
*@param maxZ is the maximum Z value.
*/
public XYZSolid(final PlanetModel planetModel,
final double minX,
final double maxX,
final double minY,
final double maxY,
final double minZ,
final double maxZ) {
super(planetModel);
// Argument checking
if (maxX - minX < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("X values in wrong order or identical");
if (maxY - minY < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("Y values in wrong order or identical");
if (maxZ - minZ < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("Z values in wrong order or identical");
final double worldMinX = planetModel.getMinimumXValue();
final double worldMaxX = planetModel.getMaximumXValue();
final double worldMinY = planetModel.getMinimumYValue();
final double worldMaxY = planetModel.getMaximumYValue();
final double worldMinZ = planetModel.getMinimumZValue();
final double worldMaxZ = planetModel.getMaximumZValue();
// We must distinguish between the case where the solid represents the entire world,
// and when the solid has no overlap with any part of the surface. In both cases,
// there will be no edgepoints.
isWholeWorld =
(minX - worldMinX < -Vector.MINIMUM_RESOLUTION) &&
(maxX - worldMaxX > Vector.MINIMUM_RESOLUTION) &&
(minY - worldMinY < -Vector.MINIMUM_RESOLUTION) &&
(maxY - worldMaxY > Vector.MINIMUM_RESOLUTION) &&
(minZ - worldMinZ < -Vector.MINIMUM_RESOLUTION) &&
(maxZ - worldMaxZ > Vector.MINIMUM_RESOLUTION);
if (isWholeWorld) {
minXPlane = null;
maxXPlane = null;
minYPlane = null;
maxYPlane = null;
minZPlane = null;
maxZPlane = null;
notableMinXPoints = null;
notableMaxXPoints = null;
notableMinYPoints = null;
notableMaxYPoints = null;
notableMinZPoints = null;
notableMaxZPoints = null;
edgePoints = null;
} else {
// Construct the planes
minXPlane = new SidedPlane(maxX,0.0,0.0,xUnitVector,-minX);
maxXPlane = new SidedPlane(minX,0.0,0.0,xUnitVector,-maxX);
minYPlane = new SidedPlane(0.0,maxY,0.0,yUnitVector,-minY);
maxYPlane = new SidedPlane(0.0,minY,0.0,yUnitVector,-maxY);
minZPlane = new SidedPlane(0.0,0.0,maxZ,zUnitVector,-minZ);
maxZPlane = new SidedPlane(0.0,0.0,minZ,zUnitVector,-maxZ);
// We need at least one point on the planet surface for each manifestation of the shape.
// There can be up to 2 (on opposite sides of the world). But we have to go through
// 12 combinations of adjacent planes in order to find out if any have 2 intersection solution.
// Typically, this requires 12 square root operations.
final GeoPoint[] minXminY = minXPlane.findIntersections(planetModel,minYPlane,maxXPlane,maxYPlane,minZPlane,maxZPlane);
final GeoPoint[] minXmaxY = minXPlane.findIntersections(planetModel,maxYPlane,maxXPlane,minYPlane,minZPlane,maxZPlane);
final GeoPoint[] minXminZ = minXPlane.findIntersections(planetModel,minZPlane,maxXPlane,maxZPlane,minYPlane,maxYPlane);
final GeoPoint[] minXmaxZ = minXPlane.findIntersections(planetModel,maxZPlane,maxXPlane,minZPlane,minYPlane,maxYPlane);
final GeoPoint[] maxXminY = maxXPlane.findIntersections(planetModel,minYPlane,minXPlane,maxYPlane,minZPlane,maxZPlane);
final GeoPoint[] maxXmaxY = maxXPlane.findIntersections(planetModel,maxYPlane,minXPlane,minYPlane,minZPlane,maxZPlane);
final GeoPoint[] maxXminZ = maxXPlane.findIntersections(planetModel,minZPlane,minXPlane,maxZPlane,minYPlane,maxYPlane);
final GeoPoint[] maxXmaxZ = maxXPlane.findIntersections(planetModel,maxZPlane,minXPlane,minZPlane,minYPlane,maxYPlane);
final GeoPoint[] minYminZ = minYPlane.findIntersections(planetModel,minZPlane,maxYPlane,maxZPlane,minXPlane,maxXPlane);
final GeoPoint[] minYmaxZ = minYPlane.findIntersections(planetModel,maxZPlane,maxYPlane,minZPlane,minXPlane,maxXPlane);
final GeoPoint[] maxYminZ = maxYPlane.findIntersections(planetModel,minZPlane,minYPlane,maxZPlane,minXPlane,maxXPlane);
final GeoPoint[] maxYmaxZ = maxYPlane.findIntersections(planetModel,maxZPlane,minYPlane,minZPlane,minXPlane,maxXPlane);
notableMinXPoints = glueTogether(minXminY, minXmaxY, minXminZ, minXmaxZ);
notableMaxXPoints = glueTogether(maxXminY, maxXmaxY, maxXminZ, maxXmaxZ);
notableMinYPoints = glueTogether(minXminY, maxXminY, minYminZ, minYmaxZ);
notableMaxYPoints = glueTogether(minXmaxY, maxXmaxY, maxYminZ, maxYmaxZ);
notableMinZPoints = glueTogether(minXminZ, maxXminZ, minYminZ, maxYminZ);
notableMaxZPoints = glueTogether(minXmaxZ, maxXmaxZ, minYmaxZ, maxYmaxZ);
// Now, compute the edge points.
// This is the trickiest part of setting up an XYZSolid. We've computed intersections already, so
// we'll start there.
// There can be a number of shapes, each of which needs an edgepoint. Each side by itself might contribute
// an edgepoint, for instance, if the plane describing that side intercepts the planet in such a way that the ellipse
// of interception does not meet any other planes. Plane intersections can each contribute 0, 1, or 2 edgepoints.
//
// All of this makes for a lot of potential edgepoints, but I believe these can be pruned back with careful analysis.
// I haven't yet done that analysis, however, so I will treat them all as individual edgepoints.
// The cases we are looking for are when the four corner points for any given
// plane are all outside of the world, AND that plane intersects the world.
// There are eight corner points all told; we must evaluate these WRT the planet surface.
final boolean minXminYminZ = planetModel.pointOutside(minX, minY, minZ);
final boolean minXminYmaxZ = planetModel.pointOutside(minX, minY, maxZ);
final boolean minXmaxYminZ = planetModel.pointOutside(minX, maxY, minZ);
final boolean minXmaxYmaxZ = planetModel.pointOutside(minX, maxY, maxZ);
final boolean maxXminYminZ = planetModel.pointOutside(maxX, minY, minZ);
final boolean maxXminYmaxZ = planetModel.pointOutside(maxX, minY, maxZ);
final boolean maxXmaxYminZ = planetModel.pointOutside(maxX, maxY, minZ);
final boolean maxXmaxYmaxZ = planetModel.pointOutside(maxX, maxY, maxZ);
// Look at single-plane/world intersections.
// We detect these by looking at the world model and noting its x, y, and z bounds.
final GeoPoint[] minXEdges;
if (minX - worldMinX >= -Vector.MINIMUM_RESOLUTION && minX - worldMaxX <= Vector.MINIMUM_RESOLUTION &&
minY < 0.0 && maxY > 0.0 && minZ < 0.0 && maxZ > 0.0 &&
minXminYminZ && minXminYmaxZ && minXmaxYminZ && minXmaxYmaxZ) {
// Find any point on the minX plane that intersects the world
// First construct a perpendicular plane that will allow us to find a sample point.
// This plane is vertical and goes through the points (0,0,0) and (1,0,0)
// Then use it to compute a sample point.
minXEdges = new GeoPoint[]{minXPlane.getSampleIntersectionPoint(planetModel, xVerticalPlane)};
} else {
minXEdges = EMPTY_POINTS;
}
final GeoPoint[] maxXEdges;
if (maxX - worldMinX >= -Vector.MINIMUM_RESOLUTION && maxX - worldMaxX <= Vector.MINIMUM_RESOLUTION &&
minY < 0.0 && maxY > 0.0 && minZ < 0.0 && maxZ > 0.0 &&
maxXminYminZ && maxXminYmaxZ && maxXmaxYminZ && maxXmaxYmaxZ) {
// Find any point on the maxX plane that intersects the world
// First construct a perpendicular plane that will allow us to find a sample point.
// This plane is vertical and goes through the points (0,0,0) and (1,0,0)
// Then use it to compute a sample point.
maxXEdges = new GeoPoint[]{maxXPlane.getSampleIntersectionPoint(planetModel, xVerticalPlane)};
} else {
maxXEdges = EMPTY_POINTS;
}
final GeoPoint[] minYEdges;
if (minY - worldMinY >= -Vector.MINIMUM_RESOLUTION && minY - worldMaxY <= Vector.MINIMUM_RESOLUTION &&
minX < 0.0 && maxX > 0.0 && minZ < 0.0 && maxZ > 0.0 &&
minXminYminZ && minXminYmaxZ && maxXminYminZ && maxXminYmaxZ) {
// Find any point on the minY plane that intersects the world
// First construct a perpendicular plane that will allow us to find a sample point.
// This plane is vertical and goes through the points (0,0,0) and (0,1,0)
// Then use it to compute a sample point.
minYEdges = new GeoPoint[]{minYPlane.getSampleIntersectionPoint(planetModel, yVerticalPlane)};
} else {
minYEdges = EMPTY_POINTS;
}
final GeoPoint[] maxYEdges;
if (maxY - worldMinY >= -Vector.MINIMUM_RESOLUTION && maxY - worldMaxY <= Vector.MINIMUM_RESOLUTION &&
minX < 0.0 && maxX > 0.0 && minZ < 0.0 && maxZ > 0.0 &&
minXmaxYminZ && minXmaxYmaxZ && maxXmaxYminZ && maxXmaxYmaxZ) {
// Find any point on the maxY plane that intersects the world
// First construct a perpendicular plane that will allow us to find a sample point.
// This plane is vertical and goes through the points (0,0,0) and (0,1,0)
// Then use it to compute a sample point.
maxYEdges = new GeoPoint[]{maxYPlane.getSampleIntersectionPoint(planetModel, yVerticalPlane)};
} else {
maxYEdges = EMPTY_POINTS;
}
final GeoPoint[] minZEdges;
if (minZ - worldMinZ >= -Vector.MINIMUM_RESOLUTION && minZ - worldMaxZ <= Vector.MINIMUM_RESOLUTION &&
minX < 0.0 && maxX > 0.0 && minY < 0.0 && maxY > 0.0 &&
minXminYminZ && minXmaxYminZ && maxXminYminZ && maxXmaxYminZ) {
// Find any point on the minZ plane that intersects the world
// First construct a perpendicular plane that will allow us to find a sample point.
// This plane is vertical and goes through the points (0,0,0) and (1,0,0)
// Then use it to compute a sample point.
minZEdges = new GeoPoint[]{minZPlane.getSampleIntersectionPoint(planetModel, xVerticalPlane)};
} else {
minZEdges = EMPTY_POINTS;
}
final GeoPoint[] maxZEdges;
if (maxZ - worldMinZ >= -Vector.MINIMUM_RESOLUTION && maxZ - worldMaxZ <= Vector.MINIMUM_RESOLUTION &&
minX < 0.0 && maxX > 0.0 && minY < 0.0 && maxY > 0.0 &&
minXminYmaxZ && minXmaxYmaxZ && maxXminYmaxZ && maxXmaxYmaxZ) {
// Find any point on the maxZ plane that intersects the world
// First construct a perpendicular plane that will allow us to find a sample point.
// This plane is vertical and goes through the points (0,0,0) and (1,0,0) (that is, its orientation doesn't matter)
// Then use it to compute a sample point.
maxZEdges = new GeoPoint[]{maxZPlane.getSampleIntersectionPoint(planetModel, xVerticalPlane)};
} else {
maxZEdges = EMPTY_POINTS;
}
// Glue everything together. This is not a minimal set of edgepoints, as of now, but it does completely describe all shapes on the
// planet.
this.edgePoints = glueTogether(minXminY, minXmaxY, minXminZ, minXmaxZ,
maxXminY, maxXmaxY, maxXminZ, maxXmaxZ,
minYminZ, minYmaxZ, maxYminZ, maxYmaxZ,
minXEdges, maxXEdges, minYEdges, maxYEdges, minZEdges, maxZEdges);
}
}
@Override
protected GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
if (isWholeWorld) {
return true;
}
return minXPlane.isWithin(x, y, z) &&
maxXPlane.isWithin(x, y, z) &&
minYPlane.isWithin(x, y, z) &&
maxYPlane.isWithin(x, y, z) &&
minZPlane.isWithin(x, y, z) &&
maxZPlane.isWithin(x, y, z);
}
@Override
public int getRelationship(final GeoShape path) {
if (isWholeWorld) {
if (path.getEdgePoints().length > 0)
return WITHIN;
return OVERLAPS;
}
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideArea(path);
if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some shape points inside area");
return OVERLAPS;
}
// Figure out if the entire XYZArea is contained by the shape.
final int insideShape = isAreaInsideShape(path);
if (insideShape == SOME_INSIDE) {
//System.err.println(" some area points inside shape");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE && insideShape == ALL_INSIDE) {
//System.err.println(" inside of each other");
return OVERLAPS;
}
if (path.intersects(minXPlane, notableMinXPoints, maxXPlane, minYPlane, maxYPlane, minZPlane, maxZPlane) ||
path.intersects(maxXPlane, notableMaxXPoints, minXPlane, minYPlane, maxYPlane, minZPlane, maxZPlane) ||
path.intersects(minYPlane, notableMinYPoints, maxYPlane, minXPlane, maxXPlane, minZPlane, maxZPlane) ||
path.intersects(maxYPlane, notableMaxYPoints, minYPlane, minXPlane, maxXPlane, minZPlane, maxZPlane) ||
path.intersects(minZPlane, notableMinZPoints, maxZPlane, minXPlane, maxXPlane, minYPlane, maxYPlane) ||
path.intersects(maxZPlane, notableMaxZPoints, minZPlane, minXPlane, maxXPlane, minYPlane, maxYPlane)) {
//System.err.println(" edges intersect");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
//System.err.println(" all shape points inside area");
return WITHIN;
}
if (insideShape == ALL_INSIDE) {
//System.err.println(" all area points inside shape");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof XYZSolid))
return false;
XYZSolid other = (XYZSolid) o;
if (!super.equals(other) ||
other.isWholeWorld != isWholeWorld) {
return false;
}
if (!isWholeWorld) {
return other.minXPlane.equals(minXPlane) &&
other.maxXPlane.equals(maxXPlane) &&
other.minYPlane.equals(minYPlane) &&
other.maxYPlane.equals(maxYPlane) &&
other.minZPlane.equals(minZPlane) &&
other.maxZPlane.equals(maxZPlane);
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (isWholeWorld?1:0);
if (!isWholeWorld) {
result = 31 * result + minXPlane.hashCode();
result = 31 * result + maxXPlane.hashCode();
result = 31 * result + minYPlane.hashCode();
result = 31 * result + maxYPlane.hashCode();
result = 31 * result + minZPlane.hashCode();
result = 31 * result + maxZPlane.hashCode();
}
return result;
}
@Override
public String toString() {
return "XYZSolid: {planetmodel="+planetModel+", isWholeWorld="+isWholeWorld+", minXplane="+minXPlane+", maxXplane="+maxXPlane+", minYplane="+minYPlane+", maxYplane="+maxYPlane+", minZplane="+minZPlane+", maxZplane="+maxZPlane+"}";
}
}

View File

@ -0,0 +1,209 @@
package org.apache.lucene.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.
*/
/**
* 3D rectangle, bounded on six sides by X,Y,Z limits, degenerate in Z
*
* @lucene.internal
*/
public class XYdZSolid extends BaseXYZSolid {
/** Min-X plane */
protected final SidedPlane minXPlane;
/** Max-X plane */
protected final SidedPlane maxXPlane;
/** Min-Y plane */
protected final SidedPlane minYPlane;
/** Max-Y plane */
protected final SidedPlane maxYPlane;
/** Z plane */
protected final Plane zPlane;
/** These are the edge points of the shape, which are defined to be at least one point on
* each surface area boundary. In the case of a solid, this includes points which represent
* the intersection of XYZ bounding planes and the planet, as well as points representing
* the intersection of single bounding planes with the planet itself.
*/
protected final GeoPoint[] edgePoints;
/** Notable points for ZPlane */
protected final GeoPoint[] notableZPoints;
/**
* Sole constructor
*
*@param planetModel is the planet model.
*@param minX is the minimum X value.
*@param maxX is the maximum X value.
*@param minY is the minimum Y value.
*@param maxY is the maximum Y value.
*@param Z is the Z value.
*/
public XYdZSolid(final PlanetModel planetModel,
final double minX,
final double maxX,
final double minY,
final double maxY,
final double Z) {
super(planetModel);
// Argument checking
if (maxX - minX < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("X values in wrong order or identical");
if (maxY - minY < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("Y values in wrong order or identical");
final double worldMinZ = planetModel.getMinimumZValue();
final double worldMaxZ = planetModel.getMaximumZValue();
// Construct the planes
minXPlane = new SidedPlane(maxX,0.0,0.0,xUnitVector,-minX);
maxXPlane = new SidedPlane(minX,0.0,0.0,xUnitVector,-maxX);
minYPlane = new SidedPlane(0.0,maxY,0.0,yUnitVector,-minY);
maxYPlane = new SidedPlane(0.0,minY,0.0,yUnitVector,-maxY);
zPlane = new Plane(zUnitVector,-Z);
// We need at least one point on the planet surface for each manifestation of the shape.
// There can be up to 2 (on opposite sides of the world). But we have to go through
// 4 combinations of adjacent planes in order to find out if any have 2 intersection solution.
// Typically, this requires 4 square root operations.
final GeoPoint[] minXZ = minXPlane.findIntersections(planetModel,zPlane,maxXPlane,minYPlane,maxYPlane);
final GeoPoint[] maxXZ = maxXPlane.findIntersections(planetModel,zPlane,minXPlane,minYPlane,maxYPlane);
final GeoPoint[] minYZ = minYPlane.findIntersections(planetModel,zPlane,maxYPlane,minXPlane,maxXPlane);
final GeoPoint[] maxYZ = maxYPlane.findIntersections(planetModel,zPlane,minYPlane,minXPlane,maxXPlane);
notableZPoints = glueTogether(minXZ, maxXZ, minYZ, maxYZ);
// Now, compute the edge points.
// This is the trickiest part of setting up an XYZSolid. We've computed intersections already, so
// we'll start there. We know that at most there will be two disconnected shapes on the planet surface.
// But there's also a case where exactly one plane slices through the world, and none of the bounding plane
// intersections do. Thus, if we don't find any of the edge intersection cases, we have to look for that last case.
// If we still haven't encountered anything, we need to look at single-plane/world intersections.
// We detect these by looking at the world model and noting its x, y, and z bounds.
// The cases we are looking for are when the four corner points for any given
// plane are all outside of the world, AND that plane intersects the world.
// There are four corner points all told; we must evaluate these WRT the planet surface.
final boolean minXminYZ = planetModel.pointOutside(minX, minY, Z);
final boolean minXmaxYZ = planetModel.pointOutside(minX, maxY, Z);
final boolean maxXminYZ = planetModel.pointOutside(maxX, minY, Z);
final boolean maxXmaxYZ = planetModel.pointOutside(maxX, maxY, Z);
final GeoPoint[] zEdges;
if (Z - worldMinZ >= -Vector.MINIMUM_RESOLUTION && Z - worldMaxZ <= Vector.MINIMUM_RESOLUTION &&
minX < 0.0 && maxX > 0.0 && minY < 0.0 && maxY > 0.0 &&
minXminYZ && minXmaxYZ && maxXminYZ && maxXmaxYZ) {
// Find any point on the minZ plane that intersects the world
// First construct a perpendicular plane that will allow us to find a sample point.
// This plane is vertical and goes through the points (0,0,0) and (1,0,0)
// Then use it to compute a sample point.
zEdges = new GeoPoint[]{zPlane.getSampleIntersectionPoint(planetModel, xVerticalPlane)};
} else {
zEdges= EMPTY_POINTS;
}
this.edgePoints = glueTogether(minXZ, maxXZ, minYZ, maxYZ, zEdges);
}
@Override
protected GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return minXPlane.isWithin(x, y, z) &&
maxXPlane.isWithin(x, y, z) &&
minYPlane.isWithin(x, y, z) &&
maxYPlane.isWithin(x, y, z) &&
zPlane.evaluateIsZero(x, y, z);
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideArea(path);
if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some inside");
return OVERLAPS;
}
// Figure out if the entire XYZArea is contained by the shape.
final int insideShape = isAreaInsideShape(path);
if (insideShape == SOME_INSIDE) {
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE && insideShape == ALL_INSIDE) {
//System.err.println(" inside of each other");
return OVERLAPS;
}
if (path.intersects(zPlane, notableZPoints, minXPlane, maxXPlane, minYPlane, maxYPlane)) {
//System.err.println(" edges intersect");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
//System.err.println(" shape inside rectangle");
return WITHIN;
}
if (insideShape == ALL_INSIDE) {
//System.err.println(" shape contains rectangle");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof XYdZSolid))
return false;
XYdZSolid other = (XYdZSolid) o;
if (!super.equals(other)) {
return false;
}
return other.minXPlane.equals(minXPlane) &&
other.maxXPlane.equals(maxXPlane) &&
other.minYPlane.equals(minYPlane) &&
other.maxYPlane.equals(maxYPlane) &&
other.zPlane.equals(zPlane);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + minXPlane.hashCode();
result = 31 * result + maxXPlane.hashCode();
result = 31 * result + minYPlane.hashCode();
result = 31 * result + maxYPlane.hashCode();
result = 31 * result + zPlane.hashCode();
return result;
}
@Override
public String toString() {
return "XYdZSolid: {planetmodel="+planetModel+", minXplane="+minXPlane+", maxXplane="+maxXPlane+", minYplane="+minYPlane+", maxYplane="+maxYPlane+", zplane="+zPlane+"}";
}
}

View File

@ -0,0 +1,208 @@
package org.apache.lucene.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.
*/
/**
* 3D rectangle, bounded on six sides by X,Y,Z limits, degenerate in Y
*
* @lucene.internal
*/
public class XdYZSolid extends BaseXYZSolid {
/** Min-X plane */
protected final SidedPlane minXPlane;
/** Max-X plane */
protected final SidedPlane maxXPlane;
/** Y plane */
protected final Plane yPlane;
/** Min-Z plane */
protected final SidedPlane minZPlane;
/** Max-Z plane */
protected final SidedPlane maxZPlane;
/** These are the edge points of the shape, which are defined to be at least one point on
* each surface area boundary. In the case of a solid, this includes points which represent
* the intersection of XYZ bounding planes and the planet, as well as points representing
* the intersection of single bounding planes with the planet itself.
*/
protected final GeoPoint[] edgePoints;
/** Notable points for YPlane */
protected final GeoPoint[] notableYPoints;
/**
* Sole constructor
*
*@param planetModel is the planet model.
*@param minX is the minimum X value.
*@param maxX is the maximum X value.
*@param Y is the Y value.
*@param minZ is the minimum Z value.
*@param maxZ is the maximum Z value.
*/
public XdYZSolid(final PlanetModel planetModel,
final double minX,
final double maxX,
final double Y,
final double minZ,
final double maxZ) {
super(planetModel);
// Argument checking
if (maxX - minX < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("X values in wrong order or identical");
if (maxZ - minZ < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("Z values in wrong order or identical");
final double worldMinY = planetModel.getMinimumYValue();
final double worldMaxY = planetModel.getMaximumYValue();
// Construct the planes
minXPlane = new SidedPlane(maxX,0.0,0.0,xUnitVector,-minX);
maxXPlane = new SidedPlane(minX,0.0,0.0,xUnitVector,-maxX);
yPlane = new Plane(yUnitVector,-Y);
minZPlane = new SidedPlane(0.0,0.0,maxZ,zUnitVector,-minZ);
maxZPlane = new SidedPlane(0.0,0.0,minZ,zUnitVector,-maxZ);
// We need at least one point on the planet surface for each manifestation of the shape.
// There can be up to 2 (on opposite sides of the world). But we have to go through
// 4 combinations of adjacent planes in order to find out if any have 2 intersection solution.
// Typically, this requires 4 square root operations.
final GeoPoint[] minXY = minXPlane.findIntersections(planetModel,yPlane,maxXPlane,minZPlane,maxZPlane);
final GeoPoint[] maxXY = maxXPlane.findIntersections(planetModel,yPlane,minXPlane,minZPlane,maxZPlane);
final GeoPoint[] YminZ = yPlane.findIntersections(planetModel,minZPlane,maxZPlane,minXPlane,maxXPlane);
final GeoPoint[] YmaxZ = yPlane.findIntersections(planetModel,maxZPlane,minZPlane,minXPlane,maxXPlane);
notableYPoints = glueTogether(minXY, maxXY, YminZ, YmaxZ);
// Now, compute the edge points.
// This is the trickiest part of setting up an XYZSolid. We've computed intersections already, so
// we'll start there. We know that at most there will be two disconnected shapes on the planet surface.
// But there's also a case where exactly one plane slices through the world, and none of the bounding plane
// intersections do. Thus, if we don't find any of the edge intersection cases, we have to look for that last case.
// We need to look at single-plane/world intersections.
// We detect these by looking at the world model and noting its x, y, and z bounds.
// The cases we are looking for are when the four corner points for any given
// plane are all outside of the world, AND that plane intersects the world.
// There are four corner points all told; we must evaluate these WRT the planet surface.
final boolean minXYminZ = planetModel.pointOutside(minX, Y, minZ);
final boolean minXYmaxZ = planetModel.pointOutside(minX, Y, maxZ);
final boolean maxXYminZ = planetModel.pointOutside(maxX, Y, minZ);
final boolean maxXYmaxZ = planetModel.pointOutside(maxX, Y, maxZ);
final GeoPoint[] yEdges;
if (Y - worldMinY >= -Vector.MINIMUM_RESOLUTION && Y - worldMaxY <= Vector.MINIMUM_RESOLUTION &&
minX < 0.0 && maxX > 0.0 && minZ < 0.0 && maxZ > 0.0 &&
minXYminZ && minXYmaxZ && maxXYminZ && maxXYmaxZ) {
// Find any point on the minY plane that intersects the world
// First construct a perpendicular plane that will allow us to find a sample point.
// This plane is vertical and goes through the points (0,0,0) and (0,1,0)
// Then use it to compute a sample point.
yEdges = new GeoPoint[]{yPlane.getSampleIntersectionPoint(planetModel, yVerticalPlane)};
} else {
yEdges = EMPTY_POINTS;
}
this.edgePoints = glueTogether(minXY, maxXY, YminZ, YmaxZ, yEdges);
}
@Override
protected GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return minXPlane.isWithin(x, y, z) &&
maxXPlane.isWithin(x, y, z) &&
yPlane.evaluateIsZero(x, y, z) &&
minZPlane.isWithin(x, y, z) &&
maxZPlane.isWithin(x, y, z);
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideArea(path);
if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some inside");
return OVERLAPS;
}
// Figure out if the entire XYZArea is contained by the shape.
final int insideShape = isAreaInsideShape(path);
if (insideShape == SOME_INSIDE) {
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE && insideShape == ALL_INSIDE) {
//System.err.println(" inside of each other");
return OVERLAPS;
}
if (path.intersects(yPlane, notableYPoints, minXPlane, maxXPlane, minZPlane, maxZPlane)) {
//System.err.println(" edges intersect");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
//System.err.println(" shape inside rectangle");
return WITHIN;
}
if (insideShape == ALL_INSIDE) {
//System.err.println(" shape contains rectangle");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof XdYZSolid))
return false;
XdYZSolid other = (XdYZSolid) o;
if (!super.equals(other)) {
return false;
}
return other.minXPlane.equals(minXPlane) &&
other.maxXPlane.equals(maxXPlane) &&
other.yPlane.equals(yPlane) &&
other.minZPlane.equals(minZPlane) &&
other.maxZPlane.equals(maxZPlane);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + minXPlane.hashCode();
result = 31 * result + maxXPlane.hashCode();
result = 31 * result + yPlane.hashCode();
result = 31 * result + minZPlane.hashCode();
result = 31 * result + maxZPlane.hashCode();
return result;
}
@Override
public String toString() {
return "XdYZSolid: {planetmodel="+planetModel+", minXplane="+minXPlane+", maxXplane="+maxXPlane+", yplane="+yPlane+", minZplane="+minZPlane+", maxZplane="+maxZPlane+"}";
}
}

View File

@ -0,0 +1,139 @@
package org.apache.lucene.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.
*/
/**
* 3D rectangle, bounded on six sides by X,Y,Z limits, degenerate in Y and Z.
* This figure, in fact, represents either zero, one, or two points, so the
* actual data stored is minimal.
*
* @lucene.internal
*/
public class XdYdZSolid extends BaseXYZSolid {
/** The points in this figure on the planet surface; also doubles for edge points */
protected final GeoPoint[] surfacePoints;
/**
* Sole constructor
*
*@param planetModel is the planet model.
*@param minX is the minimum X value.
*@param maxX is the maximum X value.
*@param Y is the Y value.
*@param Z is the Z value.
*/
public XdYdZSolid(final PlanetModel planetModel,
final double minX,
final double maxX,
final double Y,
final double Z) {
super(planetModel);
// Argument checking
if (maxX - minX < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("X values in wrong order or identical");
// Build the planes and intersect them.
final Plane yPlane = new Plane(yUnitVector,-Y);
final Plane zPlane = new Plane(zUnitVector,-Z);
final SidedPlane minXPlane = new SidedPlane(maxX,0.0,0.0,xUnitVector,-minX);
final SidedPlane maxXPlane = new SidedPlane(minX,0.0,0.0,xUnitVector,-maxX);
surfacePoints = yPlane.findIntersections(planetModel,zPlane,minXPlane,maxXPlane);
}
@Override
protected GeoPoint[] getEdgePoints() {
return surfacePoints;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
for (final GeoPoint p : surfacePoints) {
if (p.isIdentical(x,y,z))
return true;
}
return false;
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideArea(path);
if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some inside");
return OVERLAPS;
}
// Figure out if the entire XYZArea is contained by the shape.
final int insideShape = isAreaInsideShape(path);
if (insideShape == SOME_INSIDE) {
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE && insideShape == ALL_INSIDE) {
//System.err.println(" inside of each other");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
return WITHIN;
}
if (insideShape == ALL_INSIDE) {
//System.err.println(" shape contains rectangle");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof XdYdZSolid))
return false;
XdYdZSolid other = (XdYdZSolid) o;
if (!super.equals(other) || surfacePoints.length != other.surfacePoints.length ) {
return false;
}
for (int i = 0; i < surfacePoints.length; i++) {
if (!surfacePoints[i].equals(other.surfacePoints[i]))
return false;
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
for (final GeoPoint p : surfacePoints) {
result = 31 * result + p.hashCode();
}
return result;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (final GeoPoint p : surfacePoints) {
sb.append(" ").append(p).append(" ");
}
return "XdYdZSolid: {planetmodel="+planetModel+", "+sb.toString()+"}";
}
}

View File

@ -0,0 +1,212 @@
package org.apache.lucene.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.
*/
/**
* 3D rectangle, bounded on six sides by X,Y,Z limits, degenerate in X.
*
* @lucene.internal
*/
public class dXYZSolid extends BaseXYZSolid {
/** X plane */
protected final Plane xPlane;
/** Min-Y plane */
protected final SidedPlane minYPlane;
/** Max-Y plane */
protected final SidedPlane maxYPlane;
/** Min-Z plane */
protected final SidedPlane minZPlane;
/** Max-Z plane */
protected final SidedPlane maxZPlane;
/** These are the edge points of the shape, which are defined to be at least one point on
* each surface area boundary. In the case of a solid, this includes points which represent
* the intersection of XYZ bounding planes and the planet, as well as points representing
* the intersection of single bounding planes with the planet itself.
*/
protected final GeoPoint[] edgePoints;
/** Notable points for XPlane */
protected final GeoPoint[] notableXPoints;
/**
* Sole constructor
*
*@param planetModel is the planet model.
*@param X is the X value.
*@param minY is the minimum Y value.
*@param maxY is the maximum Y value.
*@param minZ is the minimum Z value.
*@param maxZ is the maximum Z value.
*/
public dXYZSolid(final PlanetModel planetModel,
final double X,
final double minY,
final double maxY,
final double minZ,
final double maxZ) {
super(planetModel);
// Argument checking
if (maxY - minY < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("Y values in wrong order or identical");
if (maxZ - minZ < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("Z values in wrong order or identical");
final double worldMinX = planetModel.getMinimumXValue();
final double worldMaxX = planetModel.getMaximumXValue();
// Construct the planes
xPlane = new Plane(xUnitVector,-X);
minYPlane = new SidedPlane(0.0,maxY,0.0,yUnitVector,-minY);
maxYPlane = new SidedPlane(0.0,minY,0.0,yUnitVector,-maxY);
minZPlane = new SidedPlane(0.0,0.0,maxZ,zUnitVector,-minZ);
maxZPlane = new SidedPlane(0.0,0.0,minZ,zUnitVector,-maxZ);
// We need at least one point on the planet surface for each manifestation of the shape.
// There can be up to 2 (on opposite sides of the world). But we have to go through
// 4 combinations of adjacent planes in order to find out if any have 2 intersection solution.
// Typically, this requires 4 square root operations.
final GeoPoint[] XminY = xPlane.findIntersections(planetModel,minYPlane,maxYPlane,minZPlane,maxZPlane);
final GeoPoint[] XmaxY = xPlane.findIntersections(planetModel,maxYPlane,minYPlane,minZPlane,maxZPlane);
final GeoPoint[] XminZ = xPlane.findIntersections(planetModel,minZPlane,maxZPlane,minYPlane,maxYPlane);
final GeoPoint[] XmaxZ = xPlane.findIntersections(planetModel,maxZPlane,minZPlane,minYPlane,maxYPlane);
notableXPoints = glueTogether(XminY, XmaxY, XminZ, XmaxZ);
// Now, compute the edge points.
// This is the trickiest part of setting up an XYZSolid. We've computed intersections already, so
// we'll start there. We know that at most there will be two disconnected shapes on the planet surface.
// But there's also a case where exactly one plane slices through the world, and none of the bounding plane
// intersections do. Thus, if we don't find any of the edge intersection cases, we have to look for that last case.
// We need to look at single-plane/world intersections.
// We detect these by looking at the world model and noting its x, y, and z bounds.
// For the single-dimension degenerate case, there's really only one plane that can possibly intersect the world.
// The cases we are looking for are when the four corner points for any given
// plane are all outside of the world, AND that plane intersects the world.
// There are four corner points all told; we must evaluate these WRT the planet surface.
final boolean XminYminZ = planetModel.pointOutside(X, minY, minZ);
final boolean XminYmaxZ = planetModel.pointOutside(X, minY, maxZ);
final boolean XmaxYminZ = planetModel.pointOutside(X, maxY, minZ);
final boolean XmaxYmaxZ = planetModel.pointOutside(X, maxY, maxZ);
final GeoPoint[] xEdges;
if (X - worldMinX >= -Vector.MINIMUM_RESOLUTION && X - worldMaxX <= Vector.MINIMUM_RESOLUTION &&
minY < 0.0 && maxY > 0.0 && minZ < 0.0 && maxZ > 0.0 &&
XminYminZ && XminYmaxZ && XmaxYminZ && XmaxYmaxZ) {
// Find any point on the X plane that intersects the world
// First construct a perpendicular plane that will allow us to find a sample point.
// This plane is vertical and goes through the points (0,0,0) and (1,0,0)
// Then use it to compute a sample point.
xEdges = new GeoPoint[]{xPlane.getSampleIntersectionPoint(planetModel, xVerticalPlane)};
} else {
xEdges = EMPTY_POINTS;
}
this.edgePoints = glueTogether(XminY,XmaxY,XminZ,XmaxZ,xEdges);
}
@Override
protected GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return xPlane.evaluateIsZero(x, y, z) &&
minYPlane.isWithin(x, y, z) &&
maxYPlane.isWithin(x, y, z) &&
minZPlane.isWithin(x, y, z) &&
maxZPlane.isWithin(x, y, z);
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideArea(path);
if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some shape points inside area");
return OVERLAPS;
}
// Figure out if the entire XYZArea is contained by the shape.
final int insideShape = isAreaInsideShape(path);
if (insideShape == SOME_INSIDE) {
//System.err.println(" some area points inside shape");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE && insideShape == ALL_INSIDE) {
//System.err.println(" inside of each other");
return OVERLAPS;
}
// The entire locus of points in this shape is on a single plane, so we only need ot look for an intersection with that plane.
//System.err.println("xPlane = "+xPlane);
if (path.intersects(xPlane, notableXPoints, minYPlane, maxYPlane, minZPlane, maxZPlane)) {
//System.err.println(" edges intersect");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
//System.err.println(" shape points inside area");
return WITHIN;
}
if (insideShape == ALL_INSIDE) {
//System.err.println(" shape contains all area");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof dXYZSolid))
return false;
dXYZSolid other = (dXYZSolid) o;
if (!super.equals(other)) {
return false;
}
return other.xPlane.equals(xPlane) &&
other.minYPlane.equals(minYPlane) &&
other.maxYPlane.equals(maxYPlane) &&
other.minZPlane.equals(minZPlane) &&
other.maxZPlane.equals(maxZPlane);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + xPlane.hashCode();
result = 31 * result + minYPlane.hashCode();
result = 31 * result + maxYPlane.hashCode();
result = 31 * result + minZPlane.hashCode();
result = 31 * result + maxZPlane.hashCode();
return result;
}
@Override
public String toString() {
return "dXYZSolid: {planetmodel="+planetModel+", xplane="+xPlane+", minYplane="+minYPlane+", maxYplane="+maxYPlane+", minZplane="+minZPlane+", maxZplane="+maxZPlane+"}";
}
}

View File

@ -0,0 +1,139 @@
package org.apache.lucene.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.
*/
/**
* 3D rectangle, bounded on six sides by X,Y,Z limits, degenerate in X and Z.
* This figure, in fact, represents either zero, one, or two points, so the
* actual data stored is minimal.
*
* @lucene.internal
*/
public class dXYdZSolid extends BaseXYZSolid {
/** The points in this figure on the planet surface; also doubles for edge points */
protected final GeoPoint[] surfacePoints;
/**
* Sole constructor
*
*@param planetModel is the planet model.
*@param X is the X value.
*@param minY is the minimum Y value.
*@param maxY is the maximum Y value.
*@param Z is the Z value.
*/
public dXYdZSolid(final PlanetModel planetModel,
final double X,
final double minY,
final double maxY,
final double Z) {
super(planetModel);
// Argument checking
if (maxY - minY < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("Y values in wrong order or identical");
// Build the planes and intersect them.
final Plane xPlane = new Plane(xUnitVector,-X);
final Plane zPlane = new Plane(zUnitVector,-Z);
final SidedPlane minYPlane = new SidedPlane(0.0,maxY,0.0,yUnitVector,-minY);
final SidedPlane maxYPlane = new SidedPlane(0.0,minY,0.0,yUnitVector,-maxY);
surfacePoints = xPlane.findIntersections(planetModel,zPlane,minYPlane,maxYPlane);
}
@Override
protected GeoPoint[] getEdgePoints() {
return surfacePoints;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
for (final GeoPoint p : surfacePoints) {
if (p.isIdentical(x,y,z))
return true;
}
return false;
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideArea(path);
if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some inside");
return OVERLAPS;
}
// Figure out if the entire XYZArea is contained by the shape.
final int insideShape = isAreaInsideShape(path);
if (insideShape == SOME_INSIDE) {
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE && insideShape == ALL_INSIDE) {
//System.err.println(" inside of each other");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
return WITHIN;
}
if (insideShape == ALL_INSIDE) {
//System.err.println(" shape contains rectangle");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof dXYdZSolid))
return false;
dXYdZSolid other = (dXYdZSolid) o;
if (!super.equals(other) || surfacePoints.length != other.surfacePoints.length ) {
return false;
}
for (int i = 0; i < surfacePoints.length; i++) {
if (!surfacePoints[i].equals(other.surfacePoints[i]))
return false;
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
for (final GeoPoint p : surfacePoints) {
result = 31 * result + p.hashCode();
}
return result;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (final GeoPoint p : surfacePoints) {
sb.append(" ").append(p).append(" ");
}
return "dXYdZSolid: {planetmodel="+planetModel+", "+sb.toString()+"}";
}
}

View File

@ -0,0 +1,139 @@
package org.apache.lucene.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.
*/
/**
* 3D rectangle, bounded on six sides by X,Y,Z limits, degenerate in X and Y.
* This figure, in fact, represents either zero, one, or two points, so the
* actual data stored is minimal.
*
* @lucene.internal
*/
public class dXdYZSolid extends BaseXYZSolid {
/** The points in this figure on the planet surface; also doubles for edge points */
protected final GeoPoint[] surfacePoints;
/**
* Sole constructor
*
*@param planetModel is the planet model.
*@param X is the X value.
*@param Y is the Y value.
*@param minZ is the minimum Z value.
*@param maxZ is the maximum Z value.
*/
public dXdYZSolid(final PlanetModel planetModel,
final double X,
final double Y,
final double minZ,
final double maxZ) {
super(planetModel);
// Argument checking
if (maxZ - minZ < Vector.MINIMUM_RESOLUTION)
throw new IllegalArgumentException("Z values in wrong order or identical");
// Build the planes and intersect them.
final Plane xPlane = new Plane(xUnitVector,-X);
final Plane yPlane = new Plane(yUnitVector,-Y);
final SidedPlane minZPlane = new SidedPlane(0.0,0.0,maxZ,zUnitVector,-minZ);
final SidedPlane maxZPlane = new SidedPlane(0.0,0.0,minZ,zUnitVector,-maxZ);
surfacePoints = xPlane.findIntersections(planetModel,yPlane,minZPlane,maxZPlane);
}
@Override
protected GeoPoint[] getEdgePoints() {
return surfacePoints;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
for (final GeoPoint p : surfacePoints) {
if (p.isIdentical(x,y,z))
return true;
}
return false;
}
@Override
public int getRelationship(final GeoShape path) {
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideArea(path);
if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some inside");
return OVERLAPS;
}
// Figure out if the entire XYZArea is contained by the shape.
final int insideShape = isAreaInsideShape(path);
if (insideShape == SOME_INSIDE) {
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE && insideShape == ALL_INSIDE) {
//System.err.println(" inside of each other");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
return WITHIN;
}
if (insideShape == ALL_INSIDE) {
//System.err.println(" shape contains rectangle");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof dXdYZSolid))
return false;
dXdYZSolid other = (dXdYZSolid) o;
if (!super.equals(other) || surfacePoints.length != other.surfacePoints.length ) {
return false;
}
for (int i = 0; i < surfacePoints.length; i++) {
if (!surfacePoints[i].equals(other.surfacePoints[i]))
return false;
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
for (final GeoPoint p : surfacePoints) {
result = 31 * result + p.hashCode();
}
return result;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (final GeoPoint p : surfacePoints) {
sb.append(" ").append(p).append(" ");
}
return "dXdYZSolid: {planetmodel="+planetModel+", "+sb.toString()+"}";
}
}

View File

@ -0,0 +1,147 @@
package org.apache.lucene.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.
*/
/**
* 3D rectangle, bounded on six sides by X,Y,Z limits, degenerate in all dimensions
*
* @lucene.internal
*/
public class dXdYdZSolid extends BaseXYZSolid {
/** On surface? */
protected final boolean isOnSurface;
/** The point */
protected final GeoPoint thePoint;
/** These are the edge points of the shape, which are defined to be at least one point on
* each surface area boundary. In the case of a solid, this includes points which represent
* the intersection of XYZ bounding planes and the planet, as well as points representing
* the intersection of single bounding planes with the planet itself.
*/
protected final GeoPoint[] edgePoints;
/** Empty array of {@link GeoPoint}. */
protected static final GeoPoint[] nullPoints = new GeoPoint[0];
/**
* Sole constructor
*
*@param planetModel is the planet model.
*@param X is the X value.
*@param Y is the Y value.
*@param Z is the Z value.
*/
public dXdYdZSolid(final PlanetModel planetModel,
final double X,
final double Y,
final double Z) {
super(planetModel);
isOnSurface = planetModel.pointOnSurface(X,Y,Z);
if (isOnSurface) {
thePoint = new GeoPoint(X,Y,Z);
edgePoints = new GeoPoint[]{thePoint};
} else {
thePoint = null;
edgePoints = nullPoints;
}
}
@Override
protected GeoPoint[] getEdgePoints() {
return edgePoints;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
if (!isOnSurface) {
return false;
}
return thePoint.isIdentical(x,y,z);
}
@Override
public int getRelationship(final GeoShape path) {
if (!isOnSurface) {
return DISJOINT;
}
//System.err.println(this+" getrelationship with "+path);
final int insideRectangle = isShapeInsideArea(path);
if (insideRectangle == SOME_INSIDE) {
//System.err.println(" some shape points inside area");
return OVERLAPS;
}
// Figure out if the entire XYZArea is contained by the shape.
final int insideShape = isAreaInsideShape(path);
if (insideShape == SOME_INSIDE) {
//System.err.println(" some area points inside shape");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE && insideShape == ALL_INSIDE) {
//System.err.println(" inside of each other");
return OVERLAPS;
}
if (insideRectangle == ALL_INSIDE) {
//System.err.println(" shape inside area entirely");
return WITHIN;
}
if (insideShape == ALL_INSIDE) {
//System.err.println(" shape contains area entirely");
return CONTAINS;
}
//System.err.println(" disjoint");
return DISJOINT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof dXdYdZSolid))
return false;
dXdYdZSolid other = (dXdYdZSolid) o;
if (!super.equals(other) ||
other.isOnSurface != isOnSurface) {
return false;
}
if (isOnSurface) {
return other.thePoint.equals(thePoint);
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (isOnSurface?1:0);
if (isOnSurface) {
result = 31 * result + thePoint.hashCode();
}
return result;
}
@Override
public String toString() {
return "dXdYdZSolid: {planetmodel="+planetModel+", isOnSurface="+isOnSurface+", thePoint="+thePoint+"}";
}
}

View File

@ -0,0 +1,17 @@
# 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.
org.apache.lucene.bkdtree3d.Geo3DDocValuesFormat

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@ public class GeoBBoxTest {
relationship = box.getRelationship(shape);
assertEquals(GeoArea.CONTAINS, relationship);
box = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, -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);
//System.out.println("Shape = " + shape + " Rect = " + box);
relationship = box.getRelationship(shape);
assertEquals(GeoArea.CONTAINS, relationship);
}
@ -139,11 +139,61 @@ public class GeoBBoxTest {
@Test
public void testBBoxBounds() {
GeoBBox c;
Bounds b;
LatLonBounds b;
XYZBounds xyzb;
GeoArea solid;
GeoPoint point;
int relationship;
c= GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, 0.7570958596622309, -0.7458670829264561, -0.9566079379002148, 1.4802570961901191);
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE,0.10922258701604912,0.1248184603754517,-0.8172414690802067,0.9959041483215542,-0.6136586624726926,0.6821740363641521);
point = new GeoPoint(PlanetModel.SPHERE, 0.3719987557178081, 1.4529582778845198);
assertTrue(c.isWithin(point));
assertTrue(solid.isWithin(point));
relationship = solid.getRelationship(c);
assertTrue(relationship == GeoArea.OVERLAPS || relationship == GeoArea.CONTAINS || relationship == GeoArea.WITHIN);
c= GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, 0.006607096847842122, -0.002828135860810422, -0.0012934461873348349, 0.006727418645092394);
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE,0.9999995988328008,1.0000000002328306,-0.0012934708508166816,0.006727393021214471,-0.002828157275369464,0.006607074060760007);
point = new GeoPoint(PlanetModel.SPHERE, -5.236470872437899E-4, 3.992578692654256E-4);
assertTrue(c.isWithin(point));
assertTrue(solid.isWithin(point));
relationship = solid.getRelationship(c);
assertTrue(relationship == GeoArea.OVERLAPS || relationship == GeoArea.CONTAINS || relationship == GeoArea.WITHIN);
c = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI * 0.25, -Math.PI * 0.25, -1.0, 1.0);
b = new LatLonBounds();
c.getBounds(b);
xyzb = new XYZBounds();
c.getBounds(xyzb);
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(Math.PI * 0.25, b.getMaxLatitude(), 0.000001);
assertEquals(0.382051, xyzb.getMinimumX(), 0.000001);
assertEquals(1.0, xyzb.getMaximumX(), 0.000001);
assertEquals(-0.841471, xyzb.getMinimumY(), 0.000001);
assertEquals(0.841471, xyzb.getMaximumY(), 0.000001);
assertEquals(-0.707107, xyzb.getMinimumZ(), 0.000001);
assertEquals(0.707107, xyzb.getMaximumZ(), 0.000001);
GeoArea area = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE,
xyzb.getMinimumX() - 2.0 * Vector.MINIMUM_RESOLUTION,
xyzb.getMaximumX() + 2.0 * Vector.MINIMUM_RESOLUTION,
xyzb.getMinimumY() - 2.0 * Vector.MINIMUM_RESOLUTION,
xyzb.getMaximumY() + 2.0 * Vector.MINIMUM_RESOLUTION,
xyzb.getMinimumZ() - 2.0 * Vector.MINIMUM_RESOLUTION,
xyzb.getMaximumZ() + 2.0 * Vector.MINIMUM_RESOLUTION);
assertEquals(GeoArea.WITHIN, area.getRelationship(c));
c = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, 0.0, -Math.PI * 0.25, -1.0, 1.0);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
xyzb = new XYZBounds();
c.getBounds(xyzb);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -151,10 +201,19 @@ public class GeoBBoxTest {
assertEquals(1.0, b.getRightLongitude(), 0.000001);
assertEquals(-Math.PI * 0.25, b.getMinLatitude(), 0.000001);
assertEquals(0.0, b.getMaxLatitude(), 0.000001);
assertEquals(0.382051, xyzb.getMinimumX(), 0.000001);
assertEquals(1.0, xyzb.getMaximumX(), 0.000001);
assertEquals(-0.841471, xyzb.getMinimumY(), 0.000001);
assertEquals(0.841471, xyzb.getMaximumY(), 0.000001);
assertEquals(-0.707107, xyzb.getMinimumZ(), 0.000001);
assertEquals(0.0, xyzb.getMaximumZ(), 0.000001);
c = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, 0.0, -Math.PI * 0.25, 1.0, -1.0);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
xyzb = new XYZBounds();
c.getBounds(xyzb);
assertTrue(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -162,30 +221,56 @@ public class GeoBBoxTest {
//assertEquals(-1.0,b.getRightLongitude(),0.000001);
assertEquals(-Math.PI * 0.25, b.getMinLatitude(), 0.000001);
assertEquals(0.0, b.getMaxLatitude(), 0.000001);
assertEquals(-1.0, xyzb.getMinimumX(), 0.000001);
assertEquals(0.540303, xyzb.getMaximumX(), 0.000001);
assertEquals(-1.0, xyzb.getMinimumY(), 0.000001);
assertEquals(1.0, xyzb.getMaximumY(), 0.000001);
assertEquals(-0.707107, xyzb.getMinimumZ(), 0.000001);
assertEquals(0.0, xyzb.getMaximumZ(), 0.000001);
c = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI * 0.5, -Math.PI * 0.5, -1.0, 1.0);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
b = new LatLonBounds();
c.getBounds(b);
xyzb = new XYZBounds();
c.getBounds(xyzb);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
assertEquals(-1.0, b.getLeftLongitude(), 0.000001);
assertEquals(1.0, b.getRightLongitude(), 0.000001);
//assertEquals(-1.0, b.getLeftLongitude(), 0.000001);
//assertEquals(1.0, b.getRightLongitude(), 0.000001);
assertEquals(0.0, xyzb.getMinimumX(), 0.000001);
assertEquals(1.0, xyzb.getMaximumX(), 0.000001);
assertEquals(-0.841471, xyzb.getMinimumY(), 0.000001);
assertEquals(0.841471, xyzb.getMaximumY(), 0.000001);
assertEquals(-1.0, xyzb.getMinimumZ(), 0.000001);
assertEquals(1.0, xyzb.getMaximumZ(), 0.000001);
c = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI * 0.5, -Math.PI * 0.5, 1.0, -1.0);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
xyzb = new XYZBounds();
c.getBounds(xyzb);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
//assertEquals(1.0,b.getLeftLongitude(),0.000001);
//assertEquals(-1.0,b.getRightLongitude(),0.000001);
assertEquals(-1.0, xyzb.getMinimumX(), 0.000001);
assertEquals(0.540303, xyzb.getMaximumX(), 0.000001);
assertEquals(-1.0, xyzb.getMinimumY(), 0.000001);
assertEquals(1.0, xyzb.getMaximumY(), 0.000001);
assertEquals(-1.0, xyzb.getMinimumZ(), 0.000001);
assertEquals(1.0, xyzb.getMaximumZ(), 0.000001);
// Check wide variants of rectangle and longitude slice
c = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, 0.0, -Math.PI * 0.25, -Math.PI + 0.1, Math.PI - 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -196,7 +281,8 @@ public class GeoBBoxTest {
c = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, 0.0, -Math.PI * 0.25, Math.PI - 0.1, -Math.PI + 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -207,7 +293,8 @@ public class GeoBBoxTest {
c = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI * 0.5, -Math.PI * 0.5, -Math.PI + 0.1, Math.PI - 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
@ -216,17 +303,19 @@ public class GeoBBoxTest {
c = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI * 0.5, -Math.PI * 0.5, Math.PI - 0.1, -Math.PI + 0.1);
b = c.getBounds(null);
assertFalse(b.checkNoLongitudeBound());
b = new LatLonBounds();
c.getBounds(b);
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);
//assertEquals(Math.PI - 0.1, b.getLeftLongitude(), 0.000001);
//assertEquals(-Math.PI + 0.1, b.getRightLongitude(), 0.000001);
// Check latitude zone
c = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, 1.0, -1.0, -Math.PI, Math.PI);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -240,9 +329,9 @@ public class GeoBBoxTest {
c1 = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI * 0.5, -Math.PI * 0.5, -Math.PI, 0.0);
c2 = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI * 0.5, -Math.PI * 0.5, 0.0, Math.PI);
b = new Bounds();
b = c1.getBounds(b);
b = c2.getBounds(b);
b = new LatLonBounds();
c1.getBounds(b);
c2.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
@ -250,9 +339,9 @@ public class GeoBBoxTest {
c1 = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI * 0.5, -Math.PI * 0.5, -Math.PI, 0.0);
c2 = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, 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);
b = new LatLonBounds();
c1.getBounds(b);
c2.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
@ -262,9 +351,9 @@ public class GeoBBoxTest {
c1 = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI * 0.5, -Math.PI * 0.5, -Math.PI * 0.5, 0.0);
c2 = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, Math.PI * 0.5, -Math.PI * 0.5, 0.0, Math.PI);
b = new Bounds();
b = c1.getBounds(b);
b = c2.getBounds(b);
b = new LatLonBounds();
c1.getBounds(b);
c2.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());

View File

@ -23,8 +23,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class GeoCircleTest {
import org.apache.lucene.util.LuceneTestCase;
public class GeoCircleTest extends LuceneTestCase {
@Test
public void testCircleDistance() {
@ -62,7 +63,8 @@ public class GeoCircleTest {
assertTrue(c.isWithin(gp));
gp = new GeoPoint(PlanetModel.SPHERE, 0.0, Math.PI);
assertTrue(c.isWithin(gp));
Bounds b = c.getBounds(null);
LatLonBounds b = new LatLonBounds();
c.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
@ -93,12 +95,157 @@ public class GeoCircleTest {
@Test
public void testCircleBounds() {
GeoCircle c;
Bounds b;
LatLonBounds b;
XYZBounds xyzb;
GeoArea area;
GeoPoint p1;
GeoPoint p2;
int relationship;
// Twelfth BKD discovered failure
c = new GeoCircle(PlanetModel.WGS84,-0.00824379317765984,-0.0011677469001838581,0.0011530035396910402);
p1 = new GeoPoint(PlanetModel.WGS84,-0.006505092992723671,0.007654282718327381);
p2 = new GeoPoint(1.0010681673665647,0.007662608264336381,-0.006512324005914593);
assertTrue(!c.isWithin(p1));
assertTrue(!c.isWithin(p2));
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.WGS84,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
relationship = area.getRelationship(c);
assertTrue(relationship == GeoArea.OVERLAPS || relationship == GeoArea.WITHIN);
// Point is actually outside the bounds, and outside the shape
assertTrue(!area.isWithin(p1));
// Approximate point the same
assertTrue(!area.isWithin(p2));
// Eleventh BKD discovered failure
c = new GeoCircle(PlanetModel.SPHERE,-0.004431288600558495,-0.003687846671278374,1.704543429364245E-8);
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
//System.err.println(area);
relationship = area.getRelationship(c);
assertTrue(GeoArea.WITHIN == relationship || GeoArea.OVERLAPS == relationship);
// Tenth BKD discovered failure
c = new GeoCircle(PlanetModel.WGS84,-0.0018829770647349636,-0.001969499061382591,1.3045439293158305E-5);
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.WGS84,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
//System.err.println(area);
relationship = area.getRelationship(c);
assertTrue(GeoArea.WITHIN == relationship || GeoArea.OVERLAPS == relationship);
// Ninth BKD discovered failure
c = new GeoCircle(PlanetModel.SPHERE,-4.211990380885122E-5,-0.0022958453508173044,1.4318475623498535E-5);
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
//System.err.println(area);
relationship = area.getRelationship(c);
assertTrue(GeoArea.WITHIN == relationship || GeoArea.OVERLAPS == relationship);
// Eighth BKD discovered failure
c = new GeoCircle(PlanetModel.SPHERE,0.005321278689117842,-0.00216937368755372,1.5306034422500785E-4);
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
//System.err.println(area);
relationship = area.getRelationship(c);
assertTrue(GeoArea.WITHIN == relationship || GeoArea.OVERLAPS == relationship);
// Seventh BKD discovered failure
c = new GeoCircle(PlanetModel.SPHERE,-0.0021627146783861745, -0.0017298167021592304,2.0818312293195752E-4);
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
//System.err.println(area);
relationship = area.getRelationship(c);
assertTrue(GeoArea.WITHIN == relationship || GeoArea.OVERLAPS == relationship);
// Sixth BKD discovered failure
c = new GeoCircle(PlanetModel.WGS84,-0.006450320645814321,0.004660694205115142,0.00489710732634323);
//xyzb = new XYZBounds();
//c.getBounds(xyzb);
//System.err.println("xmin="+xyzb.getMinimumX()+", xmax="+xyzb.getMaximumX()+",ymin="+xyzb.getMinimumY()+", ymax="+xyzb.getMaximumY()+",zmin="+xyzb.getMinimumZ()+", zmax="+xyzb.getMaximumZ());
//xmin=1.0010356621420726, xmax=1.0011141249179447,ymin=-2.5326643901354566E-4, ymax=0.009584741915757169,zmin=-0.011359874956269283, zmax=-0.0015549504447452225
area = GeoAreaFactory.makeGeoArea(PlanetModel.WGS84,1.0010822580620098,1.0010945779732867,0.007079167343247293,0.007541006774427837,-0.0021855011220022575,-0.001896122718181518);
assertTrue(GeoArea.CONTAINS != area.getRelationship(c));
/*
p1 = new GeoPoint(1.0010893045436076,0.007380935180644008,-0.002140671370616495);
// This has a different bounding box, so we can't use it.
//p2 = new GeoPoint(PlanetModel.WGS84,-0.002164069780096702, 0.007505617500830066);
p2 = new GeoPoint(PlanetModel.WGS84,p1.getLatitude(),p1.getLongitude());
assertTrue(PlanetModel.WGS84.pointOnSurface(p2));
assertTrue(!c.isWithin(p2));
assertTrue(!area.isWithin(p2));
assertTrue(!c.isWithin(p1));
assertTrue(PlanetModel.WGS84.pointOnSurface(p1)); // This fails
assertTrue(!area.isWithin(p1)); // This fails
*/
// Fifth BKD discovered failure
c = new GeoCircle(PlanetModel.SPHERE, -0.004282454525970269, -1.6739831367422277E-4, 1.959639723134033E-6);
assertTrue(c.isWithin(c.getEdgePoints()[0]));
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
assertTrue(GeoArea.WITHIN == area.getRelationship(c) || GeoArea.OVERLAPS == area.getRelationship(c));
// Fourth BKD discovered failure
c = new GeoCircle(PlanetModel.SPHERE, -0.0048795517261255, 0.004053904306995974, 5.93699764258874E-6);
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
assertTrue(GeoArea.WITHIN == area.getRelationship(c) || GeoArea.OVERLAPS == area.getRelationship(c));
// Yet another test case from BKD
c = new GeoCircle(PlanetModel.WGS84, 0.006229478708446979, 0.005570196723795424, 3.840276763694387E-5);
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.WGS84,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
p1 = new GeoPoint(PlanetModel.WGS84, 0.006224927111830945, 0.005597367237251763);
p2 = new GeoPoint(1.0010836083810235, 0.005603490759433942, 0.006231850560862502);
assertTrue(PlanetModel.WGS84.pointOnSurface(p1));
//assertTrue(PlanetModel.WGS84.pointOnSurface(p2));
assertTrue(c.isWithin(p1));
assertTrue(c.isWithin(p2));
assertTrue(area.isWithin(p1));
assertTrue(area.isWithin(p2));
// Another test case from BKD
c = new GeoCircle(PlanetModel.SPHERE, -0.005955031040627789, -0.0029274772647399153, 1.601488279374338E-5);
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
relationship = area.getRelationship(c);
assertTrue(relationship == GeoArea.WITHIN || relationship == GeoArea.OVERLAPS);
// Test case from BKD
c = new GeoCircle(PlanetModel.SPHERE, -0.765816119338, 0.991848766844, 0.8153163226330487);
p1 = new GeoPoint(0.7692262265236023, -0.055089298115534646, -0.6365973465711254);
assertTrue(c.isWithin(p1));
xyzb = new XYZBounds();
c.getBounds(xyzb);
assertTrue(p1.x >= xyzb.getMinimumX() && p1.x <= xyzb.getMaximumX());
assertTrue(p1.y >= xyzb.getMinimumY() && p1.y <= xyzb.getMaximumY());
assertTrue(p1.z >= xyzb.getMinimumZ() && p1.z <= xyzb.getMaximumZ());
// Vertical circle cases
c = new GeoCircle(PlanetModel.SPHERE, 0.0, -0.5, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -107,7 +254,8 @@ public class GeoCircleTest {
assertEquals(-0.1, b.getMinLatitude(), 0.000001);
assertEquals(0.1, b.getMaxLatitude(), 0.000001);
c = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.5, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -116,7 +264,8 @@ public class GeoCircleTest {
assertEquals(-0.1, b.getMinLatitude(), 0.000001);
assertEquals(0.1, b.getMaxLatitude(), 0.000001);
c = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -125,7 +274,8 @@ public class GeoCircleTest {
assertEquals(-0.1, b.getMinLatitude(), 0.000001);
assertEquals(0.1, b.getMaxLatitude(), 0.000001);
c = new GeoCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -135,13 +285,15 @@ public class GeoCircleTest {
assertEquals(0.1, b.getMaxLatitude(), 0.000001);
// Horizontal circle cases
c = new GeoCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertTrue(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
assertEquals(Math.PI * 0.5 - 0.1, b.getMinLatitude(), 0.000001);
c = new GeoCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertTrue(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertTrue(b.checkNoBottomLatitudeBound());
@ -149,7 +301,8 @@ public class GeoCircleTest {
// Now do a somewhat tilted plane, facing different directions.
c = new GeoCircle(PlanetModel.SPHERE, 0.01, 0.0, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -159,7 +312,8 @@ public class GeoCircleTest {
assertEquals(0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(PlanetModel.SPHERE, 0.01, Math.PI, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -169,7 +323,8 @@ public class GeoCircleTest {
assertEquals(-Math.PI + 0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(PlanetModel.SPHERE, 0.01, Math.PI * 0.5, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -179,7 +334,8 @@ public class GeoCircleTest {
assertEquals(Math.PI * 0.5 + 0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(PlanetModel.SPHERE, 0.01, -Math.PI * 0.5, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -190,7 +346,8 @@ public class GeoCircleTest {
// Slightly tilted, PI/4 direction.
c = new GeoCircle(PlanetModel.SPHERE, 0.01, Math.PI * 0.25, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -200,7 +357,8 @@ public class GeoCircleTest {
assertEquals(Math.PI * 0.25 + 0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(PlanetModel.SPHERE, 0.01, -Math.PI * 0.25, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -210,7 +368,8 @@ public class GeoCircleTest {
assertEquals(-Math.PI * 0.25 + 0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(PlanetModel.SPHERE, -0.01, Math.PI * 0.25, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -220,7 +379,8 @@ public class GeoCircleTest {
assertEquals(Math.PI * 0.25 + 0.1, b.getRightLongitude(), 0.00001);
c = new GeoCircle(PlanetModel.SPHERE, -0.01, -Math.PI * 0.25, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());
@ -231,7 +391,8 @@ public class GeoCircleTest {
// Now do a somewhat tilted plane.
c = new GeoCircle(PlanetModel.SPHERE, 0.01, -0.5, 0.1);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());

View File

@ -70,7 +70,7 @@ public class GeoConvexPolygonTest {
@Test
public void testPolygonBounds() {
GeoConvexPolygon c;
Bounds b;
LatLonBounds b;
c = new GeoConvexPolygon(PlanetModel.SPHERE, -0.1, -0.5);
c.addPoint(0.0, -0.6, false);
@ -78,7 +78,8 @@ public class GeoConvexPolygonTest {
c.addPoint(0.0, -0.4, false);
c.done(false);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());

View File

@ -43,7 +43,9 @@ public class GeoModelTest {
assertTrue(circle.isWithin(northPole));
assertFalse(circle.isWithin(southPole));
assertFalse(circle.isWithin(point1));
Bounds bounds = circle.getBounds(null);
LatLonBounds bounds;
bounds = new LatLonBounds();
circle.getBounds(bounds);
assertTrue(bounds.checkNoLongitudeBound());
assertTrue(bounds.checkNoTopLatitudeBound());
assertFalse(bounds.checkNoBottomLatitudeBound());
@ -53,7 +55,8 @@ public class GeoModelTest {
assertTrue(circle.isWithin(point1));
assertFalse(circle.isWithin(northPole));
assertFalse(circle.isWithin(southPole));
bounds = circle.getBounds(null);
bounds = new LatLonBounds();
circle.getBounds(bounds);
assertFalse(bounds.checkNoTopLatitudeBound());
assertFalse(bounds.checkNoLongitudeBound());
assertFalse(bounds.checkNoBottomLatitudeBound());
@ -66,7 +69,8 @@ public class GeoModelTest {
assertTrue(circle.isWithin(point2));
assertFalse(circle.isWithin(northPole));
assertFalse(circle.isWithin(southPole));
bounds = circle.getBounds(null);
bounds = new LatLonBounds();
circle.getBounds(bounds);
assertFalse(bounds.checkNoLongitudeBound());
assertFalse(bounds.checkNoTopLatitudeBound());
assertFalse(bounds.checkNoBottomLatitudeBound());
@ -91,7 +95,8 @@ public class GeoModelTest {
assertFalse(bbox.isWithin(leftOutsidePoint));
final GeoPoint rightOutsidePoint = new GeoPoint(scaledModel, 0.5, 1.01);
assertFalse(bbox.isWithin(rightOutsidePoint));
final Bounds bounds = bbox.getBounds(null);
final LatLonBounds bounds = new LatLonBounds();
bbox.getBounds(bounds);
assertFalse(bounds.checkNoLongitudeBound());
assertFalse(bounds.checkNoTopLatitudeBound());
assertFalse(bounds.checkNoBottomLatitudeBound());

View File

@ -165,14 +165,54 @@ public class GeoPathTest {
@Test
public void testPathBounds() {
GeoPath c;
Bounds b;
LatLonBounds b;
XYZBounds xyzb;
GeoPoint point;
int relationship;
GeoArea area;
c = new GeoPath(PlanetModel.WGS84, 0.6894050545377601);
c.addPoint(-0.0788176065762948, 0.9431251741731624);
c.addPoint(0.510387871458147, 0.5327078872484678);
c.addPoint(-0.5624521609859962, 1.5398841746888388);
c.addPoint(-0.5025171434638661, -0.5895998642788894);
c.done();
point = new GeoPoint(PlanetModel.WGS84, 0.023652082107211682, 0.023131910152748437);
//System.err.println("Point.x = "+point.x+"; point.y="+point.y+"; point.z="+point.z);
assertTrue(c.isWithin(point));
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.WGS84,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
//System.err.println("minx="+xyzb.getMinimumX()+" maxx="+xyzb.getMaximumX()+" miny="+xyzb.getMinimumY()+" maxy="+xyzb.getMaximumY()+" minz="+xyzb.getMinimumZ()+" maxz="+xyzb.getMaximumZ());
//System.err.println("point.x="+point.x+" point.y="+point.y+" point.z="+point.z);
relationship = area.getRelationship(c);
assertTrue(relationship == GeoArea.WITHIN || relationship == GeoArea.OVERLAPS);
assertTrue(area.isWithin(point));
c = new GeoPath(PlanetModel.WGS84, 0.7766715171374766);
c.addPoint(-0.2751718361148076, -0.7786721269011477);
c.addPoint(0.5728375851539309, -1.2700115736820465);
c.done();
point = new GeoPoint(PlanetModel.WGS84, -0.01580760332365284, -0.03956004622490505);
assertTrue(c.isWithin(point));
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.WGS84,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
//System.err.println("minx="+xyzb.getMinimumX()+" maxx="+xyzb.getMaximumX()+" miny="+xyzb.getMinimumY()+" maxy="+xyzb.getMaximumY()+" minz="+xyzb.getMinimumZ()+" maxz="+xyzb.getMaximumZ());
//System.err.println("point.x="+point.x+" point.y="+point.y+" point.z="+point.z);
relationship = area.getRelationship(c);
assertTrue(relationship == GeoArea.WITHIN || relationship == GeoArea.OVERLAPS);
assertTrue(area.isWithin(point));
c = new GeoPath(PlanetModel.SPHERE, 0.1);
c.addPoint(-0.3, -0.3);
c.addPoint(0.3, 0.3);
c.done();
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());

View File

@ -121,9 +121,29 @@ public class GeoPolygonTest {
@Test
public void testPolygonBounds() {
GeoMembershipShape c;
Bounds b;
LatLonBounds b;
List<GeoPoint> points;
XYZBounds xyzb;
GeoPoint point;
GeoArea area;
// BKD failure
points = new ArrayList<GeoPoint>();
points.add(new GeoPoint(PlanetModel.WGS84, -0.36716183577912814, 1.4836349969188696));
points.add(new GeoPoint(PlanetModel.WGS84, 0.7846038240742979, -0.02743348424931823));
points.add(new GeoPoint(PlanetModel.WGS84, -0.7376479402362607, -0.5072961758807019));
points.add(new GeoPoint(PlanetModel.WGS84, -0.3760415907667887, 1.4970455334565513));
c = GeoPolygonFactory.makeGeoPolygon(PlanetModel.WGS84, points, 1);
point = new GeoPoint(PlanetModel.WGS84, -0.01580760332365284, -0.03956004622490505);
assertTrue(c.isWithin(point));
xyzb = new XYZBounds();
c.getBounds(xyzb);
area = GeoAreaFactory.makeGeoArea(PlanetModel.WGS84,
xyzb.getMinimumX(), xyzb.getMaximumX(), xyzb.getMinimumY(), xyzb.getMaximumY(), xyzb.getMinimumZ(), xyzb.getMaximumZ());
assertTrue(area.isWithin(point));
points = new ArrayList<GeoPoint>();
points.add(new GeoPoint(PlanetModel.SPHERE, -0.1, -0.5));
points.add(new GeoPoint(PlanetModel.SPHERE, 0.0, -0.6));
@ -132,7 +152,8 @@ public class GeoPolygonTest {
c = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, points, 0);
b = c.getBounds(null);
b = new LatLonBounds();
c.getBounds(b);
assertFalse(b.checkNoLongitudeBound());
assertFalse(b.checkNoTopLatitudeBound());
assertFalse(b.checkNoBottomLatitudeBound());

View File

@ -0,0 +1,221 @@
package org.apache.lucene.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.apache.lucene.util.LuceneTestCase;
import org.junit.Test;
public class XYZSolidTest extends LuceneTestCase {
@Test
public void testNonDegenerateRelationships() {
XYZSolid s;
GeoShape shape;
// Something bigger than the world
s = new XYZSolid(PlanetModel.SPHERE, -2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
// Any shape, except whole world, should be within.
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.WITHIN, s.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
// An XYZSolid represents a surface shape, which when larger than the world is in fact
// the entire world, so it should overlap the world.
assertEquals(GeoArea.OVERLAPS, s.getRelationship(shape));
// Something overlapping the world on only one side
s = new XYZSolid(PlanetModel.SPHERE, -2.0, 0.0, -2.0, 2.0, -2.0, 2.0);
// Some things should be disjoint...
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, s.getRelationship(shape));
// And, some things should be within...
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
assertEquals(GeoArea.WITHIN, s.getRelationship(shape));
// And, some things should overlap.
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
assertEquals(GeoArea.OVERLAPS, s.getRelationship(shape));
// Partial world should be contained by GeoWorld object...
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.CONTAINS, s.getRelationship(shape));
// Something inside the world
s = new XYZSolid(PlanetModel.SPHERE, -0.1, 0.1, -0.1, 0.1, -0.1, 0.1);
// All shapes should be disjoint
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, s.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.DISJOINT, s.getRelationship(shape));
}
@Test
public void testDegenerateRelationships() {
GeoArea solid;
GeoShape shape;
// Basic test of the factory method - non-degenerate
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, -2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
// Any shape, except whole world, should be within.
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.WITHIN, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
// An XYZSolid represents a surface shape, which when larger than the world is in fact
// the entire world, so it should overlap the world.
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
// Build a degenerate point, not on sphere
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
// disjoint with everything?
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
// Build a degenerate point that IS on the sphere
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0);
// inside everything that it touches?
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
// Build a shape degenerate in (x,y), which has no points on sphere
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, -0.1, 0.1);
// disjoint with everything?
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
// Build a shape degenerate in (x,y) which has one point on sphere
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, -0.1, 1.1);
// inside everything that it touches?
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
// Build a shape degenerate in (x,y) which has two points on sphere
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, -1.1, 1.1);
// inside everything that it touches?
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
// Build a shape degenerate in (x,z), which has no points on sphere
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 0.1, 0.0, 0.0);
// disjoint with everything?
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
// Build a shape degenerate in (x,z) which has one point on sphere
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 1.1, 0.0, 0.0);
// inside everything that it touches?
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
// Build a shape degenerate in (x,y) which has two points on sphere
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -1.1, 1.1, 0.0, 0.0);
// inside everything that it touches?
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
// MHL for y-z check
// Build a shape that is degenerate in x, which has zero points intersecting sphere
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 0.1, -0.1, 0.1);
// disjoint with everything?
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
// Build a shape that is degenerate in x, which has zero points intersecting sphere, second variation
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 0.1, 1.1, 1.2);
// disjoint with everything?
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
// Build a shape that is disjoint in X but intersects sphere in a complete circle
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -1.1, 1.1, -1.1, 1.1);
// inside everything that it touches?
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
// Build a shape that is disjoint in X but intersects sphere in a half circle in Y
solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 1.1, -1.1, 1.1);
// inside everything that it touches?
shape = new GeoWorld(PlanetModel.SPHERE);
assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
shape = new GeoCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
// MHL for degenerate Y
// MHL for degenerate Z
}
}