mirror of https://github.com/apache/lucene.git
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:
parent
21bd68a443
commit
03e5bcfa73
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -319,7 +319,7 @@ class RangeTreeWriter {
|
|||
long innerNodeCount = 1;
|
||||
|
||||
while (countPerLeaf > maxValuesInLeafNode) {
|
||||
countPerLeaf /= 2;
|
||||
countPerLeaf = (countPerLeaf+1)/2;
|
||||
innerNodeCount *= 2;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 <= 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 + ")";
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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 + ")";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 + ")";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()+"]";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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+"}";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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+"}";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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+"}";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()+"}";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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+"}";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()+"}";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()+"}";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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+"}";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue