| /* |
| * 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. |
| */ |
| package org.apache.lucene.spatial3d.geom; |
| |
| /** |
| * 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 */ |
| private boolean noLongitudeBound = false; |
| /** Set to true if no top latitude bound can be stated */ |
| private boolean noTopLatitudeBound = false; |
| /** Set to true if no bottom latitude bound can be stated */ |
| private boolean noBottomLatitudeBound = false; |
| |
| /** If non-null, the minimum latitude bound */ |
| private Double minLatitude = null; |
| /** If non-null, the maximum latitude bound */ |
| private 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 */ |
| private Double leftLongitude = null; |
| /** If non-null, the right longitude bound */ |
| private 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 addIntersection(final PlanetModel planetModel, final Plane plane1, final Plane plane2, final Membership... bounds) { |
| plane1.recordBounds(planetModel, this, plane2, bounds); |
| 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; |
| } |
| |
| @Override |
| public Bounds noBound(final PlanetModel planetModel) { |
| return noLongitudeBound().noTopLatitudeBound().noBottomLatitudeBound(); |
| } |
| |
| // Protected methods |
| |
| /** Update latitude bound. |
| *@param latitude is the latitude. |
| */ |
| private 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. |
| */ |
| private 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; |
| } |
| } |
| |
| } |