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}