1 /**
2    Represents a closed, bounded interval on the real line.
3 
4    Copyright: 2005 Google Inc. All Rights Reserved.
5 
6    License:
7    Licensed under the Apache License, Version 2.0 (the "License");
8    you may not use this file except in compliance with the License.
9    You may obtain a copy of the License at
10 
11    $(LINK http://www.apache.org/licenses/LICENSE-2.0)
12 
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS-IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18 
19    Authors: ericv@google.com (Eric Veach), madric@gmail.com (Vijay Nayar)
20 */
21 module s2.r1interval;
22 
23 import s2.util.math.vector;
24 import algorithm = std.algorithm;
25 import conv = std.conv;
26 import math = std.math;
27 
28 /**
29    An R1Interval represents a closed, bounded interval on the real line.
30    It is capable of representing the empty interval (containing no points)
31    and zero-length intervals (containing a single point).
32 
33    This class is intended to be copied by value as desired.  It uses
34    the default copy constructor and assignment operator.
35 */
36 struct R1Interval {
37 public:
38   /// Constructor.  If lo > hi, the interval is empty.
39   this(double lo, double hi) {
40     _bounds = Vector2_d([lo, hi]);
41   }
42 
43   /// Returns an empty interval.
44   static R1Interval empty() {
45     return R1Interval();
46   }
47 
48   /// Convenience method to construct an interval containing a single point.
49   static R1Interval fromPoint(double p) {
50     return R1Interval(p, p);
51   }
52 
53   /**
54      Convenience method to construct the minimal interval containing
55      the two given points.  This is equivalent to starting with an empty
56      interval and calling AddPoint() twice, but it is more efficient.
57   */
58   static R1Interval fromPointPair(double p1, double p2) {
59     if (p1 <= p2) {
60       return R1Interval(p1, p2);
61     } else {
62       return R1Interval(p2, p1);
63     }
64   }
65 
66   // Accessors methods.
67   @property
68   double lo() const {
69     return _bounds[0];
70   }
71 
72   @property
73   double hi() const {
74     return _bounds[1];
75   }
76 
77   // Methods to modify one endpoint of an existing R1Interval.  Do not use
78   // these methods if you want to replace both endpoints of the interval; use
79   // a constructor instead.  For example:
80   //
81   //   *lat_bounds = R1Interval(lat_lo, lat_hi);
82   void setLo(double p) {
83     _bounds[0] = p;
84   }
85 
86   void setHi(double p) {
87     _bounds[1] = p;
88   }
89 
90   /**
91      Methods that allow the R1Interval to be accessed as a vector.  (The
92      recommended style is to use lo() and hi() whenever possible, but these
93      methods are useful when the endpoint to be selected is not constant.)
94   */
95   ref inout(double) opIndex(size_t i) inout {
96     return _bounds[i];
97   }
98 
99   void opIndexAssign(in double v, size_t i) {
100     _bounds[i] = v;
101   }
102 
103   Vector2_d bounds() const {
104     return _bounds;
105   }
106 
107   ref Vector2_d mutableBounds() return {
108     return _bounds;
109   }
110 
111   /// Return true if the interval is empty, i.e. it contains no points.
112   bool isEmpty() const {
113     return lo() > hi();
114   }
115 
116   /// Returns the center of the interval.  For empty intervals, the result is arbitrary.
117   double getCenter() const {
118     return 0.5 * (lo() + hi());
119   }
120 
121   /// Returns the length of the interval.  The length of an empty interval is negative.
122   double getLength() const {
123     return hi() - lo();
124   }
125 
126   bool contains(double p) const {
127     return p >= lo() && p <= hi();
128   }
129 
130   bool interiorContains(double p) const {
131     return p > lo() && p < hi();
132   }
133 
134   /// Returns true if this interval contains the interval 'y'.
135   bool contains(in R1Interval y) const {
136     if (y.isEmpty()) {
137       return true;
138     }
139     return y.lo() >= lo() && y.hi() <= hi();
140   }
141 
142   /// Returns true if the interior of this interval contains the entire interval 'y' (including its
143   /// boundary).
144   bool interiorContains(in R1Interval y) const {
145     if (y.isEmpty()) {
146       return true;
147     }
148     return y.lo() > lo() && y.hi() < hi();
149   }
150 
151   /// Returns true if this interval intersects the given interval, i.e. if they have any points in
152   /// common.
153   bool intersects(in R1Interval y) const {
154     if (lo() <= y.lo()) {
155       return y.lo() <= hi() && y.lo() <= y.hi();
156     } else {
157       return lo() <= y.hi() && lo() <= hi();
158     }
159   }
160 
161   /// Returns true if the interior of this interval intersects any point of the given interval
162   /// (including its boundary).
163   bool interiorIntersects(in R1Interval y) const {
164     return y.lo() < hi() && lo() < y.hi() && lo() < hi() && y.lo() <= y.hi();
165   }
166 
167   /// Returns the Hausdorff distance to the given interval 'y'. For two
168   /// R1Intervals x and y, this distance is defined as
169   ///     `h(x, y) = max_{p in x} min_{q in y} d(p, q)`.
170   double getDirectedHausdorffDistance(in R1Interval y) const {
171     if (isEmpty()) {
172       return 0.0;
173     } else if (y.isEmpty()) {
174       return double.max;
175     } else {
176       return algorithm.max(0.0, algorithm.max(hi() - y.hi(), y.lo() - lo()));
177     }
178   }
179 
180   /// Expands the interval so that it contains the given point "p".
181   void addPoint(double p) {
182     if (isEmpty()) {
183       setLo(p);
184       setHi(p);
185     } else if (p < lo()) {
186       setLo(p);
187     } else if (p > hi()) {
188       setHi(p);
189     }
190   }
191 
192   /// Expands the interval so that it contains the given interval "y".
193   void addInterval(in R1Interval y) {
194     if (y.isEmpty()) {
195       return;
196     } else if (isEmpty()) {
197       this = y;
198       return;
199     }
200     if (y.lo() < lo()) {
201       setLo(y.lo());
202     }
203     if (y.hi() > hi()) {
204       setHi(y.hi());
205     }
206   }
207 
208   /// Returns the closest point in the interval to the given point "p".  The interval must be
209   // non-empty.
210   double project(double p) const
211   in {
212     assert(!isEmpty());
213   } do {
214     return algorithm.max(lo(), algorithm.min(hi(), p));
215   }
216 
217   /**
218      Returns an interval that has been expanded on each side by the given
219      distance "margin".  If "margin" is negative, then shrink the interval on
220      each side by "margin" instead.  The resulting interval may be empty.  Any
221      expansion of an empty interval remains empty.
222   */
223   R1Interval expanded(double margin) const {
224     if (isEmpty()) {
225       return this;
226     }
227     return R1Interval(lo() - margin, hi() + margin);
228   }
229 
230   /// Returns the smallest interval that contains this interval and the given interval "y".
231   R1Interval unite(in R1Interval y) const {
232     if (isEmpty()) {
233       return y;
234     } else if (y.isEmpty()) {
235       return this;
236     } else {
237       return R1Interval(algorithm.min(lo(), y.lo()), algorithm.max(hi(), y.hi()));
238     }
239   }
240 
241   /// Returns the intersection of this interval with the given interval.  Empty intervals do not
242   /// need to be special-cased.
243   R1Interval intersection(in R1Interval y) const {
244     return R1Interval(algorithm.max(lo(), y.lo()), algorithm.min(hi(), y.hi()));
245   }
246 
247   /// Supports the == and != operators. Return true if two intervals contain the same set of points.
248   bool opEquals(in R1Interval y) const {
249     return (lo() == y.lo() && hi() == y.hi()) || (isEmpty() && y.isEmpty());
250   }
251 
252   /**
253      Returns true if this interval can be transformed into the given interval
254      by moving each endpoint by at most "max_error".  The empty interval is
255      considered to be positioned arbitrarily on the real line, thus any
256      interval with (length <= 2*max_error) matches the empty interval.
257   */
258   bool approxEquals(in R1Interval y, double max_error = 1e-15) const {
259     if (isEmpty()) {
260       return y.getLength() <= 2 * max_error;
261     }
262     if (y.isEmpty()) {
263       return getLength() <= 2 * max_error;
264     }
265     return (math.fabs(y.lo() - lo()) <= max_error
266         && math.fabs(y.hi() - hi()) <= max_error);
267   }
268 
269   string toString() const {
270     return "[" ~ conv.to!string(lo()) ~ ", " ~ conv.to!string(hi()) ~ "]";
271   }
272 
273 private:
274   // The default constructor creates an empty interval.  (Any interval where
275   // lo > hi is considered to be empty.)
276   //
277   // Note: Don't construct an interval using the default constructor and
278   // set_lo()/set_hi(), since this technique doesn't work with S1Interval and
279   // is bad programming style anyways.  If you need to set both endpoints, use
280   // the constructor below:
281   //
282   //   lat_bounds_ = R1Interval(lat_lo, lat_hi);
283   Vector2_d _bounds = Vector2_d([1, 0]);
284 }