View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.geometry.spherical.twod;
    18
    19  import java.util.Collections;
    20  import java.util.List;
    21
    22  import org.apache.commons.geometry.core.Transform;
    23  import org.apache.commons.geometry.core.partitioning.Hyperplane;
    24  import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
    25  import org.apache.commons.geometry.core.partitioning.Split;
    26  import org.apache.commons.geometry.core.partitioning.SplitLocation;
    27  import org.apache.commons.geometry.spherical.oned.AngularInterval;
    28  import org.apache.commons.geometry.spherical.oned.CutAngle;
    29  import org.apache.commons.geometry.spherical.oned.CutAngles;
    30  import org.apache.commons.geometry.spherical.oned.Transform1S;
    31
    32  /** Class representing a single, <em>convex</em> angular interval in a {@link GreatCircle}. Convex
    33   * angular intervals are those where the shortest path between all pairs of points in the
    34   * interval are completely contained in the interval. In the case of paths that tie for the
    35   * shortest length, it is sufficient that one of the paths is completely contained in the
    36   * interval. In spherical 2D space, convex arcs either fill the entire great circle or have
    37   * an angular size of less than or equal to {@code pi} radians.
    38   *
    39   * <p>Instances of this class are guaranteed to be immutable.</p>
    40   * @see GreatCircles
    41   */
    42  public final class GreatArc extends GreatCircleSubset implements HyperplaneConvexSubset<Point2S> {
    43      /** The interval representing the region of the great circle contained in the arc.
    44       */
    45      private final AngularInterval.Convex interval;
    46
    47      /** Create a new instance from a great circle and the interval embedded in it.
    48       * @param circle defining great circle instance
    49       * @param interval convex angular interval embedded in the great circle
    50       */
    51      GreatArc(final GreatCircle circle, final AngularInterval.Convex interval) {
    52          super(circle);
    53
    54          this.interval = interval;
    55      }
    56
    57      /** Return the start point of the arc, or null if the arc represents the full space.
    58       * @return the start point of the arc, or null if the arc represents the full space.
    59       */
    60      public Point2S getStartPoint() {
    61          if (!interval.isFull()) {
    62              return getCircle().toSpace(interval.getMinBoundary().getPoint());
    63          }
    64
    65          return null;
    66      }
    67
    68      /** Return the end point of the arc, or null if the arc represents the full space.
    69       * @return the end point of the arc, or null if the arc represents the full space.
    70       */
    71      public Point2S getEndPoint() {
    72          if (!interval.isFull()) {
    73              return getCircle().toSpace(interval.getMaxBoundary().getPoint());
    74          }
    75
    76          return null;
    77      }
    78
    79      /** Return the midpoint of the arc, or null if the arc represents the full space.
    80       * @return the midpoint of the arc, or null if the arc represents the full space.
    81       */
    82      public Point2S getMidPoint() {
    83          if (!interval.isFull()) {
    84              return getCircle().toSpace(interval.getMidPoint());
    85          }
    86
    87          return null;
    88      }
    89
    90      /** Get the angular interval for the arc.
    91       * @return the angular interval for the arc
    92       * @see #getSubspaceRegion()
    93       */
    94      public AngularInterval.Convex getInterval() {
    95          return interval;
    96      }
    97
    98      /** {@inheritDoc} */
    99      @Override
    100     public AngularInterval.Convex getSubspaceRegion() {
    101         return getInterval();
    102     }
    103
    104     /** {@inheritDoc} */
    105     @Override
    106     public List<GreatArc> toConvex() {
    107         return Collections.singletonList(this);
    108     }
    109
    110     /** {@inheritDoc} */
    111     @Override
    112     public Split<GreatArc> split(final Hyperplane<Point2S> splitter) {
    113         final GreatCircle splitterCircle = (GreatCircle) splitter;
    114         final GreatCircle thisCircle = getCircle();
    115
    116         final Point2S intersection = splitterCircle.intersection(thisCircle);
    117
    118         GreatArc minus = null;
    119         GreatArc plus = null;
    120
    121         if (intersection != null) {
    122             // use a negative-facing cut angle to account for the fact that the great circle
    123             // poles point to the minus side of the circle
    124             final CutAngle subSplitter = CutAngles.createNegativeFacing(
    125                     thisCircle.toSubspace(intersection), splitterCircle.getPrecision());
    126
    127             final Split<AngularInterval.Convex> subSplit = interval.splitDiameter(subSplitter);
    128             final SplitLocation subLoc = subSplit.getLocation();
    129
    130             if (subLoc == SplitLocation.MINUS) {
    131                 minus = this;
    132             } else if (subLoc == SplitLocation.PLUS) {
    133                 plus = this;
    134             } else if (subLoc == SplitLocation.BOTH) {
    135                 minus = GreatCircles.arcFromInterval(thisCircle, subSplit.getMinus());
    136                 plus = GreatCircles.arcFromInterval(thisCircle, subSplit.getPlus());
    137             }
    138         }
    139
    140         return new Split<>(minus, plus);
    141     }
    142
    143     /** {@inheritDoc} */
    144     @Override
    145     public GreatArc transform(final Transform<Point2S> transform) {
    146         return new GreatArc(getCircle().transform(transform), interval);
    147     }
    148
    149     /** {@inheritDoc} */
    150     @Override
    151     public GreatArc reverse() {
    152         return new GreatArc(
    153                 getCircle().reverse(),
    154                 interval.transform(Transform1S.createNegation()));
    155     }
    156
    157     /** Return a string representation of this great arc.
    158      *
    159      * <p>In order to keep the string representation short but useful, the exact format of the return
    160      * value depends on the properties of the arc. See below for examples.
    161      *
    162      * <ul>
    163      *      <li>Full arc
    164      *          <ul>
    165      *              <li>{@code GreatArc[full= true, circle= GreatCircle[pole= (0.0, 0.0, 1.0), x= (1.0, 0.0, 0.0), y= (0.0, 1.0, 0.0)]}</li>
    166      *          </ul>
    167      *      </li>
    168      *      <li>Non-full arc
    169      *          <ul>
    170      *              <li>{@code GreatArc[start= (1.0, 1.5707963267948966), end= (2.0, 1.5707963267948966)}</li>
    171      *          </ul>
    172      *      </li>
    173      * </ul>
    174      */
    175     @Override
    176     public String toString() {
    177         final StringBuilder sb = new StringBuilder();
    178         sb.append(this.getClass().getSimpleName()).append('[');
    179
    180         if (isFull()) {
    181             sb.append("full= true, circle= ")
    182                 .append(getCircle());
    183         } else {
    184             sb.append("start= ")
    185                 .append(getStartPoint())
    186                 .append(", end= ")
    187                 .append(getEndPoint());
    188         }
    189
    190         return sb.toString();
    191     }
    192 }