<commit_before>/*
 * 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.Map;
import java.util.List;
import java.util.HashMap;
import java.util.ArrayList;

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

/**
 * Circular area with a center and radius.
 *
 * @lucene.experimental
 */
class GeoExactCircle extends GeoBaseCircle {
  /** Center of circle */
  protected final GeoPoint center;
  /** Cutoff angle of circle (not quite the same thing as radius) */
  protected final double cutoffAngle;
  /** Actual accuracy */
  protected final double actualAccuracy;
  /** Planes describing the circle */
  protected final List<SidedPlane> circlePlanes;
  /** Bounds for the planes */
  protected final Map<SidedPlane, Membership> eitherBounds;
  /** A point that is on the world and on the circle plane */
  protected final GeoPoint[] edgePoints;
  /** The set of notable points for each edge */
  protected final List<GeoPoint[]> notableEdgePoints;
  /** Notable points for a circle -- there aren't any */
  protected static final GeoPoint[] circlePoints = new GeoPoint[0];

  /** Constructor.
   *@param planetModel is the planet model.
   *@param lat is the center latitude.
   *@param lon is the center longitude.
   *@param cutoffAngle is the cutoff angle for the circle.
   *@param accuracy is the allowed error value.
   */
  public GeoExactCircle(final PlanetModel planetModel, final double lat, final double lon, final double cutoffAngle, 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 (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);
    this.cutoffAngle = cutoffAngle;

    if (accuracy < Vector.MINIMUM_RESOLUTION) {
      actualAccuracy = Vector.MINIMUM_RESOLUTION;
    } else {
      actualAccuracy = accuracy;
    }
    
    if (Math.abs(cutoffAngle - Math.PI) < Vector.MINIMUM_RESOLUTION) {
      // Circle is the whole world
      this.circlePlanes = null;
      this.eitherBounds = null;
      this.edgePoints = new GeoPoint[0];
      this.notableEdgePoints = null;
    } else if (planetModel.c == planetModel.ab) {
      // Sphere
      this.eitherBounds = null;
      this.circlePlanes = new ArrayList<>(1);
      // Compute two points on the circle, with the right angle from the center.  We'll use these
      // to obtain the perpendicular plane to the circle.
      double upperLat = lat + cutoffAngle;
      double upperLon = lon;
      if (upperLat > Math.PI * 0.5) {
        upperLon += Math.PI;
        if (upperLon > Math.PI)
          upperLon -= 2.0 * Math.PI;
        upperLat = Math.PI - upperLat;
      }
      double lowerLat = lat - cutoffAngle;
      double lowerLon = lon;
      if (lowerLat < -Math.PI * 0.5) {
        lowerLon += Math.PI;
        if (lowerLon > Math.PI)
          lowerLon -= 2.0 * Math.PI;
        lowerLat = -Math.PI - lowerLat;
      }
      final GeoPoint upperPoint = new GeoPoint(planetModel, upperLat, upperLon);
      final GeoPoint lowerPoint = new GeoPoint(planetModel, lowerLat, lowerLon);
      // Construct normal plane
      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.
      final SidedPlane circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(center, normalPlane, upperPoint, lowerPoint);
      if (circlePlane == null)
        throw new IllegalArgumentException("Couldn't construct circle plane, probably too small?  Cutoff angle = "+cutoffAngle+"; upperPoint = "+upperPoint+"; lowerPoint = "+lowerPoint);
      circlePlanes.add(circlePlane);
      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};
      this.notableEdgePoints = null;
    } else {
      this.circlePlanes = new ArrayList<>();
      this.notableEdgePoints = new ArrayList<>();
      this.eitherBounds = new HashMap<>();
      // 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, cutoffAngle, Math.PI * 0.5);
      final GeoPoint southPoint = planetModel.surfacePointOnBearing(center, cutoffAngle, Math.PI * 1.5);
      final GeoPoint eastPoint = planetModel.surfacePointOnBearing(center, cutoffAngle, 0.0);
      final GeoPoint westPoint = planetModel.surfacePointOnBearing(center, cutoffAngle, Math.PI);
      
      this.edgePoints = new GeoPoint[]{northPoint};
      
      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, 0.0, westPoint, Math.PI, northPoint, Math.PI * 0.5));
        slices.add(new ApproximationSlice(center, westPoint, Math.PI, eastPoint, 0.0, southPoint, Math.PI * 1.5));
      } else {
        // z will be less than x or y, so ellipse is shorter than it is tall
        slices.add(new ApproximationSlice(center, northPoint, Math.PI * 0.5, southPoint, Math.PI * 1.5, eastPoint, 0.0));
        slices.add(new ApproximationSlice(center, southPoint, Math.PI * 1.5, northPoint, Math.PI * 0.5, westPoint, Math.PI));
      }
      
      // 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;<commit_msg>LUCENE-7970: Correct a misinterpretation of bearing direction<commit_after>/*
 * 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.Map;
import java.util.List;
import java.util.HashMap;
import java.util.ArrayList;

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

/**
 * Circular area with a center and radius.
 *
 * @lucene.experimental
 */
class GeoExactCircle extends GeoBaseCircle {
  /** Center of circle */
  protected final GeoPoint center;
  /** Cutoff angle of circle (not quite the same thing as radius) */
  protected final double cutoffAngle;
  /** Actual accuracy */
  protected final double actualAccuracy;
  /** Planes describing the circle */
  protected final List<SidedPlane> circlePlanes;
  /** Bounds for the planes */
  protected final Map<SidedPlane, Membership> eitherBounds;
  /** A point that is on the world and on the circle plane */
  protected final GeoPoint[] edgePoints;
  /** The set of notable points for each edge */
  protected final List<GeoPoint[]> notableEdgePoints;
  /** Notable points for a circle -- there aren't any */
  protected static final GeoPoint[] circlePoints = new GeoPoint[0];

  /** Constructor.
   *@param planetModel is the planet model.
   *@param lat is the center latitude.
   *@param lon is the center longitude.
   *@param cutoffAngle is the cutoff angle for the circle.
   *@param accuracy is the allowed error value.
   */
  public GeoExactCircle(final PlanetModel planetModel, final double lat, final double lon, final double cutoffAngle, 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 (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);
    this.cutoffAngle = cutoffAngle;

    if (accuracy < Vector.MINIMUM_RESOLUTION) {
      actualAccuracy = Vector.MINIMUM_RESOLUTION;
    } else {
      actualAccuracy = accuracy;
    }
    
    if (Math.abs(cutoffAngle - Math.PI) < Vector.MINIMUM_RESOLUTION) {
      // Circle is the whole world
      this.circlePlanes = null;
      this.eitherBounds = null;
      this.edgePoints = new GeoPoint[0];
      this.notableEdgePoints = null;
    } else if (planetModel.c == planetModel.ab) {
      // Sphere
      this.eitherBounds = null;
      this.circlePlanes = new ArrayList<>(1);
      // Compute two points on the circle, with the right angle from the center.  We'll use these
      // to obtain the perpendicular plane to the circle.
      double upperLat = lat + cutoffAngle;
      double upperLon = lon;
      if (upperLat > Math.PI * 0.5) {
        upperLon += Math.PI;
        if (upperLon > Math.PI)
          upperLon -= 2.0 * Math.PI;
        upperLat = Math.PI - upperLat;
      }
      double lowerLat = lat - cutoffAngle;
      double lowerLon = lon;
      if (lowerLat < -Math.PI * 0.5) {
        lowerLon += Math.PI;
        if (lowerLon > Math.PI)
          lowerLon -= 2.0 * Math.PI;
        lowerLat = -Math.PI - lowerLat;
      }
      final GeoPoint upperPoint = new GeoPoint(planetModel, upperLat, upperLon);
      final GeoPoint lowerPoint = new GeoPoint(planetModel, lowerLat, lowerLon);
      // Construct normal plane
      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.
      final SidedPlane circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(center, normalPlane, upperPoint, lowerPoint);
      if (circlePlane == null)
        throw new IllegalArgumentException("Couldn't construct circle plane, probably too small?  Cutoff angle = "+cutoffAngle+"; upperPoint = "+upperPoint+"; lowerPoint = "+lowerPoint);
      circlePlanes.add(circlePlane);
      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};
      this.notableEdgePoints = null;
    } else {
      this.circlePlanes = new ArrayList<>();
      this.notableEdgePoints = new ArrayList<>();
      this.eitherBounds = new HashMap<>();
      // 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, cutoffAngle, 0.0);
      final GeoPoint southPoint = planetModel.surfacePointOnBearing(center, cutoffAngle, Math.PI);
      final GeoPoint eastPoint = planetModel.surfacePointOnBearing(center, cutoffAngle, Math.PI * 0.5);
      final GeoPoint westPoint = planetModel.surfacePointOnBearing(center, cutoffAngle, Math.PI * 1.5);
      
      this.edgePoints = new GeoPoint[]{northPoint};
      
      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));
        slices.add(new ApproximationSlice(center, westPoint, Math.PI * 1.5, eastPoint, Math.PI * 0.5, southPoint, Math.PI));
      } else {
        // z will be less than x or y, so ellipse is shorter than it is tall
        slices.add(new ApproximationSlice(center, northPoint, Math.PI * 2.0, southPoint, Math.PI, eastPoint, Math.PI * 0.5));
        slices.add(new ApproximationSlice(center, southPoint, Math.PI, northPoint, 0.0, westPoint, Math.PI * 1.5));
      }
      
      // 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;
{'fraction_non_alphanumeric': 0.09117510141650595, 'fraction_numerical': 0.007581299461328722, 'mean_word_length': 3.2408347433728144, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2279817', 'n_tokens_mistral': 4361, 'n_tokens_neox': 4182, 'n_words': 1510}