1"""
2Temporal extent classes
3
4Usage:
5
6.. code-block:: python
7
8    >>> import grass.temporal as tgis
9    >>> from datetime import datetime
10    >>> tgis.init()
11    >>> t = tgis.RasterRelativeTime()
12    >>> t = tgis.RasterAbsoluteTime()
13
14
15(C) 2012-2013 by the GRASS Development Team
16This program is free software under the GNU General Public
17License (>=v2). Read the file COPYING that comes with GRASS
18for details.
19
20:authors: Soeren Gebbert
21"""
22from __future__ import print_function
23from .base import SQLDatabaseInterface
24from .core import init
25from datetime import datetime
26
27###############################################################################
28
29
30class TemporalExtent(SQLDatabaseInterface):
31    """This is the abstract time base class for relative and absolute time
32    objects.
33
34    It abstract class implements the interface to absolute and relative time.
35    Absolute time is represented by datetime time stamps,
36    relative time is represented by a unit an integer value.
37
38    This class implements temporal topology relationships computation
39    after [Allen and Ferguson 1994 Actions and Events in Interval Temporal Logic].
40
41    Usage:
42
43    .. code-block:: python
44
45        >>> init()
46        >>> A = TemporalExtent(table="raster_absolute_time",
47        ... ident="soil@PERMANENT", start_time=datetime(2001, 01, 01),
48        ... end_time=datetime(2005,01,01) )
49        >>> A.id
50        'soil@PERMANENT'
51        >>> A.start_time
52        datetime.datetime(2001, 1, 1, 0, 0)
53        >>> A.end_time
54        datetime.datetime(2005, 1, 1, 0, 0)
55        >>> A.print_info()
56         | Start time:................. 2001-01-01 00:00:00
57         | End time:................... 2005-01-01 00:00:00
58        >>> A.print_shell_info()
59        start_time='2001-01-01 00:00:00'
60        end_time='2005-01-01 00:00:00'
61        >>> # relative time
62        >>> A = TemporalExtent(table="raster_absolute_time",
63        ... ident="soil@PERMANENT", start_time=0, end_time=1 )
64        >>> A.id
65        'soil@PERMANENT'
66        >>> A.start_time
67        0
68        >>> A.end_time
69        1
70        >>> A.print_info()
71         | Start time:................. 0
72         | End time:................... 1
73        >>> A.print_shell_info()
74        start_time='0'
75        end_time='1'
76
77    """
78    def __init__(self, table=None, ident=None, start_time=None, end_time=None):
79
80        SQLDatabaseInterface.__init__(self, table, ident)
81
82        self.set_id(ident)
83        self.set_start_time(start_time)
84        self.set_end_time(end_time)
85
86    def intersect(self, extent):
87        """Intersect this temporal extent with the provided temporal extent and
88           return a new temporal extent with the new start and end time
89
90           :param extent: The temporal extent to intersect with
91           :return: The new temporal extent with start and end time,
92                   or None in case of no intersection
93
94           Usage:
95
96           .. code-block:: python
97
98               >>> A = TemporalExtent(start_time=5, end_time=6 )
99               >>> inter = A.intersect(A)
100               >>> inter.print_info()
101                | Start time:................. 5
102                | End time:................... 6
103
104               >>> A = TemporalExtent(start_time=5, end_time=6 )
105               >>> B = TemporalExtent(start_time=5, end_time=7 )
106               >>> inter = A.intersect(B)
107               >>> inter.print_info()
108                | Start time:................. 5
109                | End time:................... 6
110               >>> inter = B.intersect(A)
111               >>> inter.print_info()
112                | Start time:................. 5
113                | End time:................... 6
114
115               >>> A = TemporalExtent(start_time=3, end_time=6 )
116               >>> B = TemporalExtent(start_time=5, end_time=7 )
117               >>> inter = A.intersect(B)
118               >>> inter.print_info()
119                | Start time:................. 5
120                | End time:................... 6
121               >>> inter = B.intersect(A)
122               >>> inter.print_info()
123                | Start time:................. 5
124                | End time:................... 6
125
126               >>> A = TemporalExtent(start_time=3, end_time=8 )
127               >>> B = TemporalExtent(start_time=5, end_time=6 )
128               >>> inter = A.intersect(B)
129               >>> inter.print_info()
130                | Start time:................. 5
131                | End time:................... 6
132               >>> inter = B.intersect(A)
133               >>> inter.print_info()
134                | Start time:................. 5
135                | End time:................... 6
136
137               >>> A = TemporalExtent(start_time=5, end_time=8 )
138               >>> B = TemporalExtent(start_time=3, end_time=6 )
139               >>> inter = A.intersect(B)
140               >>> inter.print_info()
141                | Start time:................. 5
142                | End time:................... 6
143               >>> inter = B.intersect(A)
144               >>> inter.print_info()
145                | Start time:................. 5
146                | End time:................... 6
147
148               >>> A = TemporalExtent(start_time=5, end_time=None )
149               >>> B = TemporalExtent(start_time=3, end_time=6 )
150               >>> inter = A.intersect(B)
151               >>> inter.print_info()
152                | Start time:................. 5
153                | End time:................... None
154               >>> inter = B.intersect(A)
155               >>> inter.print_info()
156                | Start time:................. 5
157                | End time:................... None
158
159               >>> A = TemporalExtent(start_time=5, end_time=8 )
160               >>> B = TemporalExtent(start_time=3, end_time=4 )
161               >>> inter = A.intersect(B)
162               >>> print(inter)
163               None
164
165               >>> A = TemporalExtent(start_time=5, end_time=8 )
166               >>> B = TemporalExtent(start_time=3, end_time=None )
167               >>> inter = A.intersect(B)
168               >>> print(inter)
169               None
170
171        """
172        relation = self.temporal_relation(extent)
173
174        if relation == "after" or relation == "before":
175            return None
176
177        if self.D["end_time"] is None:
178            return TemporalExtent(start_time=self.D["start_time"])
179
180        if extent.D["end_time"] is None:
181            return TemporalExtent(start_time=extent.D["start_time"])
182
183        start = None
184        end = None
185
186        if self.D["start_time"] > extent.D["start_time"]:
187            start = self.D["start_time"]
188        else:
189            start = extent.D["start_time"]
190
191        if self.D["end_time"] > extent.D["end_time"]:
192            end = extent.D["end_time"]
193        else:
194            end = self.D["end_time"]
195
196        if issubclass(type(self), RelativeTemporalExtent):
197            return RelativeTemporalExtent(start_time=start, end_time=end,
198                                          unit=self.get_unit())
199        elif issubclass(type(self), AbsoluteTemporalExtent):
200            return AbsoluteTemporalExtent(start_time=start, end_time=end)
201        elif issubclass(type(self), TemporalExtent):
202            return TemporalExtent(start_time=start, end_time=end)
203
204    def disjoint_union(self, extent):
205        """Creates a disjoint union with this temporal extent and the provided one.
206           Return a new temporal extent with the new start and end time.
207
208           :param extent: The temporal extent to create a union with
209           :return: The new temporal extent with start and end time
210
211           Usage:
212
213           .. code-block:: python
214
215               >>> A = TemporalExtent(start_time=5, end_time=6 )
216               >>> inter = A.intersect(A)
217               >>> inter.print_info()
218                | Start time:................. 5
219                | End time:................... 6
220
221               >>> A = TemporalExtent(start_time=5, end_time=6 )
222               >>> B = TemporalExtent(start_time=5, end_time=7 )
223               >>> inter = A.disjoint_union(B)
224               >>> inter.print_info()
225                | Start time:................. 5
226                | End time:................... 7
227               >>> inter = B.disjoint_union(A)
228               >>> inter.print_info()
229                | Start time:................. 5
230                | End time:................... 7
231
232               >>> A = TemporalExtent(start_time=3, end_time=6 )
233               >>> B = TemporalExtent(start_time=5, end_time=7 )
234               >>> inter = A.disjoint_union(B)
235               >>> inter.print_info()
236                | Start time:................. 3
237                | End time:................... 7
238               >>> inter = B.disjoint_union(A)
239               >>> inter.print_info()
240                | Start time:................. 3
241                | End time:................... 7
242
243               >>> A = TemporalExtent(start_time=3, end_time=8 )
244               >>> B = TemporalExtent(start_time=5, end_time=6 )
245               >>> inter = A.disjoint_union(B)
246               >>> inter.print_info()
247                | Start time:................. 3
248                | End time:................... 8
249               >>> inter = B.disjoint_union(A)
250               >>> inter.print_info()
251                | Start time:................. 3
252                | End time:................... 8
253
254               >>> A = TemporalExtent(start_time=5, end_time=8 )
255               >>> B = TemporalExtent(start_time=3, end_time=6 )
256               >>> inter = A.disjoint_union(B)
257               >>> inter.print_info()
258                | Start time:................. 3
259                | End time:................... 8
260               >>> inter = B.disjoint_union(A)
261               >>> inter.print_info()
262                | Start time:................. 3
263                | End time:................... 8
264
265               >>> A = TemporalExtent(start_time=5, end_time=None )
266               >>> B = TemporalExtent(start_time=3, end_time=6 )
267               >>> inter = A.disjoint_union(B)
268               >>> inter.print_info()
269                | Start time:................. 3
270                | End time:................... 6
271               >>> inter = B.disjoint_union(A)
272               >>> inter.print_info()
273                | Start time:................. 3
274                | End time:................... 6
275
276               >>> A = TemporalExtent(start_time=5, end_time=8 )
277               >>> B = TemporalExtent(start_time=3, end_time=4 )
278               >>> inter = A.disjoint_union(B)
279               >>> inter.print_info()
280                | Start time:................. 3
281                | End time:................... 8
282               >>> inter = B.disjoint_union(A)
283               >>> inter.print_info()
284                | Start time:................. 3
285                | End time:................... 8
286               >>> A = TemporalExtent(start_time=5, end_time=8 )
287               >>> B = TemporalExtent(start_time=3, end_time=None )
288               >>> inter = A.disjoint_union(B)
289               >>> inter.print_info()
290                | Start time:................. 3
291                | End time:................... 8
292               >>> inter = B.disjoint_union(A)
293               >>> inter.print_info()
294                | Start time:................. 3
295                | End time:................... 8
296               >>> A = TemporalExtent(start_time=5, end_time=None )
297               >>> B = TemporalExtent(start_time=3, end_time=8 )
298               >>> inter = A.disjoint_union(B)
299               >>> inter.print_info()
300                | Start time:................. 3
301                | End time:................... 8
302               >>> inter = B.disjoint_union(A)
303               >>> inter.print_info()
304                | Start time:................. 3
305                | End time:................... 8
306               >>> A = TemporalExtent(start_time=5, end_time=None )
307               >>> B = TemporalExtent(start_time=3, end_time=None )
308               >>> inter = A.disjoint_union(B)
309               >>> inter.print_info()
310                | Start time:................. 3
311                | End time:................... 5
312               >>> inter = B.disjoint_union(A)
313               >>> inter.print_info()
314                | Start time:................. 3
315                | End time:................... 5
316
317               >>> A = RelativeTemporalExtent(start_time=5, end_time=None, unit="years" )
318               >>> B = RelativeTemporalExtent(start_time=3, end_time=None, unit="years" )
319               >>> inter = A.disjoint_union(B)
320               >>> inter.print_info()
321                +-------------------- Relative time -----------------------------------------+
322                | Start time:................. 3
323                | End time:................... 5
324                | Relative time unit:......... years
325
326               >>> inter = B.disjoint_union(A)
327               >>> inter.print_info()
328                +-------------------- Relative time -----------------------------------------+
329                | Start time:................. 3
330                | End time:................... 5
331                | Relative time unit:......... years
332
333
334               >>> from datetime import datetime as dt
335               >>> A = AbsoluteTemporalExtent(start_time=dt(2001,1,10), end_time=dt(2003,1,1))
336               >>> B = AbsoluteTemporalExtent(start_time=dt(2005,1,10), end_time=dt(2008,1,1))
337               >>> inter = A.disjoint_union(B)
338               >>> inter.print_info()
339                +-------------------- Absolute time -----------------------------------------+
340                | Start time:................. 2001-01-10 00:00:00
341                | End time:................... 2008-01-01 00:00:00
342
343               >>> inter = B.disjoint_union(A)
344               >>> inter.print_info()
345                +-------------------- Absolute time -----------------------------------------+
346                | Start time:................. 2001-01-10 00:00:00
347                | End time:................... 2008-01-01 00:00:00
348
349        """
350
351        start = None
352        end = None
353
354        if self.D["start_time"] < extent.D["start_time"]:
355            start = self.D["start_time"]
356        else:
357            start = extent.D["start_time"]
358
359        # End time handling
360        if self.D["end_time"] is None and extent.D["end_time"] is None:
361            if self.D["start_time"] > extent.D["start_time"]:
362                end = self.D["start_time"]
363            else:
364                end = extent.D["start_time"]
365        elif self.D["end_time"] is None:
366            if self.D["start_time"] > extent.D["end_time"]:
367                end = self.D["start_time"]
368            else:
369                end = extent.D["end_time"]
370        elif extent.D["end_time"] is None:
371            if self.D["end_time"] > extent.D["start_time"]:
372                end = self.D["end_time"]
373            else:
374                end = extent.D["start_time"]
375        elif self.D["end_time"] < extent.D["end_time"]:
376            end = extent.D["end_time"]
377        else:
378            end = self.D["end_time"]
379
380        if issubclass(type(self), RelativeTemporalExtent):
381            return RelativeTemporalExtent(start_time=start, end_time=end,
382                                          unit=self.get_unit())
383        elif issubclass(type(self), AbsoluteTemporalExtent):
384            return AbsoluteTemporalExtent(start_time=start, end_time=end)
385        elif issubclass(type(self), TemporalExtent):
386            return TemporalExtent(start_time=start, end_time=end)
387
388    def union(self, extent):
389        """Creates a union with this temporal extent and the provided one.
390           Return a new temporal extent with the new start and end time.
391
392           :param extent: The temporal extent to create a union with
393           :return: The new temporal extent with start and end time,
394                    or None in case the temporal extents are unrelated
395                    (before or after)
396
397           .. code-block:: python
398
399               >>> A = TemporalExtent(start_time=5, end_time=8 )
400               >>> B = TemporalExtent(start_time=3, end_time=4 )
401               >>> inter = A.intersect(B)
402               >>> print(inter)
403               None
404
405               >>> A = TemporalExtent(start_time=5, end_time=8 )
406               >>> B = TemporalExtent(start_time=3, end_time=None )
407               >>> inter = A.intersect(B)
408               >>> print(inter)
409               None
410
411        """
412
413        relation = self.temporal_relation(extent)
414
415        if relation == "after" or relation == "before":
416            return None
417
418        return self.disjoint_union(extent)
419
420    def starts(self, extent):
421        """Return True if this temporal extent (A) starts at the start of the
422           provided temporal extent (B) and finishes within it
423           ::
424
425               A  |-----|
426               B  |---------|
427
428
429           :param extent: The temporal extent object with which this extent
430                          starts
431
432           Usage:
433
434           .. code-block:: python
435
436               >>> A = TemporalExtent(start_time=5, end_time=6 )
437               >>> B = TemporalExtent(start_time=5, end_time=7 )
438               >>> A.starts(B)
439               True
440               >>> B.starts(A)
441               False
442
443        """
444        if self.D["end_time"] is None or extent.D["end_time"] is None:
445            return False
446
447        if self.D["start_time"] == extent.D["start_time"] and \
448           self.D["end_time"] < extent.D["end_time"]:
449            return True
450        else:
451            return False
452
453    def started(self, extent):
454        """Return True if this temporal extent (A) started at the start of the
455           provided temporal extent (B) and finishes after it
456           ::
457
458               A  |---------|
459               B  |-----|
460
461           :param extent: The temporal extent object with which this extent
462                          started
463
464           Usage:
465
466           .. code-block:: python
467
468               >>> A = TemporalExtent(start_time=5, end_time=7 )
469               >>> B = TemporalExtent(start_time=5, end_time=6 )
470               >>> A.started(B)
471               True
472               >>> B.started(A)
473               False
474
475        """
476        if self.D["end_time"] is None or extent.D["end_time"] is None:
477            return False
478
479        if self.D["start_time"] == extent.D["start_time"] and \
480           self.D["end_time"] > extent.D["end_time"]:
481            return True
482        else:
483            return False
484
485    def finishes(self, extent):
486        """Return True if this temporal extent (A) starts after the start of
487           the provided temporal extent (B) and finishes with it
488           ::
489
490               A      |-----|
491               B  |---------|
492
493           :param extent: The temporal extent object with which this extent
494                          finishes
495
496           Usage:
497
498           .. code-block:: python
499
500               >>> A = TemporalExtent(start_time=6, end_time=7 )
501               >>> B = TemporalExtent(start_time=5, end_time=7 )
502               >>> A.finishes(B)
503               True
504               >>> B.finishes(A)
505               False
506
507        """
508        if self.D["end_time"] is None or extent.D["end_time"] is None:
509            return False
510
511        if self.D["end_time"] == extent.D["end_time"] and \
512           self.D["start_time"] > extent.D["start_time"]:
513            return True
514        else:
515            return False
516
517    def finished(self, extent):
518        """Return True if this temporal extent (A) starts before the start of
519           the provided temporal extent (B) and finishes with it
520           ::
521
522               A  |---------|
523               B      |-----|
524
525           :param extent: The temporal extent object with which this extent
526                          finishes
527
528           Usage:
529
530           .. code-block:: python
531
532               >>> A = TemporalExtent(start_time=5, end_time=7 )
533               >>> B = TemporalExtent(start_time=6, end_time=7 )
534               >>> A.finished(B)
535               True
536               >>> B.finished(A)
537               False
538
539        """
540        if self.D["end_time"] is None or extent.D["end_time"] is None:
541            return False
542
543        if self.D["end_time"] == extent.D["end_time"] and \
544           self.D["start_time"] < extent.D["start_time"]:
545            return True
546        else:
547            return False
548
549    def after(self, extent):
550        """Return True if this temporal extent (A) is located after the
551           provided temporal extent (B)
552           ::
553
554               A             |---------|
555               B  |---------|
556
557           :param extent: The temporal extent object that is located before
558                          this extent
559
560           Usage:
561
562           .. code-block:: python
563
564               >>> A = TemporalExtent(start_time=8, end_time=9 )
565               >>> B = TemporalExtent(start_time=6, end_time=7 )
566               >>> A.after(B)
567               True
568               >>> B.after(A)
569               False
570
571        """
572        if extent.D["end_time"] is None:
573            if self.D["start_time"] > extent.D["start_time"]:
574                return True
575            else:
576                return False
577
578        if self.D["start_time"] > extent.D["end_time"]:
579            return True
580        else:
581            return False
582
583    def before(self, extent):
584        """Return True if this temporal extent (A) is located before the
585           provided temporal extent (B)
586           ::
587
588               A  |---------|
589               B             |---------|
590
591           :param extent: The temporal extent object that is located after
592                          this extent
593
594           Usage:
595
596           .. code-block:: python
597
598               >>> A = TemporalExtent(start_time=6, end_time=7 )
599               >>> B = TemporalExtent(start_time=8, end_time=9 )
600               >>> A.before(B)
601               True
602               >>> B.before(A)
603               False
604
605        """
606        if self.D["end_time"] is None:
607            if self.D["start_time"] < extent.D["start_time"]:
608                return True
609            else:
610                return False
611
612        if self.D["end_time"] < extent.D["start_time"]:
613            return True
614        else:
615            return False
616
617    def adjacent(self, extent):
618        """Return True if this temporal extent (A) is a meeting neighbor the
619           provided temporal extent (B)
620           ::
621
622               A            |---------|
623               B  |---------|
624               A  |---------|
625               B            |---------|
626
627           :param extent: The temporal extent object that is a meeting neighbor
628                          of this extent
629
630           Usage:
631
632           .. code-block:: python
633
634               >>> A = TemporalExtent(start_time=5, end_time=7 )
635               >>> B = TemporalExtent(start_time=7, end_time=9 )
636               >>> A.adjacent(B)
637               True
638               >>> B.adjacent(A)
639               True
640               >>> A = TemporalExtent(start_time=5, end_time=7 )
641               >>> B = TemporalExtent(start_time=3, end_time=5 )
642               >>> A.adjacent(B)
643               True
644               >>> B.adjacent(A)
645               True
646
647        """
648        if self.D["end_time"] is None and extent.D["end_time"] is None:
649            return False
650
651        if (self.D["start_time"] == extent.D["end_time"]) or \
652           (self.D["end_time"] == extent.D["start_time"]):
653            return True
654        else:
655            return False
656
657    def follows(self, extent):
658        """Return True if this temporal extent (A) follows the
659           provided temporal extent (B)
660           ::
661
662               A            |---------|
663               B  |---------|
664
665           :param extent: The temporal extent object that is the predecessor
666                          of this extent
667
668           Usage:
669
670           .. code-block:: python
671
672               >>> A = TemporalExtent(start_time=5, end_time=7 )
673               >>> B = TemporalExtent(start_time=3, end_time=5 )
674               >>> A.follows(B)
675               True
676               >>> B.follows(A)
677               False
678
679        """
680        if extent.D["end_time"] is None:
681            return False
682
683        if self.D["start_time"] == extent.D["end_time"]:
684            return True
685        else:
686            return False
687
688    def precedes(self, extent):
689        """Return True if this temporal extent (A) precedes the provided
690           temporal extent (B)
691           ::
692
693               A  |---------|
694               B            |---------|
695
696
697           :param extent: The temporal extent object that is the successor
698                          of this extent
699
700           Usage:
701
702           .. code-block:: python
703
704               >>> A = TemporalExtent(start_time=5, end_time=7 )
705               >>> B = TemporalExtent(start_time=7, end_time=9 )
706               >>> A.precedes(B)
707               True
708               >>> B.precedes(A)
709               False
710
711
712        """
713        if self.D["end_time"] is None:
714            return False
715
716        if self.D["end_time"] == extent.D["start_time"]:
717            return True
718        else:
719            return False
720
721    def during(self, extent):
722        """Return True if this temporal extent (A) is located during the provided
723           temporal extent (B)
724           ::
725
726               A   |-------|
727               B  |---------|
728
729           :param extent: The temporal extent object that contains this extent
730
731           Usage:
732
733           .. code-block:: python
734
735               >>> A = TemporalExtent(start_time=5, end_time=7 )
736               >>> B = TemporalExtent(start_time=4, end_time=9 )
737               >>> A.during(B)
738               True
739               >>> B.during(A)
740               False
741
742        """
743        # Check single point of time in interval
744        if extent.D["end_time"] is None:
745            return False
746
747        # Check single point of time in interval
748        if self.D["end_time"] is None:
749            if self.D["start_time"] >= extent.D["start_time"] and \
750               self.D["start_time"] < extent.D["end_time"]:
751                return True
752            else:
753                return False
754
755        if self.D["start_time"] > extent.D["start_time"] and \
756           self.D["end_time"] < extent.D["end_time"]:
757            return True
758        else:
759            return False
760
761    def contains(self, extent):
762        """Return True if this temporal extent (A) contains the provided
763           temporal extent (B)
764           ::
765
766               A  |---------|
767               B   |-------|
768
769           :param extent: The temporal extent object that is located
770                          during this extent
771
772           Usage:
773
774           .. code-block:: python
775
776               >>> A = TemporalExtent(start_time=4, end_time=9 )
777               >>> B = TemporalExtent(start_time=5, end_time=8 )
778               >>> A.contains(B)
779               True
780               >>> B.contains(A)
781               False
782
783        """
784        # Check single point of time in interval
785        if self.D["end_time"] is None:
786            return False
787
788        # Check single point of time in interval
789        if extent.D["end_time"] is None:
790            if self.D["start_time"] <= extent.D["start_time"] and \
791               self.D["end_time"] > extent.D["start_time"]:
792                return True
793            else:
794                return False
795
796        if self.D["start_time"] < extent.D["start_time"] and \
797           self.D["end_time"] > extent.D["end_time"]:
798            return True
799        else:
800            return False
801
802    def equal(self, extent):
803        """Return True if this temporal extent (A) is equal to the provided
804           temporal extent (B)
805           ::
806
807               A  |---------|
808               B  |---------|
809
810           :param extent: The temporal extent object that is equal
811                          during this extent
812
813           Usage:
814
815           .. code-block:: python
816
817               >>> A = TemporalExtent(start_time=5, end_time=6 )
818               >>> B = TemporalExtent(start_time=5, end_time=6 )
819               >>> A.equal(B)
820               True
821               >>> B.equal(A)
822               True
823
824        """
825        if self.D["end_time"] is None and extent.D["end_time"] is None:
826            if self.D["start_time"] == extent.D["start_time"]:
827                return True
828            else:
829                return False
830
831        if self.D["end_time"] is None or extent.D["end_time"] is None:
832            return False
833
834        if self.D["start_time"] == extent.D["start_time"] and \
835           self.D["end_time"] == extent.D["end_time"]:
836            return True
837        else:
838            return False
839
840    def overlaps(self, extent):
841        """Return True if this temporal extent (A) overlapped the provided
842           temporal extent (B)
843           ::
844
845               A  |---------|
846               B    |---------|
847
848           :param extent: The temporal extent object that is overlaps
849                          this extent
850
851           Usage:
852
853           .. code-block:: python
854
855               >>> A = TemporalExtent(start_time=5, end_time=7 )
856               >>> B = TemporalExtent(start_time=6, end_time=8 )
857               >>> A.overlaps(B)
858               True
859               >>> B.overlaps(A)
860               False
861
862               >>> A = TemporalExtent(start_time=5, end_time=6 )
863               >>> B = TemporalExtent(start_time=6, end_time=8 )
864               >>> A.overlaps(B)
865               False
866               >>> B.overlaps(A)
867               False
868
869        """
870        if self.D["end_time"] is None or extent.D["end_time"] is None:
871            return False
872
873        if self.D["start_time"] < extent.D["start_time"] and \
874           self.D["end_time"] < extent.D["end_time"] and \
875           self.D["end_time"] > extent.D["start_time"]:
876            return True
877        else:
878            return False
879
880    def overlapped(self, extent):
881        """Return True if this temporal extent (A) overlapps the provided
882           temporal extent (B)
883           ::
884
885               A    |---------|
886               B  |---------|
887
888
889           :param extent: The temporal extent object that is overlapped
890                          this extent
891
892           Usage:
893
894           .. code-block:: python
895
896               >>> A = TemporalExtent(start_time=6, end_time=8 )
897               >>> B = TemporalExtent(start_time=5, end_time=7 )
898               >>> A.overlapped(B)
899               True
900               >>> B.overlapped(A)
901               False
902
903               >>> A = TemporalExtent(start_time=6, end_time=8 )
904               >>> B = TemporalExtent(start_time=5, end_time=6 )
905               >>> A.overlapped(B)
906               False
907               >>> B.overlapped(A)
908               False
909
910        """
911        if self.D["end_time"] is None or extent.D["end_time"] is None:
912            return False
913
914        if self.D["start_time"] > extent.D["start_time"] and \
915           self.D["end_time"] > extent.D["end_time"] and \
916           self.D["start_time"] < extent.D["end_time"]:
917            return True
918        else:
919            return False
920
921    def temporal_relation(self, extent):
922        """Returns the temporal relation between temporal objects
923        Temporal relationships are implemented after
924        [Allen and Ferguson 1994 Actions and Events in Interval Temporal Logic]
925
926        The following temporal relationships are supported:
927
928            - equal
929            - during
930            - contains
931            - overlaps
932            - overlapped
933            - after
934            - before
935            - starts
936            - finishes
937            - started
938            - finished
939            - follows
940            - precedes
941
942        :param extent: The temporal extent
943        :return: The name of the temporal relation or None if no relation
944                 found
945        """
946
947        # First check for correct time
948        if "start_time" not in self.D:
949            return None
950        if "end_time" not in self.D:
951            return None
952        if "start_time" not in extent.D:
953            return None
954        if "end_time" not in extent.D:
955            return None
956        # Return None if the start_time is undefined
957        if self.D["start_time"] is None or extent.D["start_time"] is None:
958            return None
959
960        if self.equal(extent):
961            return "equal"
962        if self.during(extent):
963            return "during"
964        if self.contains(extent):
965            return "contains"
966        if self.overlaps(extent):
967            return "overlaps"
968        if self.overlapped(extent):
969            return "overlapped"
970        if self.after(extent):
971            return "after"
972        if self.before(extent):
973            return "before"
974        if self.starts(extent):
975            return "starts"
976        if self.finishes(extent):
977            return "finishes"
978        if self.started(extent):
979            return "started"
980        if self.finished(extent):
981            return "finished"
982        if self.follows(extent):
983            return "follows"
984        if self.precedes(extent):
985            return "precedes"
986        return None
987
988    def set_id(self, ident):
989        """Convenient method to set the unique identifier (primary key)"""
990        self.ident = ident
991        self.D["id"] = ident
992
993    def set_start_time(self, start_time):
994        """Set the valid start time of the extent"""
995        self.D["start_time"] = start_time
996
997    def set_end_time(self, end_time):
998        """Set the valid end time of the extent"""
999        self.D["end_time"] = end_time
1000
1001    def get_id(self):
1002        """Convenient method to get the unique identifier (primary key)
1003           :return: None if not found
1004        """
1005        if "id" in self.D:
1006            return self.D["id"]
1007        else:
1008            return None
1009
1010    def get_start_time(self):
1011        """Get the valid start time of the extent
1012           :return: None if not found"""
1013        if "start_time" in self.D:
1014            return self.D["start_time"]
1015        else:
1016            return None
1017
1018    def get_end_time(self):
1019        """Get the valid end time of the extent
1020           :return: None if not found"""
1021        if "end_time" in self.D:
1022            return self.D["end_time"]
1023        else:
1024            return None
1025
1026    # Set the properties
1027    id = property(fget=get_id, fset=set_id)
1028    start_time = property(fget=get_start_time, fset=set_start_time)
1029    end_time = property(fget=get_end_time, fset=set_end_time)
1030
1031    def print_info(self):
1032        """Print information about this class in human readable style"""
1033        #      0123456789012345678901234567890
1034        print(" | Start time:................. " + str(self.get_start_time()))
1035        print(" | End time:................... " + str(self.get_end_time()))
1036
1037    def print_shell_info(self):
1038        """Print information about this class in shell style"""
1039        print("start_time='{}'".format(str(self.get_start_time())))
1040        print("end_time='{}'".format(str(self.get_end_time())))
1041
1042###############################################################################
1043
1044
1045class AbsoluteTemporalExtent(TemporalExtent):
1046    """This is the absolute time class for all maps and spacetime datasets
1047
1048        start_time and end_time must be of type datetime
1049    """
1050    def __init__(self, table=None, ident=None, start_time=None, end_time=None):
1051
1052        TemporalExtent.__init__(
1053            self, table, ident, start_time, end_time)
1054
1055    def print_info(self):
1056        """Print information about this class in human readable style"""
1057        #      0123456789012345678901234567890
1058        print(" +-------------------- Absolute time -----------------------------------------+")
1059        TemporalExtent.print_info(self)
1060
1061    def print_shell_info(self):
1062        """Print information about this class in shell style"""
1063        TemporalExtent.print_shell_info(self)
1064
1065###############################################################################
1066
1067
1068class RasterAbsoluteTime(AbsoluteTemporalExtent):
1069    def __init__(self, ident=None, start_time=None, end_time=None):
1070        AbsoluteTemporalExtent.__init__(self, "raster_absolute_time",
1071                                        ident, start_time, end_time)
1072
1073
1074class Raster3DAbsoluteTime(AbsoluteTemporalExtent):
1075    def __init__(self, ident=None, start_time=None, end_time=None):
1076        AbsoluteTemporalExtent.__init__(self, "raster3d_absolute_time",
1077                                        ident, start_time, end_time)
1078
1079
1080class VectorAbsoluteTime(AbsoluteTemporalExtent):
1081    def __init__(self, ident=None, start_time=None, end_time=None):
1082        AbsoluteTemporalExtent.__init__(self, "vector_absolute_time",
1083                                        ident, start_time, end_time)
1084
1085###############################################################################
1086
1087
1088class STDSAbsoluteTime(AbsoluteTemporalExtent):
1089    """This class implements the absolute time extent for space time dataset
1090
1091        In addition to the existing functionality the granularity and the
1092        map_time are added.
1093
1094        Usage:
1095
1096        .. code-block:: python
1097
1098            >>> init()
1099            >>> A = STDSAbsoluteTime(table="strds_absolute_time",
1100            ... ident="strds@PERMANENT", start_time=datetime(2001, 01, 01),
1101            ... end_time=datetime(2005,01,01), granularity="1 days",
1102            ... map_time="interval")
1103            >>> A.id
1104            'strds@PERMANENT'
1105            >>> A.start_time
1106            datetime.datetime(2001, 1, 1, 0, 0)
1107            >>> A.end_time
1108            datetime.datetime(2005, 1, 1, 0, 0)
1109            >>> A.granularity
1110            '1 days'
1111            >>> A.map_time
1112            'interval'
1113            >>> A.print_info()
1114             +-------------------- Absolute time -----------------------------------------+
1115             | Start time:................. 2001-01-01 00:00:00
1116             | End time:................... 2005-01-01 00:00:00
1117             | Granularity:................ 1 days
1118             | Temporal type of maps:...... interval
1119            >>> A.print_shell_info()
1120            start_time='2001-01-01 00:00:00'
1121            end_time='2005-01-01 00:00:00'
1122            granularity='1 days'
1123            map_time=interval
1124
1125    """
1126    def __init__(self, table=None, ident=None, start_time=None, end_time=None,
1127                 granularity=None, map_time=None):
1128        AbsoluteTemporalExtent.__init__(
1129            self, table, ident, start_time, end_time)
1130
1131        self.set_granularity(granularity)
1132        self.set_map_time(map_time)
1133
1134    def set_granularity(self, granularity):
1135        """Set the granularity of the space time dataset"""
1136        self.D["granularity"] = granularity
1137
1138    def set_map_time(self, map_time):
1139        """Set the type of the map time
1140
1141           Registered maps may have different types of time:
1142
1143           - Single point of time "point"
1144           - Time intervals "interval"
1145           - Single point and interval time "mixed"
1146
1147           This variable will be set automatically when maps are registered.
1148        """
1149        self.D["map_time"] = map_time
1150
1151    def get_granularity(self):
1152        """Get the granularity of the space time dataset
1153           :return: None if not found"""
1154        if "granularity" in self.D:
1155            return self.D["granularity"]
1156        else:
1157            return None
1158
1159    def get_map_time(self):
1160        """Get the type of the map time
1161
1162           Registered maps may have different types of time:
1163
1164           - Single point of time "point"
1165           - Time intervals "interval"
1166           - Single point and interval time "mixed"
1167
1168           This variable will be set automatically when maps are registered.
1169        """
1170        if "map_time" in self.D:
1171            return self.D["map_time"]
1172        else:
1173            return None
1174
1175    # Properties
1176    granularity = property(fget=get_granularity, fset=set_granularity)
1177    map_time = property(fget=get_map_time, fset=set_map_time)
1178
1179    def print_info(self):
1180        """Print information about this class in human readable style"""
1181        AbsoluteTemporalExtent.print_info(self)
1182        #      0123456789012345678901234567890
1183        print(" | Granularity:................ " + str(self.get_granularity()))
1184        print(" | Temporal type of maps:...... " + str(self.get_map_time()))
1185
1186    def print_shell_info(self):
1187        """Print information about this class in shell style"""
1188        AbsoluteTemporalExtent.print_shell_info(self)
1189        print("granularity='{}'".format(str(self.get_granularity())))
1190        print("map_time=" + str(self.get_map_time()))
1191
1192###############################################################################
1193
1194
1195class STRDSAbsoluteTime(STDSAbsoluteTime):
1196    def __init__(self, ident=None, start_time=None, end_time=None,
1197                 granularity=None):
1198        STDSAbsoluteTime.__init__(self, "strds_absolute_time",
1199                                  ident, start_time, end_time, granularity)
1200
1201
1202class STR3DSAbsoluteTime(STDSAbsoluteTime):
1203    def __init__(self, ident=None, start_time=None, end_time=None,
1204                 granularity=None):
1205        STDSAbsoluteTime.__init__(self, "str3ds_absolute_time",
1206                                  ident, start_time, end_time, granularity)
1207
1208
1209class STVDSAbsoluteTime(STDSAbsoluteTime):
1210    def __init__(self, ident=None, start_time=None, end_time=None,
1211                 granularity=None):
1212        STDSAbsoluteTime.__init__(self, "stvds_absolute_time",
1213                                  ident, start_time, end_time, granularity)
1214
1215###############################################################################
1216
1217
1218class RelativeTemporalExtent(TemporalExtent):
1219    """This is the relative time class for all maps and space time datasets
1220
1221        start_time and end_time must be of type integer
1222
1223        Usage:
1224
1225        .. code-block:: python
1226
1227            >>> init()
1228            >>> A = RelativeTemporalExtent(table="raster_relative_time",
1229            ... ident="soil@PERMANENT", start_time=0, end_time=1, unit="years")
1230            >>> A.id
1231            'soil@PERMANENT'
1232            >>> A.start_time
1233            0
1234            >>> A.end_time
1235            1
1236            >>> A.unit
1237            'years'
1238            >>> A.print_info()
1239             +-------------------- Relative time -----------------------------------------+
1240             | Start time:................. 0
1241             | End time:................... 1
1242             | Relative time unit:......... years
1243            >>> A.print_shell_info()
1244            start_time='0'
1245            end_time='1'
1246            unit=years
1247
1248    """
1249    def __init__(self, table=None, ident=None, start_time=None, end_time=None,
1250                 unit=None):
1251
1252        TemporalExtent.__init__(
1253            self, table, ident, start_time, end_time)
1254        self.set_unit(unit)
1255
1256    def set_unit(self, unit):
1257        """Set the unit of the relative time. Valid units are:
1258
1259           - years
1260           - months
1261           - days
1262           - hours
1263           - minutes
1264           - seconds
1265        """
1266        self.D["unit"] = unit
1267
1268    def get_unit(self):
1269        """Get the unit of the relative time
1270           :return: None if not found"""
1271        if "unit" in self.D:
1272            return self.D["unit"]
1273        else:
1274            return None
1275
1276    def temporal_relation(self, map):
1277        """Returns the temporal relation between temporal objects
1278           Temporal relationships are implemented after
1279           [Allen and Ferguson 1994 Actions and Events in Interval Temporal Logic]
1280        """
1281
1282        # Check units for relative time
1283        if "unit" not in self.D:
1284            return None
1285        if "unit" not in map.D:
1286            return None
1287
1288        # Units must be equal
1289        if self.D["unit"] != map.D["unit"]:
1290            return None
1291
1292        return TemporalExtent.temporal_relation(self, map)
1293
1294    # Properties
1295    unit = property(fget=get_unit, fset=set_unit)
1296
1297    def print_info(self):
1298        """Print information about this class in human readable style"""
1299        #      0123456789012345678901234567890
1300        print(" +-------------------- Relative time -----------------------------------------+")
1301        TemporalExtent.print_info(self)
1302        print(" | Relative time unit:......... " + str(self.get_unit()))
1303
1304    def print_shell_info(self):
1305        """Print information about this class in shell style"""
1306        TemporalExtent.print_shell_info(self)
1307        print("unit=" + str(self.get_unit()))
1308
1309###############################################################################
1310
1311
1312class RasterRelativeTime(RelativeTemporalExtent):
1313    def __init__(self, ident=None, start_time=None, end_time=None,
1314                 unit=None):
1315        RelativeTemporalExtent.__init__(self, "raster_relative_time", ident,
1316                                        start_time, end_time, unit)
1317
1318
1319class Raster3DRelativeTime(RelativeTemporalExtent):
1320    def __init__(self, ident=None, start_time=None, end_time=None,
1321                 unit=None):
1322        RelativeTemporalExtent.__init__(self, "raster3d_relative_time", ident,
1323                                        start_time, end_time, unit)
1324
1325
1326class VectorRelativeTime(RelativeTemporalExtent):
1327    def __init__(self, ident=None, start_time=None, end_time=None,
1328                 unit=None):
1329        RelativeTemporalExtent.__init__(
1330            self, "vector_relative_time", ident, start_time, end_time, unit)
1331
1332###############################################################################
1333
1334
1335class STDSRelativeTime(RelativeTemporalExtent):
1336    """This is the relative time class for all maps and space time datasets
1337
1338        start_time and end_time must be of type integer
1339
1340        Usage:
1341
1342        .. code-block:: python
1343
1344            >>> init()
1345            >>> A = STDSRelativeTime(table="strds_relative_time",
1346            ... ident="strds@PERMANENT", start_time=0, end_time=1, unit="years",
1347            ... granularity=5, map_time="interval")
1348            >>> A.id
1349            'strds@PERMANENT'
1350            >>> A.start_time
1351            0
1352            >>> A.end_time
1353            1
1354            >>> A.unit
1355            'years'
1356            >>> A.granularity
1357            5
1358            >>> A.map_time
1359            'interval'
1360            >>> A.print_info()
1361             +-------------------- Relative time -----------------------------------------+
1362             | Start time:................. 0
1363             | End time:................... 1
1364             | Relative time unit:......... years
1365             | Granularity:................ 5
1366             | Temporal type of maps:...... interval
1367            >>> A.print_shell_info()
1368            start_time='0'
1369            end_time='1'
1370            unit=years
1371            granularity=5
1372            map_time=interval
1373
1374    """
1375    def __init__(self, table=None, ident=None, start_time=None, end_time=None,
1376                 unit=None, granularity=None, map_time=None):
1377        RelativeTemporalExtent.__init__(
1378            self, table, ident, start_time, end_time, unit)
1379
1380        self.set_granularity(granularity)
1381        self.set_map_time(map_time)
1382
1383    def set_granularity(self, granularity):
1384        """Set the granularity of the space time dataset"""
1385        self.D["granularity"] = granularity
1386
1387    def set_map_time(self, map_time):
1388        """Set the type of the map time
1389
1390           Registered maps may have different types of time:
1391
1392           - Single point of time "point"
1393           - Time intervals "interval"
1394           - Single point and interval time "mixed"
1395
1396           This variable will be set automatically when maps are registered.
1397        """
1398        self.D["map_time"] = map_time
1399
1400    def get_granularity(self):
1401        """Get the granularity of the space time dataset
1402           :return: None if not found"""
1403        if "granularity" in self.D:
1404            return self.D["granularity"]
1405        else:
1406            return None
1407
1408    def get_map_time(self):
1409        """Get the type of the map time
1410
1411           Registered maps may have different types of time:
1412
1413           - Single point of time "point"
1414           - Time intervals "interval"
1415           - Single point and interval time "mixed"
1416
1417           This variable will be set automatically when maps are registered.
1418        """
1419        if "map_time" in self.D:
1420            return self.D["map_time"]
1421        else:
1422            return None
1423
1424    # Properties
1425    granularity = property(fget=get_granularity, fset=set_granularity)
1426    map_time = property(fget=get_map_time, fset=set_map_time)
1427
1428    def print_info(self):
1429        """Print information about this class in human readable style"""
1430        RelativeTemporalExtent.print_info(self)
1431        #      0123456789012345678901234567890
1432        print(" | Granularity:................ " + str(self.get_granularity()))
1433        print(" | Temporal type of maps:...... " + str(self.get_map_time()))
1434
1435    def print_shell_info(self):
1436        """Print information about this class in shell style"""
1437        RelativeTemporalExtent.print_shell_info(self)
1438        print("granularity=" + str(self.get_granularity()))
1439        print("map_time=" + str(self.get_map_time()))
1440
1441###############################################################################
1442
1443
1444class STRDSRelativeTime(STDSRelativeTime):
1445    def __init__(self, ident=None, start_time=None, end_time=None,
1446                 unit=None, granularity=None, map_time=None):
1447        STDSRelativeTime.__init__(self, "strds_relative_time", ident,
1448                                  start_time, end_time, unit, granularity,
1449                                  map_time)
1450
1451
1452class STR3DSRelativeTime(STDSRelativeTime):
1453    def __init__(self, ident=None, start_time=None, end_time=None,
1454                 unit=None, granularity=None, map_time=None):
1455        STDSRelativeTime.__init__(self, "str3ds_relative_time", ident,
1456                                  start_time, end_time, unit, granularity,
1457                                  map_time)
1458
1459
1460class STVDSRelativeTime(STDSRelativeTime):
1461    def __init__(self, ident=None, start_time=None, end_time=None,
1462                 unit=None, granularity=None, map_time=None):
1463        STDSRelativeTime.__init__(self, "stvds_relative_time", ident,
1464                                  start_time, end_time, unit, granularity,
1465                                  map_time)
1466
1467###############################################################################
1468
1469if __name__ == "__main__":
1470    import doctest
1471    doctest.testmod()
1472