mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
Rework QgsRange to better handle temporal ranges
Make a new QgsTemporalRange template class for specific temporal classes. Add QgsDateTimeRange class.
This commit is contained in:
parent
18c8c1fe36
commit
23de9a0224
@ -12,6 +12,7 @@ class QgsDoubleRange
|
||||
bool includeLower() const;
|
||||
bool includeUpper() const;
|
||||
bool isEmpty() const;
|
||||
bool isSingleton() const;
|
||||
bool contains( const QgsDoubleRange &other ) const;
|
||||
bool contains( double element ) const;
|
||||
bool overlaps( const QgsDoubleRange &other ) const;
|
||||
@ -31,6 +32,7 @@ class QgsIntRange
|
||||
bool includeLower() const;
|
||||
bool includeUpper() const;
|
||||
bool isEmpty() const;
|
||||
bool isSingleton() const;
|
||||
bool contains( const QgsIntRange &other ) const;
|
||||
bool contains( int element ) const;
|
||||
bool overlaps( const QgsIntRange &other ) const;
|
||||
@ -44,14 +46,37 @@ class QgsDateRange
|
||||
|
||||
public:
|
||||
|
||||
QgsDateRange( QDate lower, QDate upper, bool includeLower = true, bool includeUpper = true );
|
||||
QDate lower() const;
|
||||
QDate upper() const;
|
||||
bool includeLower() const;
|
||||
bool includeUpper() const;
|
||||
QgsDateRange( const QDate &lower = QDate(), const QDate &upper = QDate(), bool includeLower = true, bool includeUpper = true );
|
||||
QDate begin() const;
|
||||
QDate end() const;
|
||||
bool includeBeginning() const;
|
||||
bool includeEnd() const;
|
||||
bool isEmpty() const;
|
||||
bool isInstant() const;
|
||||
bool isInfinite() const;
|
||||
bool contains( const QgsDateRange &other ) const;
|
||||
bool contains( QDate element ) const;
|
||||
bool contains( const QDate &element ) const;
|
||||
bool overlaps( const QgsDateRange &other ) const;
|
||||
};
|
||||
|
||||
class QgsDateTimeRange
|
||||
{
|
||||
%TypeHeaderCode
|
||||
#include <qgsrange.h>
|
||||
%End
|
||||
|
||||
public:
|
||||
|
||||
QgsDateTimeRange( const QDateTime &lower = QDateTime(), const QDateTime &upper = QDateTime(), bool includeLower = true, bool includeUpper = true );
|
||||
QDateTime begin() const;
|
||||
QDateTime end() const;
|
||||
bool includeBeginning() const;
|
||||
bool includeEnd() const;
|
||||
bool isEmpty() const;
|
||||
bool isInstant() const;
|
||||
bool isInfinite() const;
|
||||
bool contains( const QgsDateTimeRange &other ) const;
|
||||
bool contains( const QDateTime &element ) const;
|
||||
bool overlaps( const QgsDateTimeRange &other ) const;
|
||||
};
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
* A template based class for storing ranges (lower to upper values).
|
||||
*
|
||||
* QgsRange classes represent a range of values of some element type. For instance,
|
||||
* ranges of QDateTime might be used to represent the ranges of timestamp ranges.
|
||||
* ranges of int might be used to represent integer ranges.
|
||||
*
|
||||
* Ranges can indicate whether the upper and lower values are inclusive or exclusive.
|
||||
* The inclusivity or exclusivity of bounds is considered when determining things like
|
||||
@ -86,9 +86,16 @@ template <class T> class CORE_EXPORT QgsRange
|
||||
/**
|
||||
* Returns true if the range is empty, ie the lower bound equals (or exceeds) the upper bound
|
||||
* and either the bounds are exclusive.
|
||||
* \see isSingleton()
|
||||
*/
|
||||
bool isEmpty() const { return mLower > mUpper || ( mUpper == mLower && !( mIncludeLower || mIncludeUpper ) ); }
|
||||
|
||||
/**
|
||||
* Returns true if the range consists only of a single value or instant.
|
||||
* \see isEmpty()
|
||||
*/
|
||||
bool isSingleton() const { return mLower == mUpper && ( mIncludeLower || mIncludeUpper ); }
|
||||
|
||||
/**
|
||||
* Returns true if this range contains another range.
|
||||
* \see overlaps()
|
||||
@ -176,6 +183,9 @@ template <class T> class CORE_EXPORT QgsRange
|
||||
*/
|
||||
typedef QgsRange< double > QgsDoubleRange;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* QgsRange which stores a range of integer values.
|
||||
* \since QGIS 3.0
|
||||
@ -186,137 +196,225 @@ typedef QgsRange< double > QgsDoubleRange;
|
||||
typedef QgsRange< int > QgsIntRange;
|
||||
|
||||
|
||||
// specialization required to handle invalid QDate bounds
|
||||
|
||||
/**
|
||||
* Returns true if the range is empty, ie the lower bound equals (or exceeds) the upper bound
|
||||
* and either the bounds are exclusive.
|
||||
* A range with both invalid lower and upper bounds is considered empty.
|
||||
* \class QgsTemporalRange
|
||||
* \ingroup core
|
||||
* A template based class for storing temporal ranges (beginning to end values).
|
||||
*
|
||||
* QgsTemporalRange classes represent a range of values of some temporal type. For instance,
|
||||
* ranges of QDateTime might be used to represent datetime ranges.
|
||||
*
|
||||
* Ranges can indicate whether the upper and lower values are inclusive or exclusive.
|
||||
* The inclusivity or exclusivity of bounds is considered when determining things like
|
||||
* whether ranges overlap or during calculation of range intersections.
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
* \see QgsDateRange
|
||||
* \note not available in Python bindings
|
||||
*/
|
||||
template<>
|
||||
bool QgsRange<QDate>::isEmpty() const
|
||||
template <class T> class CORE_EXPORT QgsTemporalRange
|
||||
{
|
||||
if ( !mLower.isValid() && !mUpper.isValid() )
|
||||
return true;
|
||||
public:
|
||||
|
||||
if ( mLower.isValid() != mUpper.isValid() )
|
||||
return false;
|
||||
/**
|
||||
* Constructor for QgsTemporalRange. The \a begin and \a end are specified,
|
||||
* and optionally whether or not these bounds are included in the range.
|
||||
*/
|
||||
QgsTemporalRange( const T &begin = T(), const T &end = T(), bool includeBeginning = true, bool includeEnd = true )
|
||||
: mLower( begin )
|
||||
, mUpper( end )
|
||||
, mIncludeLower( includeBeginning )
|
||||
, mIncludeUpper( includeEnd )
|
||||
{}
|
||||
|
||||
if ( mLower > mUpper )
|
||||
return true;
|
||||
/**
|
||||
* Returns the beginning of the range.
|
||||
* \see end()
|
||||
* \see includeBeginning()
|
||||
*/
|
||||
T begin() const { return mLower; }
|
||||
|
||||
if ( mLower == mUpper && !( mIncludeLower || mIncludeUpper ) )
|
||||
return true;
|
||||
/**
|
||||
* Returns the upper bound of the range.
|
||||
* \see begin()
|
||||
* \see includeEnd()
|
||||
*/
|
||||
T end() const { return mUpper; }
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Returns true if the beginning is inclusive, or false if the beginning
|
||||
* is exclusive.
|
||||
* \see begin()
|
||||
* \see includeEnd()
|
||||
*/
|
||||
bool includeBeginning() const { return mIncludeLower; }
|
||||
|
||||
/**
|
||||
* Returns true if this range contains another range.
|
||||
*/
|
||||
template<>
|
||||
bool QgsRange<QDate>::contains( const QgsRange<QDate> &other ) const
|
||||
{
|
||||
if ( !other.mLower.isValid() && mLower.isValid() )
|
||||
return false;
|
||||
/**
|
||||
* Returns true if the end is inclusive, or false if the end is exclusive.
|
||||
* \see end()
|
||||
* \see includeBeginning()
|
||||
*/
|
||||
bool includeEnd() const { return mIncludeUpper; }
|
||||
|
||||
/**
|
||||
* Returns true if the range consists only of a single instant.
|
||||
* \see isEmpty()
|
||||
* \see isInfinite()
|
||||
*/
|
||||
bool isInstant() const { return mLower.isValid() && mUpper.isValid() && mLower == mUpper && ( mIncludeLower || mIncludeUpper ); }
|
||||
|
||||
/**
|
||||
* Returns true if the range consists of all possible values.
|
||||
* \see isEmpty()
|
||||
* \see isInstant()
|
||||
*/
|
||||
bool isInfinite() const
|
||||
{
|
||||
return !mLower.isValid() && !mUpper.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the range is empty, ie the beginning equals (or exceeds) the end
|
||||
* and either of the bounds are exclusive.
|
||||
* A range with both invalid beginning and end is considered infinite and not empty.
|
||||
*/
|
||||
bool isEmpty() const
|
||||
{
|
||||
if ( !mLower.isValid() && !mUpper.isValid() )
|
||||
return false;
|
||||
|
||||
if ( mLower.isValid() != mUpper.isValid() )
|
||||
return false;
|
||||
|
||||
if ( mLower > mUpper )
|
||||
return true;
|
||||
|
||||
if ( mLower == mUpper && !( mIncludeLower || mIncludeUpper ) )
|
||||
return true;
|
||||
|
||||
if ( mLower.isValid() )
|
||||
{
|
||||
bool lowerOk = ( mIncludeLower && mLower <= other.mLower )
|
||||
|| ( !mIncludeLower && mLower < other.mLower )
|
||||
|| ( !mIncludeLower && !other.mIncludeLower && mLower <= other.mLower );
|
||||
if ( !lowerOk )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !other.mUpper.isValid() && mUpper.isValid() )
|
||||
return false;
|
||||
/**
|
||||
* Returns true if this range contains another range.
|
||||
*/
|
||||
bool contains( const QgsTemporalRange<T> &other ) const
|
||||
{
|
||||
if ( !other.mLower.isValid() && mLower.isValid() )
|
||||
return false;
|
||||
|
||||
if ( mLower.isValid() )
|
||||
{
|
||||
bool lowerOk = ( mIncludeLower && mLower <= other.mLower )
|
||||
|| ( !mIncludeLower && mLower < other.mLower )
|
||||
|| ( !mIncludeLower && !other.mIncludeLower && mLower <= other.mLower );
|
||||
if ( !lowerOk )
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !other.mUpper.isValid() && mUpper.isValid() )
|
||||
return false;
|
||||
|
||||
if ( mUpper.isValid() )
|
||||
{
|
||||
bool upperOk = ( mIncludeUpper && mUpper >= other.mUpper )
|
||||
|| ( !mIncludeUpper && mUpper > other.mUpper )
|
||||
|| ( !mIncludeUpper && !other.mIncludeUpper && mUpper >= other.mUpper );
|
||||
if ( !upperOk )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this range contains a specified \a element.
|
||||
*/
|
||||
bool contains( const T &element ) const
|
||||
{
|
||||
if ( !element.isValid() )
|
||||
return false;
|
||||
|
||||
if ( mLower.isValid() )
|
||||
{
|
||||
bool lowerOk = ( mIncludeLower && mLower <= element )
|
||||
|| ( !mIncludeLower && mLower < element );
|
||||
if ( !lowerOk )
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( mUpper.isValid() )
|
||||
{
|
||||
bool upperOk = ( mIncludeUpper && mUpper >= element )
|
||||
|| ( !mIncludeUpper && mUpper > element );
|
||||
if ( !upperOk )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this range overlaps another range.
|
||||
*/
|
||||
bool overlaps( const QgsTemporalRange<T> &other ) const
|
||||
{
|
||||
if ( !mUpper.isValid() && ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) )
|
||||
return true;
|
||||
|
||||
if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) )
|
||||
&& ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) )
|
||||
return true;
|
||||
|
||||
if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) )
|
||||
&& ( ( mIncludeUpper && mUpper >= other.mLower ) || ( !mIncludeUpper && mUpper > other.mLower ) ) )
|
||||
return true;
|
||||
|
||||
if ( ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) )
|
||||
&& ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) )
|
||||
return true;
|
||||
|
||||
if ( ( ( mIncludeLower && mLower >= other.mLower ) || ( !mIncludeLower && mLower > other.mLower ) )
|
||||
&& ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) )
|
||||
return true;
|
||||
|
||||
if ( mLower == other.mLower && mUpper == other.mUpper )
|
||||
return true;
|
||||
|
||||
if ( mUpper.isValid() )
|
||||
{
|
||||
bool upperOk = ( mIncludeUpper && mUpper >= other.mUpper )
|
||||
|| ( !mIncludeUpper && mUpper > other.mUpper )
|
||||
|| ( !mIncludeUpper && !other.mIncludeUpper && mUpper >= other.mUpper );
|
||||
if ( !upperOk )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
|
||||
/**
|
||||
* Returns true if this range contains a specified \a element.
|
||||
*/
|
||||
template<>
|
||||
bool QgsRange<QDate>::contains( QDate element ) const
|
||||
{
|
||||
if ( !element.isValid() )
|
||||
return false;
|
||||
T mLower;
|
||||
T mUpper;
|
||||
bool mIncludeLower = true;
|
||||
bool mIncludeUpper = true;
|
||||
};
|
||||
|
||||
if ( mLower.isValid() )
|
||||
{
|
||||
bool lowerOk = ( mIncludeLower && mLower <= element )
|
||||
|| ( !mIncludeLower && mLower < element );
|
||||
if ( !lowerOk )
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( mUpper.isValid() )
|
||||
{
|
||||
bool upperOk = ( mIncludeUpper && mUpper >= element )
|
||||
|| ( !mIncludeUpper && mUpper > element );
|
||||
if ( !upperOk )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this range overlaps another range.
|
||||
*/
|
||||
template<>
|
||||
bool QgsRange<QDate>::overlaps( const QgsRange<QDate> &other ) const
|
||||
{
|
||||
if ( !mUpper.isValid() && ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) )
|
||||
return true;
|
||||
|
||||
if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) )
|
||||
&& ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) )
|
||||
return true;
|
||||
|
||||
if ( ( ( mIncludeLower && mLower <= other.mLower ) || ( !mIncludeLower && mLower < other.mLower ) )
|
||||
&& ( ( mIncludeUpper && mUpper >= other.mLower ) || ( !mIncludeUpper && mUpper > other.mLower ) ) )
|
||||
return true;
|
||||
|
||||
if ( ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) )
|
||||
&& ( ( mIncludeUpper && mUpper >= other.mUpper ) || ( !mIncludeUpper && mUpper > other.mUpper ) ) )
|
||||
return true;
|
||||
|
||||
if ( ( ( mIncludeLower && mLower >= other.mLower ) || ( !mIncludeLower && mLower > other.mLower ) )
|
||||
&& ( ( mIncludeLower && mLower <= other.mUpper ) || ( !mIncludeLower && mLower < other.mUpper ) ) )
|
||||
return true;
|
||||
|
||||
if ( mLower == other.mLower && mUpper == other.mUpper )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* QgsRange which stores a range of dates.
|
||||
*
|
||||
* Invalid QDates as the lower or upper bound are permitted. In this case,
|
||||
* Invalid QDates as the beginning or end are permitted. In this case,
|
||||
* the bound is considered to be infinite. E.g. QgsDateRange(QDate(),QDate(2017,1,1))
|
||||
* is treated as a range containing all dates before 2017-1-1.
|
||||
* QgsDateRange(QDate(2017,1,1),QDate()) is treated as a range containing all dates after 2017-1-1.
|
||||
* \since QGIS 3.0
|
||||
* \see QgsIntRange
|
||||
* \see QgsDoubleRange
|
||||
* \see QgsDateTimeRange
|
||||
*/
|
||||
typedef QgsRange< QDate > QgsDateRange;
|
||||
|
||||
typedef QgsTemporalRange< QDate > QgsDateRange;
|
||||
|
||||
/**
|
||||
* QgsRange which stores a range of date times.
|
||||
*
|
||||
* Invalid QDateTimes as the beginning or end are permitted. In this case,
|
||||
* the bound is considered to be infinite. E.g. QgsDateTimeRange(QDateTime(),QDateTime(2017,1,1))
|
||||
* is treated as a range containing all dates before 2017-1-1.
|
||||
* QgsDateTimeRange(QDateTime(2017,1,1),QDateTime()) is treated as a range containing all dates after 2017-1-1.
|
||||
* \since QGIS 3.0
|
||||
* \see QgsDateRange
|
||||
*/
|
||||
typedef QgsTemporalRange< QDateTime > QgsDateTimeRange;
|
||||
|
||||
#endif // QGSRANGE_H
|
||||
|
@ -48,6 +48,21 @@ class TestQgsIntRange(unittest.TestCase):
|
||||
range = QgsIntRange(1, -1)
|
||||
self.assertTrue(range.isEmpty())
|
||||
|
||||
def testIsSingleton(self):
|
||||
range = QgsIntRange(1, 1)
|
||||
self.assertTrue(range.isSingleton())
|
||||
|
||||
range = QgsIntRange(1, 10)
|
||||
self.assertFalse(range.isSingleton())
|
||||
|
||||
range = QgsIntRange(1, 1, False, False)
|
||||
# should not be singleton because 1 is NOT included
|
||||
self.assertFalse(range.isSingleton())
|
||||
|
||||
# invalid range is not singleton
|
||||
range = QgsIntRange(1, -1)
|
||||
self.assertFalse(range.isSingleton())
|
||||
|
||||
def testContains(self):
|
||||
# includes both ends
|
||||
range = QgsIntRange(0, 10)
|
||||
@ -179,18 +194,18 @@ class TestQgsDateRange(unittest.TestCase):
|
||||
|
||||
def testGetters(self):
|
||||
range = QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2))
|
||||
self.assertEqual(range.lower(), QDate(2010, 3, 1))
|
||||
self.assertEqual(range.upper(), QDate(2010, 6, 2))
|
||||
self.assertTrue(range.includeLower())
|
||||
self.assertTrue(range.includeUpper())
|
||||
self.assertEqual(range.begin(), QDate(2010, 3, 1))
|
||||
self.assertEqual(range.end(), QDate(2010, 6, 2))
|
||||
self.assertTrue(range.includeBeginning())
|
||||
self.assertTrue(range.includeEnd())
|
||||
|
||||
range = QgsDateRange(QDate(), QDate(2010, 6, 2))
|
||||
self.assertFalse(range.lower().isValid())
|
||||
self.assertEqual(range.upper(), QDate(2010, 6, 2))
|
||||
self.assertFalse(range.begin().isValid())
|
||||
self.assertEqual(range.end(), QDate(2010, 6, 2))
|
||||
|
||||
range = QgsDateRange(QDate(2010, 3, 1), QDate())
|
||||
self.assertEqual(range.lower(), QDate(2010, 3, 1))
|
||||
self.assertFalse(range.upper().isValid())
|
||||
self.assertEqual(range.begin(), QDate(2010, 3, 1))
|
||||
self.assertFalse(range.end().isValid())
|
||||
|
||||
def testIsEmpty(self):
|
||||
range = QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2))
|
||||
@ -314,6 +329,25 @@ class TestQgsDateRange(unittest.TestCase):
|
||||
self.assertFalse(range.overlaps(QgsDateRange(QDate(2009, 4, 1), QDate(2009, 8, 5))))
|
||||
self.assertTrue(range.overlaps(QgsDateRange(QDate(2019, 4, 1), QDate(2019, 8, 5))))
|
||||
|
||||
def testIsInstant(self):
|
||||
self.assertFalse(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)).isInstant())
|
||||
self.assertTrue(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 3, 1)).isInstant())
|
||||
self.assertFalse(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 3, 1), False, False).isInstant())
|
||||
self.assertFalse(QgsDateRange(QDate(), QDate()).isInstant())
|
||||
|
||||
def testIsInfinite(self):
|
||||
self.assertFalse(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)).isInfinite())
|
||||
self.assertFalse(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 3, 1)).isInfinite())
|
||||
self.assertFalse(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 3, 1), False, False).isInfinite())
|
||||
self.assertTrue(QgsDateRange(QDate(), QDate()).isInfinite())
|
||||
|
||||
def testIsEmpty(self):
|
||||
self.assertFalse(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 6, 2)).isEmpty())
|
||||
self.assertFalse(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 3, 1)).isEmpty())
|
||||
self.assertTrue(QgsDateRange(QDate(2010, 3, 1), QDate(2010, 3, 1), False, False).isEmpty())
|
||||
self.assertTrue(QgsDateRange(QDate(2017, 3, 1), QDate(2010, 6, 2)).isEmpty())
|
||||
self.assertFalse(QgsDateRange(QDate(), QDate()).isEmpty())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user