All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.lucene.spatial3d.geom.LatLonBounds Maven / Gradle / Ivy

There is a newer version: 10.0.0
Show newest version
/*
 * 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;
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy