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.List;
20 import java.util.stream.Collectors;
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.Split;
25 import org.apache.commons.geometry.core.partitioning.SplitLocation;
26 import org.apache.commons.geometry.spherical.oned.CutAngle;
27 import org.apache.commons.geometry.spherical.oned.CutAngles;
28 import org.apache.commons.geometry.spherical.oned.RegionBSPTree1S;
29
30 /** Class representing an arbitrary subset of the points on a great circle using a
31 * {@link RegionBSPTree1S}. This class can represent convex, non-convex, and empty regions.
32 *
33 * <p>This class is mutable and <em>not</em> thread safe.</p>
34 */
35 public final class EmbeddedTreeGreatCircleSubset extends GreatCircleSubset {
36 /** The 1D region on the great circle. */
37 private final RegionBSPTree1S region;
38
39 /** Construct a new, empty hyperplane subset for the given great circle.
40 * @param greatCircle great circle defining this instance
41 */
42 public EmbeddedTreeGreatCircleSubset(final GreatCircle greatCircle) {
43 this(greatCircle, false);
44 }
45
46 /** Construct a new sub-region for the given great circle. If {@code full}
47 * is true, then the region will cover the entire circle; otherwise,
48 * it will be empty.
49 * @param circle great circle that the sub-region will belong to
50 * @param full if true, the sub-region will cover the entire circle;
51 * otherwise it will be empty
52 */
53 public EmbeddedTreeGreatCircleSubset(final GreatCircle circle, final boolean full) {
54 this(circle, new RegionBSPTree1S(full));
55 }
56
57 /** Construct a new instance from its defining great circle and subspace region.
58 * @param circle great circle that the sub-region will belong to
59 * @param region subspace region
60 */
61 public EmbeddedTreeGreatCircleSubset(final GreatCircle circle, final RegionBSPTree1S region) {
62 super(circle);
63
64 this.region = region;
65 }
66
67 /** {@inheritDoc} */
68 @Override
69 public RegionBSPTree1S getSubspaceRegion() {
70 return region;
71 }
72
73 /** {@inheritDoc} */
74 @Override
75 public EmbeddedTreeGreatCircleSubset transform(final Transform<Point2S> transform) {
76 final GreatCircle circle = getCircle().transform(transform);
77
78 return new EmbeddedTreeGreatCircleSubset(circle, region.copy());
79 }
80
81 /** {@inheritDoc} */
82 @Override
83 public List<GreatArc> toConvex() {
84 return region.toIntervals().stream()
85 .flatMap(i -> i.toConvex().stream())
86 .map(i -> GreatCircles.arcFromInterval(getCircle(), i))
87 .collect(Collectors.toList());
88 }
89
90 /** {@inheritDoc}
91 *
92 * <p>In all cases, the current instance is not modified. However, In order to avoid
93 * unnecessary copying, this method will use the current instance as the split value when
94 * the instance lies entirely on the plus or minus side of the splitter. For example, if
95 * this instance lies entirely on the minus side of the splitter, the sub great circle
96 * returned by {@link Split#getMinus()} will be this instance. Similarly, {@link Split#getPlus()}
97 * will return the current instance if it lies entirely on the plus side. Callers need to make
98 * special note of this, since this class is mutable.</p>
99 */
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 }