001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.spherical.twod;
018
019import java.util.List;
020import java.util.stream.Collectors;
021
022import org.apache.commons.geometry.core.Transform;
023import org.apache.commons.geometry.core.partitioning.Hyperplane;
024import org.apache.commons.geometry.core.partitioning.Split;
025import org.apache.commons.geometry.core.partitioning.SplitLocation;
026import org.apache.commons.geometry.spherical.oned.CutAngle;
027import org.apache.commons.geometry.spherical.oned.CutAngles;
028import org.apache.commons.geometry.spherical.oned.RegionBSPTree1S;
029
030/** Class representing an arbitrary subset of the points on a great circle using a
031 * {@link RegionBSPTree1S}. This class can represent convex, non-convex, and empty regions.
032 *
033 * <p>This class is mutable and <em>not</em> thread safe.</p>
034 */
035public final class EmbeddedTreeGreatCircleSubset extends GreatCircleSubset {
036    /** The 1D region on the great circle. */
037    private final RegionBSPTree1S region;
038
039    /** Construct a new, empty hyperplane subset for the given great circle.
040     * @param greatCircle great circle defining this instance
041     */
042    public EmbeddedTreeGreatCircleSubset(final GreatCircle greatCircle) {
043        this(greatCircle, false);
044    }
045
046    /** Construct a new sub-region for the given great circle. If {@code full}
047     * is true, then the region will cover the entire circle; otherwise,
048     * it will be empty.
049     * @param circle great circle that the sub-region will belong to
050     * @param full if true, the sub-region will cover the entire circle;
051     *      otherwise it will be empty
052     */
053    public EmbeddedTreeGreatCircleSubset(final GreatCircle circle, final boolean full) {
054        this(circle, new RegionBSPTree1S(full));
055    }
056
057    /** Construct a new instance from its defining great circle and subspace region.
058     * @param circle great circle that the sub-region will belong to
059     * @param region subspace region
060     */
061    public EmbeddedTreeGreatCircleSubset(final GreatCircle circle, final RegionBSPTree1S region) {
062        super(circle);
063
064        this.region = region;
065    }
066
067    /** {@inheritDoc} */
068    @Override
069    public RegionBSPTree1S getSubspaceRegion() {
070        return region;
071    }
072
073    /** {@inheritDoc} */
074    @Override
075    public EmbeddedTreeGreatCircleSubset transform(final Transform<Point2S> transform) {
076        final GreatCircle circle = getCircle().transform(transform);
077
078        return new EmbeddedTreeGreatCircleSubset(circle, region.copy());
079    }
080
081    /** {@inheritDoc} */
082    @Override
083    public List<GreatArc> toConvex() {
084        return region.toIntervals().stream()
085                .flatMap(i -> i.toConvex().stream())
086                .map(i -> GreatCircles.arcFromInterval(getCircle(), i))
087                .collect(Collectors.toList());
088    }
089
090    /** {@inheritDoc}
091     *
092     * <p>In all cases, the current instance is not modified. However, In order to avoid
093     * unnecessary copying, this method will use the current instance as the split value when
094     * the instance lies entirely on the plus or minus side of the splitter. For example, if
095     * this instance lies entirely on the minus side of the splitter, the sub great circle
096     * returned by {@link Split#getMinus()} will be this instance. Similarly, {@link Split#getPlus()}
097     * will return the current instance if it lies entirely on the plus side. Callers need to make
098     * special note of this, since this class is mutable.</p>
099     */
100    @Override
101    public Split<EmbeddedTreeGreatCircleSubset> split(final Hyperplane<Point2S> splitter) {
102
103        final GreatCircle splitterCircle = (GreatCircle) splitter;
104        final GreatCircle thisCircle = getCircle();
105
106        final Point2S intersection = splitterCircle.intersection(thisCircle);
107
108        EmbeddedTreeGreatCircleSubset minus = null;
109        EmbeddedTreeGreatCircleSubset plus = null;
110
111        if (intersection != null) {
112            final CutAngle subSplitter = CutAngles.createPositiveFacing(
113                    thisCircle.toSubspace(intersection), splitterCircle.getPrecision());
114
115            final Split<RegionBSPTree1S> subSplit = region.splitDiameter(subSplitter);
116            final SplitLocation subLoc = subSplit.getLocation();
117
118            if (subLoc == SplitLocation.MINUS) {
119                minus = this;
120            } else if (subLoc == SplitLocation.PLUS) {
121                plus = this;
122            } else if (subLoc == SplitLocation.BOTH) {
123                minus = new EmbeddedTreeGreatCircleSubset(thisCircle, subSplit.getMinus());
124                plus =  new EmbeddedTreeGreatCircleSubset(thisCircle, subSplit.getPlus());
125            }
126        }
127
128        return new Split<>(minus, plus);
129    }
130
131    /** Add an arc to this instance.
132     * @param arc arc to add
133     * @throws IllegalArgumentException if the given arc is not from
134     *      a great circle equivalent to this instance
135     */
136    public void add(final GreatArc arc) {
137        GreatCircles.validateGreatCirclesEquivalent(getCircle(), arc.getCircle());
138
139        region.add(arc.getSubspaceRegion());
140    }
141
142    /** Add the region represented by the given subcircle to this instance.
143     * The argument is not modified.
144     * @param subcircle subcircle to add
145     * @throws IllegalArgumentException if the given subcircle is not from
146     *      a great circle equivalent to this instance
147     */
148    public void add(final EmbeddedTreeGreatCircleSubset subcircle) {
149        GreatCircles.validateGreatCirclesEquivalent(getCircle(), subcircle.getCircle());
150
151        region.union(subcircle.getSubspaceRegion());
152    }
153
154    /** {@inheritDoc} */
155    @Override
156    public String toString() {
157        final StringBuilder sb = new StringBuilder();
158        sb.append(this.getClass().getSimpleName())
159            .append('[')
160            .append("circle= ")
161            .append(getCircle())
162            .append(", region= ")
163            .append(region)
164            .append(']');
165
166        return sb.toString();
167    }
168}