/*
 * 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;

import java.util.List;
import java.util.ArrayList;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

Circular area with a center and a radius that represents the surface distance to the center. The circle is divided in sectors where the circle edge is approximated using Vincenty formulae. The higher is the precision the more sectors are needed to describe the shape and therefore a penalty in performance.
@lucene.experimental
/** * Circular area with a center and a radius that represents the surface distance to the center. * The circle is divided in sectors where the circle edge is approximated using Vincenty formulae. * The higher is the precision the more sectors are needed to describe the shape and therefore a penalty * in performance. * * @lucene.experimental */
class GeoExactCircle extends GeoBaseCircle {
Center of circle
/** Center of circle */
protected final GeoPoint center;
Radius of circle
/** Radius of circle */
protected final double radius;
Actual accuracy
/** Actual accuracy */
protected final double actualAccuracy;
A point that is on the edge of the circle
/** A point that is on the edge of the circle */
protected final GeoPoint[] edgePoints;
Slices of the circle
/** Slices of the circle */
protected final List<CircleSlice> circleSlices;
Constructor.
Params:
  • planetModel – is the planet model.
  • lat – is the center latitude.
  • lon – is the center longitude.
  • radius – is the surface radius for the circle.
  • accuracy – is the allowed error value (linear distance). Maximum accuracy is 1e-12.
/** Constructor. *@param planetModel is the planet model. *@param lat is the center latitude. *@param lon is the center longitude. *@param radius is the surface radius for the circle. *@param accuracy is the allowed error value (linear distance). Maximum accuracy is 1e-12. */
public GeoExactCircle(final PlanetModel planetModel, final double lat, final double lon, final double radius, final double accuracy) { super(planetModel); if (lat < -Math.PI * 0.5 || lat > Math.PI * 0.5) throw new IllegalArgumentException("Latitude out of bounds"); if (lon < -Math.PI || lon > Math.PI) throw new IllegalArgumentException("Longitude out of bounds"); if (radius < 0.0) throw new IllegalArgumentException("Radius out of bounds"); if (radius < Vector.MINIMUM_RESOLUTION) throw new IllegalArgumentException("Radius cannot be effectively zero"); if (planetModel.minimumPoleDistance - radius < Vector.MINIMUM_RESOLUTION) throw new IllegalArgumentException("Radius out of bounds. It cannot be bigger than " + planetModel.minimumPoleDistance + " for this planet model"); this.center = new GeoPoint(planetModel, lat, lon); this.radius = radius; if (accuracy < Vector.MINIMUM_RESOLUTION) { actualAccuracy = Vector.MINIMUM_RESOLUTION; } else { actualAccuracy = accuracy; } // We construct approximation planes until we have a low enough error estimate final List<ApproximationSlice> slices = new ArrayList<>(100); // Construct four cardinal points, and then we'll build the first two planes final GeoPoint northPoint = planetModel.surfacePointOnBearing(center, radius, 0.0); final GeoPoint southPoint = planetModel.surfacePointOnBearing(center, radius, Math.PI); final GeoPoint eastPoint = planetModel.surfacePointOnBearing(center, radius, Math.PI * 0.5); final GeoPoint westPoint = planetModel.surfacePointOnBearing(center, radius, Math.PI * 1.5); final GeoPoint edgePoint; if (planetModel.c > planetModel.ab) { // z can be greater than x or y, so ellipse is longer in height than width slices.add(new ApproximationSlice(center, eastPoint, Math.PI * 0.5, westPoint, Math.PI * -0.5, northPoint, 0.0, true)); slices.add(new ApproximationSlice(center, westPoint, Math.PI * 1.5, eastPoint, Math.PI * 0.5, southPoint, Math.PI, true)); edgePoint = eastPoint; } else { // z will be less than x or y, so ellipse is shorter than it is tall slices.add(new ApproximationSlice(center, northPoint, 0.0, southPoint, Math.PI, eastPoint, Math.PI * 0.5, true)); slices.add(new ApproximationSlice(center, southPoint, Math.PI, northPoint, Math.PI * 2.0, westPoint, Math.PI * 1.5, true)); edgePoint = northPoint; } //System.out.println("Edgepoint = " + edgePoint); this.circleSlices = new ArrayList<>(); // Now, iterate over slices until we have converted all of them into safe SidedPlanes. while (slices.size() > 0) { // Peel off a slice from the back final ApproximationSlice thisSlice = slices.remove(slices.size()-1); // Assess it to see if it is OK as it is, or needs to be split. // To do this, we need to look at the part of the circle that will have the greatest error. // We will need to compute bearing points for these. final double interpPoint1Bearing = (thisSlice.point1Bearing + thisSlice.middlePointBearing) * 0.5; final GeoPoint interpPoint1 = planetModel.surfacePointOnBearing(center, radius, interpPoint1Bearing); final double interpPoint2Bearing = (thisSlice.point2Bearing + thisSlice.middlePointBearing) * 0.5; final GeoPoint interpPoint2 = planetModel.surfacePointOnBearing(center, radius, interpPoint2Bearing); // Is this point on the plane? (that is, is the approximation good enough?) if (!thisSlice.mustSplit && Math.abs(thisSlice.plane.evaluate(interpPoint1)) < actualAccuracy && Math.abs(thisSlice.plane.evaluate(interpPoint2)) < actualAccuracy) { circleSlices.add(new CircleSlice(thisSlice.plane, thisSlice.endPoint1, thisSlice.endPoint2, center, thisSlice.middlePoint)); //assert thisSlice.plane.isWithin(center); } else { // Split the plane into two, and add it back to the end slices.add(new ApproximationSlice(center, thisSlice.endPoint1, thisSlice.point1Bearing, thisSlice.middlePoint, thisSlice.middlePointBearing, interpPoint1, interpPoint1Bearing, false)); slices.add(new ApproximationSlice(center, thisSlice.middlePoint, thisSlice.middlePointBearing, thisSlice.endPoint2, thisSlice.point2Bearing, interpPoint2, interpPoint2Bearing, false)); } } this.edgePoints = new GeoPoint[]{edgePoint}; //System.out.println("Is edgepoint within? "+isWithin(edgePoint)); }
Constructor for deserialization.
Params:
  • planetModel – is the planet model.
  • inputStream – is the input stream.
/** * Constructor for deserialization. * @param planetModel is the planet model. * @param inputStream is the input stream. */
public GeoExactCircle(final PlanetModel planetModel, final InputStream inputStream) throws IOException { this(planetModel, SerializableObject.readDouble(inputStream), SerializableObject.readDouble(inputStream), SerializableObject.readDouble(inputStream), SerializableObject.readDouble(inputStream)); } @Override public void write(final OutputStream outputStream) throws IOException { SerializableObject.writeDouble(outputStream, center.getLatitude()); SerializableObject.writeDouble(outputStream, center.getLongitude()); SerializableObject.writeDouble(outputStream, radius); SerializableObject.writeDouble(outputStream, actualAccuracy); } @Override public double getRadius() { return radius; } @Override public GeoPoint getCenter() { return center; } @Override protected double distance(final DistanceStyle distanceStyle, final double x, final double y, final double z) { return distanceStyle.computeDistance(this.center, x, y, z); } @Override protected void distanceBounds(final Bounds bounds, final DistanceStyle distanceStyle, final double distanceValue) { // TBD: Compute actual bounds based on distance getBounds(bounds); } @Override protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) { double outsideDistance = Double.POSITIVE_INFINITY; for (final CircleSlice slice : circleSlices) { final double distance = distanceStyle.computeDistance(planetModel, slice.circlePlane, x, y, z, slice.plane1, slice.plane2); if (distance < outsideDistance) { outsideDistance = distance; } } return outsideDistance; } @Override public boolean isWithin(final double x, final double y, final double z) { for (final CircleSlice slice : circleSlices) { if (slice.circlePlane.isWithin(x, y, z) && slice.plane1.isWithin(x, y, z) && slice.plane2.isWithin(x, y, z)) { return true; } } return false; } @Override public GeoPoint[] getEdgePoints() { return edgePoints; } @Override public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) { for (final CircleSlice slice : circleSlices) { if (slice.circlePlane.intersects(planetModel, p, notablePoints, slice.notableEdgePoints, bounds, slice.plane1, slice.plane2)) { return true; } } return false; } @Override public boolean intersects(GeoShape geoShape) { for (final CircleSlice slice : circleSlices) { if (geoShape.intersects(slice.circlePlane, slice.notableEdgePoints, slice.plane1, slice.plane2)) { return true; } } return false; } @Override public void getBounds(Bounds bounds) { super.getBounds(bounds); bounds.addPoint(center); for (final CircleSlice slice : circleSlices) { bounds.addPlane(planetModel, slice.circlePlane, slice.plane1, slice.plane2); for (final GeoPoint point : slice.notableEdgePoints) { bounds.addPoint(point); } } } @Override public boolean equals(Object o) { if (!(o instanceof GeoExactCircle)) return false; GeoExactCircle other = (GeoExactCircle) o; return super.equals(other) && other.center.equals(center) && other.radius == radius && other.actualAccuracy == actualAccuracy; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + center.hashCode(); long temp = Double.doubleToLongBits(radius); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(actualAccuracy); result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public String toString() { return "GeoExactCircle: {planetmodel=" + planetModel+", center=" + center + ", radius=" + radius + "(" + radius * 180.0 / Math.PI + "), accuracy=" + actualAccuracy + "}"; }
A temporary description of a section of circle.
/** A temporary description of a section of circle. */
protected static class ApproximationSlice { public final SidedPlane plane; public final GeoPoint endPoint1; public final double point1Bearing; public final GeoPoint endPoint2; public final double point2Bearing; public final GeoPoint middlePoint; public final double middlePointBearing; public final boolean mustSplit; public ApproximationSlice(final GeoPoint center, final GeoPoint endPoint1, final double point1Bearing, final GeoPoint endPoint2, final double point2Bearing, final GeoPoint middlePoint, final double middlePointBearing, final boolean mustSplit) { this.endPoint1 = endPoint1; this.point1Bearing = point1Bearing; this.endPoint2 = endPoint2; this.point2Bearing = point2Bearing; this.middlePoint = middlePoint; this.middlePointBearing = middlePointBearing; this.mustSplit = mustSplit; // Construct the plane going through the three given points this.plane = SidedPlane.constructNormalizedThreePointSidedPlane(center, endPoint1, endPoint2, middlePoint); if (this.plane == null) { throw new IllegalArgumentException("Either circle is too small or accuracy is too high; could not construct a plane with endPoint1="+endPoint1+" bearing "+point1Bearing+", endPoint2="+endPoint2+" bearing "+point2Bearing+", middle="+middlePoint+" bearing "+middlePointBearing); } if (this.plane.isWithin(-center.x, -center.y, -center.z)) { //Plane is bogus, we cannot build the circle throw new IllegalArgumentException("Could not construct a valid plane for this planet model with endPoint1="+endPoint1+" bearing "+point1Bearing+", endPoint2="+endPoint2+" bearing "+point2Bearing+", middle="+middlePoint+" bearing "+middlePointBearing); } } @Override public String toString() { return "{end point 1 = " + endPoint1 + " bearing 1 = "+point1Bearing + " end point 2 = " + endPoint2 + " bearing 2 = " + point2Bearing + " middle point = " + middlePoint + " middle bearing = " + middlePointBearing + "}"; } }
A description of a section of circle.
/** A description of a section of circle. */
protected static class CircleSlice { final GeoPoint[] notableEdgePoints; public final SidedPlane circlePlane; public final SidedPlane plane1; public final SidedPlane plane2; public CircleSlice(SidedPlane circlePlane, GeoPoint endPoint1, GeoPoint endPoint2, GeoPoint center, GeoPoint check) { this.circlePlane = circlePlane; this.plane1 = new SidedPlane(check, endPoint1, center); this.plane2 = new SidedPlane(check, endPoint2, center); this.notableEdgePoints = new GeoPoint[] {endPoint1, endPoint2}; } @Override public String toString() { return "{circle plane = " + circlePlane + " plane 1 = "+plane1 + " plane 2 = " + plane2 + " notable edge points = " + notableEdgePoints + "}"; } } }