blob: 5590574cb725f02b87250c845355946c2e68c388 [file] [log] [blame]
// Copyright 2005 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#ifndef S2_R1INTERVAL_H_
#define S2_R1INTERVAL_H_
#include <algorithm>
#include <cmath>
#include <iosfwd>
#include <iostream>
#include "base/logging.h"
#include "s2/_fpcontractoff.h"
#include "s2/util/math/vector.h" // IWYU pragma: export
// An R1Interval represents a closed, bounded interval on the real line.
// It is capable of representing the empty interval (containing no points)
// and zero-length intervals (containing a single point).
//
// This class is intended to be copied by value as desired. It uses
// the default copy constructor and assignment operator.
class R1Interval {
public:
// Constructor. If lo > hi, the interval is empty.
R1Interval(double lo, double hi) : bounds_(lo, hi) {}
// The default constructor creates an empty interval. (Any interval where
// lo > hi is considered to be empty.)
//
// Note: Don't construct an interval using the default constructor and
// set_lo()/set_hi(), since this technique doesn't work with S1Interval and
// is bad programming style anyways. If you need to set both endpoints, use
// the constructor above:
//
// lat_bounds_ = R1Interval(lat_lo, lat_hi);
R1Interval() : bounds_(1, 0) {}
// Returns an empty interval.
static inline R1Interval Empty() { return R1Interval(); }
// Convenience method to construct an interval containing a single point.
static R1Interval FromPoint(double p) { return R1Interval(p, p); }
// Convenience method to construct the minimal interval containing
// the two given points. This is equivalent to starting with an empty
// interval and calling AddPoint() twice, but it is more efficient.
static R1Interval FromPointPair(double p1, double p2) {
if (p1 <= p2) {
return R1Interval(p1, p2);
} else {
return R1Interval(p2, p1);
}
}
// Accessors methods.
double lo() const { return bounds_[0]; }
double hi() const { return bounds_[1]; }
// Methods to modify one endpoint of an existing R1Interval. Do not use
// these methods if you want to replace both endpoints of the interval; use
// a constructor instead. For example:
//
// *lat_bounds = R1Interval(lat_lo, lat_hi);
void set_lo(double p) { bounds_[0] = p; }
void set_hi(double p) { bounds_[1] = p; }
// Methods that allow the R1Interval to be accessed as a vector. (The
// recommended style is to use lo() and hi() whenever possible, but these
// methods are useful when the endpoint to be selected is not constant.)
double operator[](int i) const { return bounds_[i]; }
double& operator[](int i) { return bounds_[i]; }
Vector2_d const& bounds() const { return bounds_; }
Vector2_d* mutable_bounds() { return &bounds_; }
// Return true if the interval is empty, i.e. it contains no points.
bool is_empty() const { return lo() > hi(); }
// Return the center of the interval. For empty intervals,
// the result is arbitrary.
double GetCenter() const { return 0.5 * (lo() + hi()); }
// Return the length of the interval. The length of an empty interval
// is negative.
double GetLength() const { return hi() - lo(); }
bool Contains(double p) const { return p >= lo() && p <= hi(); }
bool InteriorContains(double p) const { return p > lo() && p < hi(); }
// Return true if this interval contains the interval 'y'.
bool Contains(R1Interval const& y) const {
if (y.is_empty())
return true;
return y.lo() >= lo() && y.hi() <= hi();
}
// Return true if the interior of this interval contains the entire
// interval 'y' (including its boundary).
bool InteriorContains(R1Interval const& y) const {
if (y.is_empty())
return true;
return y.lo() > lo() && y.hi() < hi();
}
// Return true if this interval intersects the given interval,
// i.e. if they have any points in common.
bool Intersects(R1Interval const& y) const {
if (lo() <= y.lo()) {
return y.lo() <= hi() && y.lo() <= y.hi();
} else {
return lo() <= y.hi() && lo() <= hi();
}
}
// Return true if the interior of this interval intersects
// any point of the given interval (including its boundary).
bool InteriorIntersects(R1Interval const& y) const {
return y.lo() < hi() && lo() < y.hi() && lo() < hi() && y.lo() <= y.hi();
}
// Return the Hausdorff distance to the given interval 'y'. For two
// R1Intervals x and y, this distance is defined as
// h(x, y) = max_{p in x} min_{q in y} d(p, q).
double GetDirectedHausdorffDistance(R1Interval const& y) const {
if (is_empty())
return 0.0;
if (y.is_empty())
return HUGE_VAL;
return std::max(0.0, std::max(hi() - y.hi(), y.lo() - lo()));
}
// Expand the interval so that it contains the given point "p".
void AddPoint(double p) {
if (is_empty()) {
set_lo(p);
set_hi(p);
} else if (p < lo()) {
set_lo(p);
} // NOLINT
else if (p > hi()) {
set_hi(p);
} // NOLINT
}
// Expand the interval so that it contains the given interval "y".
void AddInterval(R1Interval const& y) {
if (y.is_empty())
return;
if (is_empty()) {
*this = y;
return;
}
if (y.lo() < lo())
set_lo(y.lo());
if (y.hi() > hi())
set_hi(y.hi());
}
// Return the closest point in the interval to the given point "p".
// The interval must be non-empty.
double Project(double p) const {
DCHECK(!is_empty());
return std::max(lo(), std::min(hi(), p));
}
// Return an interval that has been expanded on each side by the given
// distance "margin". If "margin" is negative, then shrink the interval on
// each side by "margin" instead. The resulting interval may be empty. Any
// expansion of an empty interval remains empty.
R1Interval Expanded(double margin) const {
if (is_empty())
return *this;
return R1Interval(lo() - margin, hi() + margin);
}
// Return the smallest interval that contains this interval and the
// given interval "y".
R1Interval Union(R1Interval const& y) const {
if (is_empty())
return y;
if (y.is_empty())
return *this;
return R1Interval(std::min(lo(), y.lo()), std::max(hi(), y.hi()));
}
// Return the intersection of this interval with the given interval.
// Empty intervals do not need to be special-cased.
R1Interval Intersection(R1Interval const& y) const {
return R1Interval(std::max(lo(), y.lo()), std::min(hi(), y.hi()));
}
// Return true if two intervals contain the same set of points.
bool operator==(R1Interval const& y) const {
return (lo() == y.lo() && hi() == y.hi()) || (is_empty() && y.is_empty());
}
// Return true if two intervals do not contain the same set of points.
bool operator!=(R1Interval const& y) const { return !operator==(y); }
// Return true if this interval can be transformed into the given interval
// by moving each endpoint by at most "max_error". The empty interval is
// considered to be positioned arbitrarily on the real line, thus any
// interval with (length <= 2*max_error) matches the empty interval.
bool ApproxEquals(R1Interval const& y, double max_error = 1e-15) const {
if (is_empty())
return y.GetLength() <= 2 * max_error;
if (y.is_empty())
return GetLength() <= 2 * max_error;
return (std::fabs(y.lo() - lo()) <= max_error &&
std::fabs(y.hi() - hi()) <= max_error);
}
private:
Vector2_d bounds_;
};
inline std::ostream& operator<<(std::ostream& os, R1Interval const& x) {
return os << "[" << x.lo() << ", " << x.hi() << "]";
}
#endif // S2_R1INTERVAL_H_