001    package edu.nrao.sss.geom;
002    
003    import java.awt.Rectangle;
004    import java.awt.Shape;
005    import java.awt.geom.AffineTransform;
006    import java.awt.geom.Ellipse2D;
007    import java.awt.geom.PathIterator;
008    import java.awt.geom.Point2D;
009    import java.awt.geom.Rectangle2D;
010    
011    /**
012     * A circle described by a central point and radial length.
013     * This circle class fits in with java's {@link java.awt.geom 2D} package.
014     * <p>
015     * <b>Version Info:</b>
016     * <table style="margin-left:2em">
017     *   <tr><td>$Revision: 797 $</td></tr>
018     *   <tr><td>$Date: 2007-08-08 16:48:50 -0600 (Wed, 08 Aug 2007) $</td></tr>
019     *   <tr><td>$Author: dharland $ (last person to modify)</td></tr>
020     * </table></p>
021     * 
022     * @author David M. Harland
023     * @since 2007-08-07
024     */
025    public class Circle
026      implements Shape, Cloneable
027    {
028      private Point2D   center;
029      private double    radius;
030      private Ellipse2D ellipse;
031      
032      /**
033       * Creates a new circle, centered at the origin, with the given radius.
034       * 
035       * @param radius the radius of this circle.  The units are arbitrary,
036       *               but are often taken to be pixels.
037       */
038      public Circle(double radius)
039      {
040        this(new Point2D.Double(), radius);
041      }
042      
043      /**
044       * Creates a new circle with the given center and radius.
045       * 
046       * @param center the center of this circle.
047       * 
048       * @param radius the radius of this circle.  The units are arbitrary,
049       *               but are often taken to be pixels.  If this value
050       *               is infinite, negative, or not a number, the radius
051       *               will be set to one.
052       */
053      public Circle(Point2D center, double radius)
054      {
055        this.center = (center == null) ? new Point2D.Double()
056                                       : (Point2D)center.clone();
057        
058        this.radius = radiusIsValid(radius) ? radius : 1.0;
059    
060        ellipse = new Ellipse2D.Double();
061        
062        updateEllipse();
063      }
064      
065      /** Updates the ellipse delegate to match this circle. */
066      private void updateEllipse()
067      {
068        double diameter = 2.0 * radius;
069        
070        ellipse.setFrame(center.getX() - radius,
071                         center.getY() - radius, diameter, diameter);
072      }
073      
074      //============================================================================
075      // 
076      //============================================================================
077      
078      /** Returns <i>true</i> if newRadius is non-negative and finite. */
079      private boolean radiusIsValid(double newRadius)
080      {
081        return (newRadius >= 0.0) && !Double.isInfinite(newRadius)
082                                  && !Double.isNaN(newRadius);
083      }
084      
085      /**
086       * Sets the radius of this circle.
087       * If {@code newRadius} is negative, infinite, or not a number, an
088       * <tt>IllegalArgumentException</tt> is thrown.
089       * 
090       * @param newRadius the new radius of this circle.
091       */
092      public void setRadius(double newRadius)
093      {
094        if (radiusIsValid(newRadius))
095        {
096          radius = newRadius;
097        }
098        else
099        {
100          throw new IllegalArgumentException(
101            "Tried to set radius to " + newRadius +
102            ".  Radius must be a finite non-negative number.");
103        }
104      }
105      
106      /**
107       * Returns the radius of this circle.
108       * The units are arbitrary, but are often taken to be pixels.
109       * @return the radius of this circle.
110       */
111      public double getRadius()  { return radius; }
112      
113      /**
114       * Returns the diameter of this circle.
115       * The units are arbitrary, but are often taken to be pixels.
116       * @return the diameter of this circle.
117       */
118      public double getDiameter()  { return 2.0 * radius; }
119      
120      /**
121       * Sets the center of this circle to be coincident with {@code newCenter}.
122       * Note that this circle will <i>not</i> hold a reference to 
123       * {@code newCenter}, so changes made to it after calling this method
124       * will not be reflected herein.
125       * <p>
126       * If {@code newCenter} is <i>null</i>, the center of this circle will
127       * not be altered.</p>
128       * 
129       * @param newCenter the new center of this circle.
130       */
131      public void setCenter(Point2D newCenter)
132      {
133        if (newCenter != null)
134          center.setLocation(newCenter.getX(), newCenter.getY());
135      }
136      
137      /**
138       * Returns a copy of the center of this circle.
139       * @return a copy of the center of this circle.
140       */
141      public Point2D getCenter()
142      {
143        return (Point2D)center.clone();
144      }
145      
146      //============================================================================
147      // IMPLEMENTATION OF Shape INTERFACE
148      //============================================================================
149      
150      public boolean contains(double x, double y)
151      {
152        return ellipse.contains(x, y);
153      }
154      
155      public boolean contains(double x, double y, double w, double h)
156      {
157        return ellipse.contains(x, y, w, h);
158      }
159      
160      public boolean contains(Point2D p)
161      {
162        return ellipse.contains(p);
163      }
164      
165      public boolean contains(Rectangle2D r)
166      {
167        return ellipse.contains(r);
168      }
169      
170      public Rectangle getBounds()
171      {
172        return ellipse.getBounds();
173      }
174      
175      public Rectangle2D getBounds2D()
176      {
177        return ellipse.getBounds2D();
178      }
179      
180      public PathIterator getPathIterator(AffineTransform at)
181      {
182        return ellipse.getPathIterator(at);
183      }
184      
185      public PathIterator getPathIterator(AffineTransform at, double flatness)
186      {
187        return ellipse.getPathIterator(at, flatness);
188      }
189      
190      public boolean intersects(double x, double y, double w, double h)
191      {
192        return ellipse.intersects(x, y, w, h);
193      }
194      
195      /** {@inheritDoc} */
196      public boolean intersects(Rectangle2D r) 
197      {
198        return ellipse.intersects(r);
199      }
200      
201      //============================================================================
202      // 
203      //============================================================================
204    
205      /**
206       * Creates a copy of this circle.
207       * If anything goes wrong during the cloning process
208       * a runtime exception is thrown.
209       */
210      @Override
211      public Circle clone()
212      {
213        Circle clone = null;
214        
215        try
216        {
217          clone = (Circle)super.clone();
218          
219          clone.center = (Point2D)this.center.clone();
220          
221          clone.ellipse = (Ellipse2D)this.ellipse.clone();
222        }
223        catch (CloneNotSupportedException ex)
224        {
225          throw new RuntimeException(ex);
226        }
227        
228        return clone;
229      }
230    }