code: /*******************************************************************************
   * Copyright (c) 2015 Voyager Search and MITRE
   * All rights reserved. This program and the accompanying materials
   * are made available under the terms of the Apache License, Version 2.0 which
   * accompanies this distribution and is available at
   *    http://www.apache.org/licenses/LICENSE-2.0.txt
   ******************************************************************************/
  
  package org.locationtech.spatial4j.shape.impl;
  
  import org.locationtech.spatial4j.context.SpatialContext;
  import org.locationtech.spatial4j.shape.BaseShape;
  import org.locationtech.spatial4j.shape.Circle;
  import org.locationtech.spatial4j.shape.Point;
  import org.locationtech.spatial4j.shape.Rectangle;
  import org.locationtech.spatial4j.shape.Shape;
  import org.locationtech.spatial4j.shape.SpatialRelation;
  
  /**
   * A circle, also known as a point-radius, based on a {@link
   * org.locationtech.spatial4j.distance.DistanceCalculator} which does all the work. This
   * implementation should work for both cartesian 2D and geodetic sphere
   * surfaces.
   */
  public class CircleImpl extends BaseShape<SpatialContext> implements Circle {
  
    protected final Point point;
    protected double radiusDEG;
  
    // calculated & cached
    protected Rectangle enclosingBox;
  
    public CircleImpl(Point p, double radiusDEG, SpatialContext ctx) {
      super(ctx);
      //We assume any validation of params already occurred (including bounding dist)
      this.point = p;
      this.radiusDEG = point.isEmpty() ? Double.NaN : radiusDEG;
      this.enclosingBox = point.isEmpty() ? ctx.makeRectangle(Double.NaN, Double.NaN, Double.NaN, Double.NaN) :
        ctx.getDistCalc().calcBoxByDistFromPt(point, this.radiusDEG, ctx, null);
    }
  
    @Override
    public void reset(double x, double y, double radiusDEG) {
      assert ! isEmpty();
      point.reset(x, y);
      this.radiusDEG = radiusDEG;
      this.enclosingBox = ctx.getDistCalc().calcBoxByDistFromPt(point, this.radiusDEG, ctx, enclosingBox);
    }
  
    @Override
    public boolean isEmpty() {
      return point.isEmpty();
    }
  
    @Override
    public Point getCenter() {
      return point;
    }
  
    @Override
    public double getRadius() {
      return radiusDEG;
    }
  
    @Override
    public double getArea(SpatialContext ctx) {
      if (ctx == null) {
        return Math.PI * radiusDEG * radiusDEG;
      } else {
        return ctx.getDistCalc().area(this);
      }
    }
  
    @Override
    public Circle getBuffered(double distance, SpatialContext ctx) {
      return ctx.makeCircle(point, distance + radiusDEG);
    }
  
    public boolean contains(double x, double y) {
      return ctx.getDistCalc().within(point, x, y, radiusDEG);
    }
  
    @Override
    public boolean hasArea() {
      return radiusDEG > 0;
    }
  
    /**
     * Note that the bounding box might contain a minX that is &gt; maxX, due to WGS84 anti-meridian.
     */
    @Override
    public Rectangle getBoundingBox() {
      return enclosingBox;
    }
  
    @Override
    public SpatialRelation relate(Shape other) {
  //This shortcut was problematic in testing due to distinctions of CONTAINS/WITHIN for no-area shapes (lines, points).
  //    if (distance == 0) {
  //      return point.relate(other,ctx).intersects() ? SpatialRelation.WITHIN : SpatialRelation.DISJOINT;
  //    }
      if (isEmpty() || other.isEmpty())
        return SpatialRelation.DISJOINT;
      if (other instanceof Point) {
        return relate((Point) other);
      }
      if (other instanceof Rectangle) {
        return relate((Rectangle) other);
      }
      if (other instanceof Circle) {
        return relate((Circle) other);
      }
      return other.relate(this).transpose();
    }
  
    public SpatialRelation relate(Point point) {
      return contains(point.getX(),point.getY()) ? SpatialRelation.CONTAINS : SpatialRelation.DISJOINT;
    }
  
    public SpatialRelation relate(Rectangle r) {
      //Note: Surprisingly complicated!
  
      //--We start by leveraging the fact we have a calculated bbox that is "cheaper" than use of DistanceCalculator.
      final SpatialRelation bboxSect = enclosingBox.relate(r);
      if (bboxSect == SpatialRelation.DISJOINT || bboxSect == SpatialRelation.WITHIN)
        return bboxSect;
      else if (bboxSect == SpatialRelation.CONTAINS && enclosingBox.equals(r))//nasty identity edge-case
        return SpatialRelation.WITHIN;
      //bboxSect is INTERSECTS or CONTAINS
      //The result can be DISJOINT, CONTAINS, or INTERSECTS (not WITHIN)
  
      return relateRectanglePhase2(r, bboxSect);
    }
  
    protected SpatialRelation relateRectanglePhase2(final Rectangle r, SpatialRelation bboxSect) {
      // DOES NOT WORK WITH GEO CROSSING DATELINE OR WORLD-WRAP. Other methods handle such cases.
  
      //At this point, the only thing we are certain of is that circle is *NOT* WITHIN r, since the
      // bounding box of a circle MUST be within r for the circle to be within r.
  
      //Quickly determine if they are DISJOINT or not.
      // Find the closest & farthest point to the circle within the rectangle
      final double closestX, farthestX;
      final double xAxis = getXAxis();
      if (xAxis < r.getMinX()) {
        closestX = r.getMinX();
        farthestX = r.getMaxX();
      } else if (xAxis > r.getMaxX()) {
        closestX = r.getMaxX();
        farthestX = r.getMinX();
      } else {
        closestX = xAxis; //we don't really use this value but to check this condition
        farthestX = r.getMaxX() - xAxis > xAxis - r.getMinX() ? r.getMaxX() : r.getMinX();
      }
  
      final double closestY, farthestY;
      final double yAxis = getYAxis();
      if (yAxis < r.getMinY()) {
        closestY = r.getMinY();
        farthestY = r.getMaxY();
      } else if (yAxis > r.getMaxY()) {
        closestY = r.getMaxY();
        farthestY = r.getMinY();
      } else {
        closestY = yAxis; //we don't really use this value but to check this condition
        farthestY = r.getMaxY() - yAxis > yAxis - r.getMinY() ? r.getMaxY() : r.getMinY();
      }
  
      //If r doesn't overlap an axis, then could be disjoint. Test closestXY
      if (xAxis != closestX && yAxis != closestY) {
        if (!contains(closestX, closestY))
          return SpatialRelation.DISJOINT;
      } // else CAN'T be disjoint if spans axis because earlier bbox check ruled that out
  
      //Now, we know it's *NOT* DISJOINT and it's *NOT* WITHIN either.
      // Does circle CONTAINS r or simply intersect it?
  
      //If circle contains r, then its bbox MUST also CONTAIN r.
      if (bboxSect != SpatialRelation.CONTAINS)
        return SpatialRelation.INTERSECTS;
  
      //If the farthest point of r away from the center of the circle is contained, then all of r is
      // contained.
      if (!contains(farthestX, farthestY))
        return SpatialRelation.INTERSECTS;
  
      //geodetic detection of farthest Y when rect crosses x axis can't be reliably determined, so
      // check other corner too, which might actually be farthest
      if (point.getY() != getYAxis()) {//geodetic
        if (yAxis == closestY) {//r crosses north to south over x axis (confusing)
          double otherY = (farthestY == r.getMaxY() ? r.getMinY() : r.getMaxY());
          if (!contains(farthestX, otherY))
            return SpatialRelation.INTERSECTS;
        }
      }
     
      return SpatialRelation.CONTAINS;
    }
  
    /**
     * The <code>Y</code> coordinate of where the circle axis intersect.
     */
    protected double getYAxis() {
      return point.getY();
    }
  
    /**
     * The <code>X</code> coordinate of where the circle axis intersect.
     */
    protected double getXAxis() {
      return point.getX();
    }
  
    public SpatialRelation relate(Circle circle) {
      double crossDist = ctx.getDistCalc().distance(point, circle.getCenter());
      double aDist = radiusDEG, bDist = circle.getRadius();
      if (crossDist > aDist + bDist)
        return SpatialRelation.DISJOINT;
      if (crossDist < aDist && crossDist + bDist <= aDist)
        return SpatialRelation.CONTAINS;
      if (crossDist < bDist && crossDist + aDist <= bDist)
        return SpatialRelation.WITHIN;
  
      return SpatialRelation.INTERSECTS;
    }
  
    @Override
    public String toString() {
      return "Circle(" + point + ", d=" + radiusDEG + "°)";
    }
  
    @Override
    public boolean equals(Object obj) {
      return equals(this,obj);
    }
  
    /**
     * All {@link Circle} implementations should use this definition of {@link Object#equals(Object)}.
     */
    public static boolean equals(Circle thiz, Object o) {
      assert thiz != null;
      if (thiz == o) return true;
      if (!(o instanceof Circle)) return false;
  
      Circle circle = (Circle) o;
  
      if (!thiz.getCenter().equals(circle.getCenter())) return false;
      if (Double.compare(circle.getRadius(), thiz.getRadius()) != 0) return false;
  
      return true;
    }
  
    @Override
    public int hashCode() {
      return hashCode(this);
    }
  
    /**
     * All {@link Circle} implementations should use this definition of {@link Object#hashCode()}.
     */
    public static int hashCode(Circle thiz) {
      int result;
      long temp;
      result = thiz.getCenter().hashCode();
      temp = thiz.getRadius() != +0.0d ? Double.doubleToLongBits(thiz.getRadius()) : 0L;
      result = 31 * result + (int) (temp ^ (temp >>> 32));
      return result;
    }
  }
  