1"""
2Class to build the spatio-temporal topology between map lists
3
4Usage:
5
6.. code-block:: python
7
8    import grass.temporal as tgis
9
10    tgis.print_temporal_relations(maps)
11
12
13(C) 2012-2013 by the GRASS Development Team
14This program is free software under the GNU General Public
15License (>=v2). Read the file COPYING that comes with GRASS
16for details.
17
18:authors: Soeren Gebbert
19"""
20from __future__ import print_function
21from datetime import datetime
22from .core import init_dbif
23from .abstract_dataset import AbstractDatasetComparisonKeyStartTime
24from .datetime_math import time_delta_to_relative_time_seconds
25import grass.lib.vector as vector
26import grass.lib.rtree as rtree
27import grass.lib.gis as gis
28
29###############################################################################
30
31
32class SpatioTemporalTopologyBuilder(object):
33    """This class is designed to build the spatio-temporal topology
34       of spatio-temporally related abstract dataset objects.
35
36       The abstract dataset objects must be provided as a single list, or in
37       two lists.
38
39        Example:
40
41        .. code-block:: python
42
43            # We have a space time raster dataset and build a map list
44            # from all registered maps ordered by start time
45            maps = strds.get_registered_maps_as_objects()
46
47            # Now lets build the temporal topology of the maps in the list
48
49            tb = SpatioTemporalTopologyBuilder()
50
51            tb.build(maps)
52
53            dbif, connected = init_dbif(None)
54
55            for map in tb:
56                map.select(dbif)
57                map.print_info()
58
59            # Same can be done with the existing map list
60            # But be aware that this is might not be temporally ordered
61            for map in maps:
62                map.select(dbf)
63                map.print_info()
64
65            # Using the next and previous methods, we can iterate over the
66            # topological related maps in this way
67
68            first = tb.get_first()
69
70            while first:
71                first.print_topology_info()
72                first = first.next()
73
74            # Dictionary like accessed
75            map = tb["name@mapset"]
76
77            >>> # Example with two lists of maps
78            >>> import grass.temporal as tgis
79            >>> import datetime
80            >>> # Create two list of maps with equal time stamps
81            >>> mapsA = []
82            >>> mapsB = []
83            >>> for i in range(4):
84            ...     idA = "a%i@B"%(i)
85            ...     mapA = tgis.RasterDataset(idA)
86            ...     idB = "b%i@B"%(i)
87            ...     mapB = tgis.RasterDataset(idB)
88            ...     check = mapA.set_relative_time(i, i + 1, "months")
89            ...     check = mapB.set_relative_time(i, i + 1, "months")
90            ...     mapsA.append(mapA)
91            ...     mapsB.append(mapB)
92            >>> # Build the topology between the two map lists
93            >>> tb = SpatioTemporalTopologyBuilder()
94            >>> tb.build(mapsA, mapsB, None)
95            >>> # Check relations of mapsA
96            >>> for map in mapsA:
97            ...     if map.get_equal():
98            ...         relations = map.get_equal()
99            ...         print("Map %s has equal relation to map %s"%(map.get_name(),
100            ...               relations[0].get_name()))
101            Map a0 has equal relation to map b0
102            Map a1 has equal relation to map b1
103            Map a2 has equal relation to map b2
104            Map a3 has equal relation to map b3
105            >>> # Check relations of mapsB
106            >>> for map in mapsB:
107            ...     if map.get_equal():
108            ...         relations = map.get_equal()
109            ...         print("Map %s has equal relation to map %s"%(map.get_name(),
110            ...               relations[0].get_name()))
111            Map b0 has equal relation to map a0
112            Map b1 has equal relation to map a1
113            Map b2 has equal relation to map a2
114            Map b3 has equal relation to map a3
115
116
117            >>> mapsA = []
118            >>> mapsB = []
119            >>> for i in range(4):
120            ...     idA = "a%i@B"%(i)
121            ...     mapA = tgis.RasterDataset(idA)
122            ...     idB = "b%i@B"%(i)
123            ...     mapB = tgis.RasterDataset(idB)
124            ...     check = mapA.set_relative_time(i, i + 1, "months")
125            ...     check = mapB.set_relative_time(i + 1, i + 2, "months")
126            ...     mapsA.append(mapA)
127            ...     mapsB.append(mapB)
128            >>> # Build the topology between the two map lists
129            >>> tb = SpatioTemporalTopologyBuilder()
130            >>> tb.build(mapsA, mapsB, None)
131            >>> # Check relations of mapsA
132            >>> for map in mapsA:
133            ...     print(map.get_temporal_extent_as_tuple())
134            ...     m = map.get_temporal_relations()
135            ...     for key in m.keys():
136            ...         if key not in ["NEXT", "PREV"]:
137            ...             print((key, m[key][0].get_temporal_extent_as_tuple()))
138            (0, 1)
139            ('PRECEDES', (1, 2))
140            (1, 2)
141            ('PRECEDES', (2, 3))
142            ('EQUAL', (1, 2))
143            (2, 3)
144            ('FOLLOWS', (1, 2))
145            ('PRECEDES', (3, 4))
146            ('EQUAL', (2, 3))
147            (3, 4)
148            ('FOLLOWS', (2, 3))
149            ('EQUAL', (3, 4))
150            ('PRECEDES', (4, 5))
151
152            >>> mapsA = []
153            >>> mapsB = []
154            >>> for i in range(4):
155            ...     idA = "a%i@B"%(i)
156            ...     mapA = tgis.RasterDataset(idA)
157            ...     idB = "b%i@B"%(i)
158            ...     mapB = tgis.RasterDataset(idB)
159            ...     start = datetime.datetime(2000 + i, 1, 1)
160            ...     end = datetime.datetime(2000 + i + 1, 1, 1)
161            ...     check = mapA.set_absolute_time(start, end)
162            ...     start = datetime.datetime(2000 + i + 1, 1, 1)
163            ...     end = datetime.datetime(2000 + i + 2, 1, 1)
164            ...     check = mapB.set_absolute_time(start, end)
165            ...     mapsA.append(mapA)
166            ...     mapsB.append(mapB)
167            >>> # Build the topology between the two map lists
168            >>> tb = SpatioTemporalTopologyBuilder()
169            >>> tb.build(mapsA, mapsB, None)
170            >>> # Check relations of mapsA
171            >>> for map in mapsA:
172            ...     print(map.get_temporal_extent_as_tuple())
173            ...     m = map.get_temporal_relations()
174            ...     for key in m.keys():
175            ...         if key not in ["NEXT", "PREV"]:
176            ...             print((key, m[key][0].get_temporal_extent_as_tuple()))
177            (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2001, 1, 1, 0, 0))
178            ('PRECEDES', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2002, 1, 1, 0, 0)))
179            (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2002, 1, 1, 0, 0))
180            ('PRECEDES', (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
181            ('EQUAL', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2002, 1, 1, 0, 0)))
182            (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0))
183            ('FOLLOWS', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2002, 1, 1, 0, 0)))
184            ('PRECEDES', (datetime.datetime(2003, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
185            ('EQUAL', (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
186            (datetime.datetime(2003, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0))
187            ('FOLLOWS', (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
188            ('EQUAL', (datetime.datetime(2003, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
189            ('PRECEDES', (datetime.datetime(2004, 1, 1, 0, 0), datetime.datetime(2005, 1, 1, 0, 0)))
190
191            >>> mapsA = []
192            >>> mapsB = []
193            >>> for i in range(4):
194            ...     idA = "a%i@B"%(i)
195            ...     mapA = tgis.RasterDataset(idA)
196            ...     idB = "b%i@B"%(i)
197            ...     mapB = tgis.RasterDataset(idB)
198            ...     start = datetime.datetime(2000 + i, 1, 1)
199            ...     end = datetime.datetime(2000 + i + 1, 1, 1)
200            ...     check = mapA.set_absolute_time(start, end)
201            ...     start = datetime.datetime(2000 + i, 1, 1)
202            ...     end = datetime.datetime(2000 + i + 3, 1, 1)
203            ...     check = mapB.set_absolute_time(start, end)
204            ...     mapsA.append(mapA)
205            ...     mapsB.append(mapB)
206            >>> # Build the topology between the two map lists
207            >>> tb = SpatioTemporalTopologyBuilder()
208            >>> tb.build(mapsA, mapsB, None)
209            >>> # Check relations of mapsA
210            >>> for map in mapsA:
211            ...     print(map.get_temporal_extent_as_tuple())
212            ...     m = map.get_temporal_relations()
213            ...     for key in m.keys():
214            ...         if key not in ["NEXT", "PREV"]:
215            ...             print((key, m[key][0].get_temporal_extent_as_tuple()))
216            (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2001, 1, 1, 0, 0))
217            ('DURING', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
218            ('STARTS', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
219            ('PRECEDES', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
220            (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2002, 1, 1, 0, 0))
221            ('DURING', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
222            ('STARTS', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
223            ('PRECEDES', (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2005, 1, 1, 0, 0)))
224            (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0))
225            ('PRECEDES', (datetime.datetime(2003, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)))
226            ('FINISHES', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
227            ('DURING', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
228            ('STARTS', (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2005, 1, 1, 0, 0)))
229            (datetime.datetime(2003, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0))
230            ('FOLLOWS', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
231            ('DURING', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
232            ('FINISHES', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
233            ('STARTS', (datetime.datetime(2003, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)))
234
235            >>> mapsA = []
236            >>> mapsB = []
237            >>> for i in range(4):
238            ...     idA = "a%i@B"%(i)
239            ...     mapA = tgis.RasterDataset(idA)
240            ...     idB = "b%i@B"%(i)
241            ...     mapB = tgis.RasterDataset(idB)
242            ...     start = datetime.datetime(2000 + i, 1, 1)
243            ...     end = datetime.datetime(2000 + i + 2, 1, 1)
244            ...     check = mapA.set_absolute_time(start, end)
245            ...     start = datetime.datetime(2000 + i, 1, 1)
246            ...     end = datetime.datetime(2000 + i + 3, 1, 1)
247            ...     check = mapB.set_absolute_time(start, end)
248            ...     mapsA.append(mapA)
249            ...     mapsB.append(mapB)
250            >>> # Build the topology between the two map lists
251            >>> tb = SpatioTemporalTopologyBuilder()
252            >>> tb.build(mapsA, mapsB, None)
253            >>> # Check relations of mapsA
254            >>> for map in mapsA:
255            ...     print(map.get_temporal_extent_as_tuple())
256            ...     m = map.get_temporal_relations()
257            ...     for key in m.keys():
258            ...         if key not in ["NEXT", "PREV"]:
259            ...             print((key, m[key][0].get_temporal_extent_as_tuple()))
260            (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2002, 1, 1, 0, 0))
261            ('OVERLAPS', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
262            ('DURING', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
263            ('STARTS', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
264            ('PRECEDES', (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2005, 1, 1, 0, 0)))
265            (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0))
266            ('OVERLAPS', (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2005, 1, 1, 0, 0)))
267            ('PRECEDES', (datetime.datetime(2003, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)))
268            ('FINISHES', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
269            ('DURING', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
270            ('STARTS', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
271            (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0))
272            ('OVERLAPS', (datetime.datetime(2003, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)))
273            ('OVERLAPPED', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
274            ('FINISHES', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
275            ('DURING', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
276            ('STARTS', (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2005, 1, 1, 0, 0)))
277            (datetime.datetime(2003, 1, 1, 0, 0), datetime.datetime(2005, 1, 1, 0, 0))
278            ('OVERLAPPED', (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2004, 1, 1, 0, 0)))
279            ('DURING', (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2005, 1, 1, 0, 0)))
280            ('FINISHES', (datetime.datetime(2002, 1, 1, 0, 0), datetime.datetime(2005, 1, 1, 0, 0)))
281            ('STARTS', (datetime.datetime(2003, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)))
282            ('FOLLOWS', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2003, 1, 1, 0, 0)))
283
284            >>> mapsA = []
285            >>> mapsB = []
286            >>> for i in range(4):
287            ...     idA = "a%i@B"%(i)
288            ...     mapA = tgis.RasterDataset(idA)
289            ...     idB = "b%i@B"%(i)
290            ...     mapB = tgis.RasterDataset(idB)
291            ...     start = datetime.datetime(2000, 1, 1, 0, 0, i)
292            ...     end = datetime.datetime(2000, 1, 1, 0, 0, i + 2)
293            ...     check = mapA.set_absolute_time(start, end)
294            ...     start = datetime.datetime(2000, 1, 1, 0, 0, i + 1)
295            ...     end = datetime.datetime(2000, 1, 1, 0, 0, i + 3)
296            ...     check = mapB.set_absolute_time(start, end)
297            ...     mapsA.append(mapA)
298            ...     mapsB.append(mapB)
299            >>> # Build the topology between the two map lists
300            >>> tb = SpatioTemporalTopologyBuilder()
301            >>> tb.build(mapsA, mapsB, None)
302            >>> # Check relations of mapsA
303            >>> for map in mapsA:
304            ...     print(map.get_temporal_extent_as_tuple())
305            ...     m = map.get_temporal_relations()
306            ...     for key in m.keys():
307            ...         if key not in ["NEXT", "PREV"]:
308            ...             print((key, m[key][0].get_temporal_extent_as_tuple()))
309            (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 1, 0, 0, 2))
310            ('OVERLAPS', (datetime.datetime(2000, 1, 1, 0, 0, 1), datetime.datetime(2000, 1, 1, 0, 0, 3)))
311            ('PRECEDES', (datetime.datetime(2000, 1, 1, 0, 0, 2), datetime.datetime(2000, 1, 1, 0, 0, 4)))
312            (datetime.datetime(2000, 1, 1, 0, 0, 1), datetime.datetime(2000, 1, 1, 0, 0, 3))
313            ('OVERLAPS', (datetime.datetime(2000, 1, 1, 0, 0, 2), datetime.datetime(2000, 1, 1, 0, 0, 4)))
314            ('PRECEDES', (datetime.datetime(2000, 1, 1, 0, 0, 3), datetime.datetime(2000, 1, 1, 0, 0, 5)))
315            ('EQUAL', (datetime.datetime(2000, 1, 1, 0, 0, 1), datetime.datetime(2000, 1, 1, 0, 0, 3)))
316            (datetime.datetime(2000, 1, 1, 0, 0, 2), datetime.datetime(2000, 1, 1, 0, 0, 4))
317            ('OVERLAPS', (datetime.datetime(2000, 1, 1, 0, 0, 3), datetime.datetime(2000, 1, 1, 0, 0, 5)))
318            ('OVERLAPPED', (datetime.datetime(2000, 1, 1, 0, 0, 1), datetime.datetime(2000, 1, 1, 0, 0, 3)))
319            ('PRECEDES', (datetime.datetime(2000, 1, 1, 0, 0, 4), datetime.datetime(2000, 1, 1, 0, 0, 6)))
320            ('EQUAL', (datetime.datetime(2000, 1, 1, 0, 0, 2), datetime.datetime(2000, 1, 1, 0, 0, 4)))
321            (datetime.datetime(2000, 1, 1, 0, 0, 3), datetime.datetime(2000, 1, 1, 0, 0, 5))
322            ('OVERLAPS', (datetime.datetime(2000, 1, 1, 0, 0, 4), datetime.datetime(2000, 1, 1, 0, 0, 6)))
323            ('FOLLOWS', (datetime.datetime(2000, 1, 1, 0, 0, 1), datetime.datetime(2000, 1, 1, 0, 0, 3)))
324            ('OVERLAPPED', (datetime.datetime(2000, 1, 1, 0, 0, 2), datetime.datetime(2000, 1, 1, 0, 0, 4)))
325            ('EQUAL', (datetime.datetime(2000, 1, 1, 0, 0, 3), datetime.datetime(2000, 1, 1, 0, 0, 5)))
326
327            >>> mapsA = []
328            >>> for i in range(4):
329            ...     idA = "a%i@B"%(i)
330            ...     mapA = tgis.RasterDataset(idA)
331            ...     start = datetime.datetime(2000, 1, 1, 0, 0, i)
332            ...     end = datetime.datetime(2000, 1, 1, 0, 0, i + 2)
333            ...     check = mapA.set_absolute_time(start, end)
334            ...     mapsA.append(mapA)
335            >>> tb = SpatioTemporalTopologyBuilder()
336            >>> tb.build(mapsA)
337            >>> # Check relations of mapsA
338            >>> for map in mapsA:
339            ...     print(map.get_temporal_extent_as_tuple())
340            ...     m = map.get_temporal_relations()
341            ...     for key in m.keys():
342            ...         if key not in ["NEXT", "PREV"]:
343            ...             print((key, m[key][0].get_temporal_extent_as_tuple()))
344            (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 1, 0, 0, 2))
345            ('OVERLAPS', (datetime.datetime(2000, 1, 1, 0, 0, 1), datetime.datetime(2000, 1, 1, 0, 0, 3)))
346            ('PRECEDES', (datetime.datetime(2000, 1, 1, 0, 0, 2), datetime.datetime(2000, 1, 1, 0, 0, 4)))
347            (datetime.datetime(2000, 1, 1, 0, 0, 1), datetime.datetime(2000, 1, 1, 0, 0, 3))
348            ('OVERLAPS', (datetime.datetime(2000, 1, 1, 0, 0, 2), datetime.datetime(2000, 1, 1, 0, 0, 4)))
349            ('OVERLAPPED', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 1, 0, 0, 2)))
350            ('PRECEDES', (datetime.datetime(2000, 1, 1, 0, 0, 3), datetime.datetime(2000, 1, 1, 0, 0, 5)))
351            (datetime.datetime(2000, 1, 1, 0, 0, 2), datetime.datetime(2000, 1, 1, 0, 0, 4))
352            ('OVERLAPS', (datetime.datetime(2000, 1, 1, 0, 0, 3), datetime.datetime(2000, 1, 1, 0, 0, 5)))
353            ('FOLLOWS', (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 1, 0, 0, 2)))
354            ('OVERLAPPED', (datetime.datetime(2000, 1, 1, 0, 0, 1), datetime.datetime(2000, 1, 1, 0, 0, 3)))
355            (datetime.datetime(2000, 1, 1, 0, 0, 3), datetime.datetime(2000, 1, 1, 0, 0, 5))
356            ('FOLLOWS', (datetime.datetime(2000, 1, 1, 0, 0, 1), datetime.datetime(2000, 1, 1, 0, 0, 3)))
357            ('OVERLAPPED', (datetime.datetime(2000, 1, 1, 0, 0, 2), datetime.datetime(2000, 1, 1, 0, 0, 4)))
358
359    """
360    def __init__(self):
361        self._reset()
362        # 0001-01-01 00:00:00
363        self._timeref = datetime(1, 1, 1)
364
365    def _reset(self):
366        self._store = {}
367        self._first = None
368        self._iteratable = False
369
370    def _set_first(self, first):
371        self._first = first
372        self._insert(first)
373
374    def _detect_first(self):
375        if len(self) > 0:
376            prev_ = list(self._store.values())[0]
377            while prev_ is not None:
378                self._first = prev_
379                prev_ = prev_.prev()
380
381    def _insert(self, t):
382        self._store[t.get_id()] = t
383
384    def get_first(self):
385        """Return the first map with the earliest start time
386
387           :return: The map with the earliest start time
388        """
389        return self._first
390
391    def _build_internal_iteratable(self, maps, spatial):
392        """Build an iteratable temporal topology structure for all maps in
393           the list and store the maps internally
394
395           Basically the "next" and "prev" relations will be set in the
396           temporal topology structure of each map
397           The maps will be added to the object, so they can be
398           accessed using the iterator of this class
399
400           :param maps: A sorted (by start_time)list of abstract_dataset
401                        objects with initiated temporal extent
402        """
403        self._build_iteratable(maps, spatial)
404
405        for _map in maps:
406            self._insert(_map)
407
408        # Detect the first map
409        self._detect_first()
410
411    def _build_iteratable(self, maps, spatial):
412        """Build an iteratable temporal topology structure for
413           all maps in the list
414
415           Basically the "next" and "prev" relations will be set in
416           the temporal topology structure of each map.
417
418           :param maps: A sorted (by start_time)list of abstract_dataset
419                        objects with initiated temporal extent
420        """
421#        for i in xrange(len(maps)):
422#            offset = i + 1
423#            for j in xrange(offset, len(maps)):
424#                # Get the temporal relationship
425#                relation = maps[j].temporal_relation(maps[i])
426#
427#                # Build the next reference
428#                if relation != "equal" and relation != "started":
429#                    maps[i].set_next(maps[j])
430#                    break
431
432        # First we need to order the map list chronologically
433        sorted_maps = sorted(
434            maps, key=AbstractDatasetComparisonKeyStartTime)
435
436        for i in range(len(sorted_maps) - 1):
437            sorted_maps[i].set_next(sorted_maps[i + 1])
438
439        for map_ in sorted_maps:
440            next_ = map_.next()
441            if next_:
442                next_.set_prev(map_)
443            map_.set_temporal_topology_build_true()
444            if spatial is not None:
445                map_.set_spatial_topology_build_true()
446
447    def _map_to_rect(self, tree, map_, spatial=None):
448        """Use the spatio-temporal extent of a map to create and
449           return a RTree rectangle
450
451           :param spatial: This indicates if the spatial topology is created
452                           as well: spatial can be None (no spatial topology),
453                           "2D" using west, east, south, north or "3D" using
454                           west, east, south, north, bottom, top
455        """
456        rect = rtree.RTreeAllocRect(tree)
457
458        start, end = map_.get_temporal_extent_as_tuple()
459
460        if not end:
461            end = start
462
463        if map_.is_time_absolute():
464            start = time_delta_to_relative_time_seconds(start - self._timeref)
465            end = time_delta_to_relative_time_seconds(end - self._timeref)
466
467        if spatial is None:
468            rtree.RTreeSetRect1D(rect, tree, float(start), float(end))
469        elif spatial == "2D":
470            north, south, east, west, top, bottom = map_.get_spatial_extent_as_tuple()
471            rtree.RTreeSetRect3D(rect, tree, west, east, south, north,
472                                 float(start), float(end))
473        elif spatial == "3D":
474            north, south, east, west, top, bottom = map_.get_spatial_extent_as_tuple()
475            rtree.RTreeSetRect4D(rect, tree, west, east, south, north,
476                                 bottom, top, float(start), float(end))
477
478        return rect
479
480    def _build_rtree(self, maps, spatial=None):
481        """Build and return the 1-4 dimensional R*-Tree
482
483           :param spatial: This indicates if the spatial topology is created
484                           as well: spatial can be None (no spatial topology),
485                           "2D" using west, east, south, north or "3D" using
486                           west, east, south, north, bottom, top
487        """
488        dim = 1
489        if spatial == "2D":
490            dim = 3
491        if spatial == "3D":
492            dim = 4
493
494        tree = rtree.RTreeCreateTree(-1, 0, dim)
495
496        for i in range(len(maps)):
497
498            rect = self._map_to_rect(tree, maps[i], spatial)
499            rtree.RTreeInsertRect(rect, i + 1, tree)
500
501        return tree
502
503    def build(self, mapsA, mapsB=None, spatial=None):
504        """Build the spatio-temporal topology structure between
505           one or two unordered lists of abstract dataset objects
506
507           This method builds the temporal or spatio-temporal topology from
508           mapsA to mapsB and vice verse. The spatio-temporal topology
509           structure of each map will be reset and rebuild for mapsA and
510           mapsB.
511
512           After building the temporal or spatio-temporal topology the modified
513           map objects of mapsA can be accessed
514           in the same way as a dictionary using there id.
515           The implemented iterator assures
516           the chronological iteration over the mapsA.
517
518           :param mapsA: A list of abstract_dataset
519                         objects with initiated spatio-temporal extent
520           :param mapsB: An optional list of abstract_dataset
521                         objects with initiated spatio-temporal extent
522           :param spatial: This indicates if the spatial topology is created
523                           as well: spatial can be None (no spatial topology),
524                           "2D" using west, east, south, north or "3D" using
525                           west, east, south, north, bottom, top
526        """
527
528        identical = False
529        if mapsA == mapsB:
530            identical = True
531
532        if mapsB is None:
533            mapsB = mapsA
534            identical = True
535
536        for map_ in mapsA:
537            map_.reset_topology()
538
539        if not identical:
540            for map_ in mapsB:
541                map_.reset_topology()
542
543        tree = self. _build_rtree(mapsA, spatial)
544
545        list_ = gis.G_new_ilist()
546
547        for j in range(len(mapsB)):
548
549            rect = self._map_to_rect(tree, mapsB[j], spatial)
550            vector.RTreeSearch2(tree, rect, list_)
551            rtree.RTreeFreeRect(rect)
552
553            for k in range(list_.contents.n_values):
554                i = list_.contents.value[k] - 1
555
556                # Get the temporal relationship
557                relation = mapsB[j].temporal_relation(mapsA[i])
558
559                A = mapsA[i]
560                B = mapsB[j]
561                set_temoral_relationship(A, B, relation)
562
563                if spatial is not None:
564                    relation = mapsB[j].spatial_relation(mapsA[i])
565                    set_spatial_relationship(A, B, relation)
566
567        self._build_internal_iteratable(mapsA, spatial)
568        if not identical and mapsB is not None:
569            self._build_iteratable(mapsB, spatial)
570
571        gis.G_free_ilist(list_)
572
573        rtree.RTreeDestroyTree(tree)
574
575    def __iter__(self):
576        start_ = self._first
577        while start_ is not None:
578            yield start_
579            start_ = start_.next()
580
581    def __getitem__(self, index):
582        return self._store[index.get_id()]
583
584    def __len__(self):
585        return len(self._store)
586
587    def __contains__(self, _map):
588        return _map in self._store.values()
589
590###############################################################################
591
592
593def set_temoral_relationship(A, B, relation):
594    if relation == "equal" or relation == "equals":
595        if A != B:
596            if not B.get_equal() or \
597            (B.get_equal() and \
598            A not in B.get_equal()):
599                B.append_equal(A)
600            if not A.get_equal() or \
601            (A.get_equal() and \
602            B not in A.get_equal()):
603                A.append_equal(B)
604    elif relation == "follows":
605        if not B.get_follows() or \
606            (B.get_follows() and \
607            A not in B.get_follows()):
608            B.append_follows(A)
609        if not A.get_precedes() or \
610            (A.get_precedes() and
611            B not in A.get_precedes()):
612            A.append_precedes(B)
613    elif relation == "precedes":
614        if not B.get_precedes() or \
615            (B.get_precedes() and \
616            A not in B.get_precedes()):
617            B.append_precedes(A)
618        if not A.get_follows() or \
619            (A.get_follows() and \
620            B not in A.get_follows()):
621            A.append_follows(B)
622    elif relation == "during" or relation == "starts" or \
623            relation == "finishes":
624        if not B.get_during() or \
625            (B.get_during() and \
626            A not in B.get_during()):
627            B.append_during(A)
628        if not A.get_contains() or \
629            (A.get_contains() and \
630            B not in A.get_contains()):
631            A.append_contains(B)
632        if relation == "starts":
633            if not B.get_starts() or \
634            (B.get_starts() and \
635            A not in B.get_starts()):
636                B.append_starts(A)
637            if not A.get_started() or \
638            (A.get_started() and \
639            B not in A.get_started()):
640                A.append_started(B)
641        if relation == "finishes":
642            if not B.get_finishes() or \
643            (B.get_finishes() and \
644            A not in B.get_finishes()):
645                B.append_finishes(A)
646            if not A.get_finished() or \
647            (A.get_finished() and \
648            B not in A.get_finished()):
649                A.append_finished(B)
650    elif relation == "contains" or relation == "started" or \
651            relation == "finished":
652        if not B.get_contains() or \
653            (B.get_contains() and \
654            A not in B.get_contains()):
655            B.append_contains(A)
656        if not A.get_during() or \
657            (A.get_during() and \
658            B not in A.get_during()):
659            A.append_during(B)
660        if relation == "started":
661            if not B.get_started() or \
662            (B.get_started() and \
663            A not in B.get_started()):
664                B.append_started(A)
665            if not A.get_starts() or \
666            (A.get_starts() and \
667            B not in A.get_starts()):
668                A.append_starts(B)
669        if relation == "finished":
670            if not B.get_finished() or \
671            (B.get_finished() and \
672            A not in B.get_finished()):
673                B.append_finished(A)
674            if not A.get_finishes() or \
675            (A.get_finishes() and \
676            B not in A.get_finishes()):
677                A.append_finishes(B)
678    elif relation == "overlaps":
679        if not B.get_overlaps() or \
680            (B.get_overlaps() and \
681            A not in B.get_overlaps()):
682            B.append_overlaps(A)
683        if not A.get_overlapped() or \
684            (A.get_overlapped() and \
685            B not in A.get_overlapped()):
686            A.append_overlapped(B)
687    elif relation == "overlapped":
688        if not B.get_overlapped() or \
689            (B.get_overlapped() and \
690            A not in B.get_overlapped()):
691            B.append_overlapped(A)
692        if not A.get_overlaps() or \
693            (A.get_overlaps() and \
694            B not in A.get_overlaps()):
695            A.append_overlaps(B)
696
697###############################################################################
698
699def set_spatial_relationship(A, B, relation):
700
701    if relation == "equivalent":
702        if A != B:
703            if not B.get_equivalent() or \
704            (B.get_equivalent() and \
705            A not in B.get_equivalent()):
706                B.append_equivalent(A)
707            if not A.get_equivalent() or \
708            (A.get_equivalent() and \
709            B not in A.get_equivalent()):
710                A.append_equivalent(B)
711    elif relation == "overlap":
712        if not B.get_overlap() or \
713            (B.get_overlap() and \
714            A not in B.get_overlap()):
715            B.append_overlap(A)
716        if not A.get_overlap() or \
717            (A.get_overlap() and
718            B not in A.get_overlap()):
719            A.append_overlap(B)
720    elif relation == "meet":
721        if not B.get_meet() or \
722            (B.get_meet() and \
723            A not in B.get_meet()):
724            B.append_meet(A)
725        if not A.get_meet() or \
726            (A.get_meet() and
727            B not in A.get_meet()):
728            A.append_meet(B)
729    elif relation == "contain":
730        if not B.get_contain() or \
731            (B.get_contain() and \
732            A not in B.get_contain()):
733            B.append_contain(A)
734        if not A.get_in() or \
735            (A.get_in() and \
736            B not in A.get_in()):
737            A.append_in(B)
738    elif relation == "in":
739        if not B.get_in() or \
740            (B.get_in() and \
741            A not in B.get_in()):
742            B.append_in(A)
743        if not A.get_contain() or \
744            (A.get_contain() and \
745            B not in A.get_contain()):
746            A.append_contain(B)
747    elif relation == "cover":
748        if not B.get_cover() or \
749            (B.get_cover() and \
750            A not in B.get_cover()):
751            B.append_cover(A)
752        if not A.get_covered() or \
753            (A.get_covered() and \
754            B not in A.get_covered()):
755            A.append_covered(B)
756    elif relation == "covered":
757        if not B.get_covered() or \
758            (B.get_covered() and \
759            A not in B.get_covered()):
760            B.append_covered(A)
761        if not A.get_cover() or \
762            (A.get_cover() and \
763            B not in A.get_cover()):
764            A.append_cover(B)
765
766###############################################################################
767
768
769def print_temporal_topology_relationships(maps1, maps2=None, dbif=None):
770    """Print the temporal relationships of the
771       map lists maps1 and maps2 to stdout.
772
773        :param maps1: A list of abstract_dataset
774                      objects with initiated temporal extent
775        :param maps2: An optional list of abstract_dataset
776                      objects with initiated temporal extent
777        :param dbif: The database interface to be used
778    """
779
780    tb = SpatioTemporalTopologyBuilder()
781
782    tb.build(maps1, maps2)
783
784    dbif, connected = init_dbif(dbif)
785
786    for _map in tb:
787        _map.select(dbif)
788        _map.print_info()
789
790    if connected:
791        dbif.close()
792
793    return
794
795###############################################################################
796
797
798def print_spatio_temporal_topology_relationships(maps1, maps2=None,
799                                                 spatial="2D", dbif=None):
800    """Print the temporal relationships of the
801       map lists maps1 and maps2 to stdout.
802
803        :param maps1: A list of abstract_dataset
804                      objects with initiated temporal extent
805        :param maps2: An optional list of abstract_dataset
806                      objects with initiated temporal extent
807        :param spatial: The dimension of the spatial extent to be used: "2D"
808                        using west, east, south, north or "3D" using west,
809                        east, south, north, bottom, top
810        :param dbif: The database interface to be used
811    """
812
813    tb = SpatioTemporalTopologyBuilder()
814
815    tb.build(maps1, maps2, spatial)
816
817    dbif, connected = init_dbif(dbif)
818
819    for _map in tb:
820        _map.select(dbif)
821        _map.print_info()
822
823    if connected:
824        dbif.close()
825
826    return
827
828###############################################################################
829
830
831def count_temporal_topology_relationships(maps1, maps2=None, dbif=None):
832    """Count the temporal relations of a single list of maps or between two
833       lists of maps
834
835
836        :param maps1: A list of abstract_dataset
837                      objects with initiated temporal extent
838        :param maps2: A list of abstract_dataset
839                      objects with initiated temporal extent
840        :param dbif: The database interface to be used
841        :return: A dictionary with counted temporal relationships
842    """
843
844    tb = SpatioTemporalTopologyBuilder()
845    tb.build(maps1, maps2)
846
847    dbif, connected = init_dbif(dbif)
848
849    relations = None
850
851    for _map in tb:
852        if relations is not None:
853            r = _map.get_number_of_relations()
854            for k in r.keys():
855                relations[k] += r[k]
856        else:
857            relations = _map.get_number_of_relations()
858
859    if connected:
860        dbif.close()
861
862    return relations
863
864###############################################################################
865
866
867def create_temporal_relation_sql_where_statement(start, end, use_start=True,
868                                                 use_during=False,
869                                                 use_overlap=False,
870                                                 use_contain=False,
871                                                 use_equal=False,
872                                                 use_follows=False,
873                                                 use_precedes=False):
874    """Create a SQL WHERE statement for temporal relation selection of maps in
875       space time datasets
876
877        :param start: The start time
878        :param end: The end time
879        :param use_start: Select maps of which the start time is located in
880                          the selection granule ::
881
882                              map    :        s
883                              granule:  s-----------------e
884
885                              map    :        s--------------------e
886                              granule:  s-----------------e
887
888                              map    :        s--------e
889                              granule:  s-----------------e
890
891
892        :param use_during: Select maps which are temporal during the selection
893                           granule  ::
894
895                               map    :     s-----------e
896                               granule:  s-----------------e
897
898        :param use_overlap: Select maps which temporal overlap the selection
899                            granule ::
900
901                                map    :     s-----------e
902                                granule:        s-----------------e
903
904                                map    :     s-----------e
905                                granule:  s----------e
906
907        :param use_contain: Select maps which temporally contain the selection
908                            granule ::
909
910                                map    :  s-----------------e
911                                granule:     s-----------e
912
913        :param use_equal: Select maps which temporally equal to the selection
914                          granule ::
915
916                              map    :  s-----------e
917                              granule:  s-----------e
918
919        :param use_follows: Select maps which temporally follow the selection
920                            granule ::
921
922                                map    :              s-----------e
923                                granule:  s-----------e
924
925        :param use_precedes: Select maps which temporally precedes the
926                             selection granule ::
927
928                                 map    :  s-----------e
929                                 granule:              s-----------e
930
931        Usage:
932
933        .. code-block:: python
934
935            >>> # Relative time
936            >>> start = 1
937            >>> end = 2
938            >>> create_temporal_relation_sql_where_statement(start, end,
939            ... use_start=False)
940            >>> create_temporal_relation_sql_where_statement(start, end)
941            '((start_time >= 1 and start_time < 2) )'
942            >>> create_temporal_relation_sql_where_statement(start, end,
943            ... use_start=True)
944            '((start_time >= 1 and start_time < 2) )'
945            >>> create_temporal_relation_sql_where_statement(start, end,
946            ... use_start=False, use_during=True)
947            '(((start_time > 1 and end_time < 2) OR (start_time >= 1 and end_time < 2) OR (start_time > 1 and end_time <= 2)))'
948            >>> create_temporal_relation_sql_where_statement(start, end,
949            ... use_start=False, use_overlap=True)
950            '(((start_time < 1 and end_time > 1 and end_time < 2) OR (start_time < 2 and start_time > 1 and end_time > 2)))'
951            >>> create_temporal_relation_sql_where_statement(start, end,
952            ... use_start=False, use_contain=True)
953            '(((start_time < 1 and end_time > 2) OR (start_time <= 1 and end_time > 2) OR (start_time < 1 and end_time >= 2)))'
954            >>> create_temporal_relation_sql_where_statement(start, end,
955            ... use_start=False, use_equal=True)
956            '((start_time = 1 and end_time = 2))'
957            >>> create_temporal_relation_sql_where_statement(start, end,
958            ... use_start=False, use_follows=True)
959            '((start_time = 2))'
960            >>> create_temporal_relation_sql_where_statement(start, end,
961            ... use_start=False, use_precedes=True)
962            '((end_time = 1))'
963            >>> create_temporal_relation_sql_where_statement(start, end,
964            ... use_start=True, use_during=True, use_overlap=True, use_contain=True,
965            ... use_equal=True, use_follows=True, use_precedes=True)
966            '((start_time >= 1 and start_time < 2)  OR ((start_time > 1 and end_time < 2) OR (start_time >= 1 and end_time < 2) OR (start_time > 1 and end_time <= 2)) OR ((start_time < 1 and end_time > 1 and end_time < 2) OR (start_time < 2 and start_time > 1 and end_time > 2)) OR ((start_time < 1 and end_time > 2) OR (start_time <= 1 and end_time > 2) OR (start_time < 1 and end_time >= 2)) OR (start_time = 1 and end_time = 2) OR (start_time = 2) OR (end_time = 1))'
967
968            >>> # Absolute time
969            >>> start = datetime(2001, 1, 1, 12, 30)
970            >>> end = datetime(2001, 3, 31, 14, 30)
971            >>> create_temporal_relation_sql_where_statement(start, end,
972            ... use_start=False)
973            >>> create_temporal_relation_sql_where_statement(start, end)
974            "((start_time >= '2001-01-01 12:30:00' and start_time < '2001-03-31 14:30:00') )"
975            >>> create_temporal_relation_sql_where_statement(start, end,
976            ... use_start=True)
977            "((start_time >= '2001-01-01 12:30:00' and start_time < '2001-03-31 14:30:00') )"
978            >>> create_temporal_relation_sql_where_statement(start, end,
979            ... use_start=False, use_during=True)
980            "(((start_time > '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time >= '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time > '2001-01-01 12:30:00' and end_time <= '2001-03-31 14:30:00')))"
981            >>> create_temporal_relation_sql_where_statement(start, end,
982            ... use_start=False, use_overlap=True)
983            "(((start_time < '2001-01-01 12:30:00' and end_time > '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time < '2001-03-31 14:30:00' and start_time > '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00')))"
984            >>> create_temporal_relation_sql_where_statement(start, end,
985            ... use_start=False, use_contain=True)
986            "(((start_time < '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00') OR (start_time <= '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00') OR (start_time < '2001-01-01 12:30:00' and end_time >= '2001-03-31 14:30:00')))"
987            >>> create_temporal_relation_sql_where_statement(start, end,
988            ... use_start=False, use_equal=True)
989            "((start_time = '2001-01-01 12:30:00' and end_time = '2001-03-31 14:30:00'))"
990            >>> create_temporal_relation_sql_where_statement(start, end,
991            ... use_start=False, use_follows=True)
992            "((start_time = '2001-03-31 14:30:00'))"
993            >>> create_temporal_relation_sql_where_statement(start, end,
994            ... use_start=False, use_precedes=True)
995            "((end_time = '2001-01-01 12:30:00'))"
996            >>> create_temporal_relation_sql_where_statement(start, end,
997            ... use_start=True, use_during=True, use_overlap=True, use_contain=True,
998            ... use_equal=True, use_follows=True, use_precedes=True)
999            "((start_time >= '2001-01-01 12:30:00' and start_time < '2001-03-31 14:30:00')  OR ((start_time > '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time >= '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time > '2001-01-01 12:30:00' and end_time <= '2001-03-31 14:30:00')) OR ((start_time < '2001-01-01 12:30:00' and end_time > '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time < '2001-03-31 14:30:00' and start_time > '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00')) OR ((start_time < '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00') OR (start_time <= '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00') OR (start_time < '2001-01-01 12:30:00' and end_time >= '2001-03-31 14:30:00')) OR (start_time = '2001-01-01 12:30:00' and end_time = '2001-03-31 14:30:00') OR (start_time = '2001-03-31 14:30:00') OR (end_time = '2001-01-01 12:30:00'))"
1000
1001        """
1002
1003    where = "("
1004
1005    if use_start:
1006        if isinstance(start, datetime):
1007            where += "(start_time >= '%s' and start_time < '%s') " % (start,
1008                                                                      end)
1009        else:
1010            where += "(start_time >= %i and start_time < %i) " % (start, end)
1011
1012    if use_during:
1013        if use_start:
1014            where += " OR "
1015
1016        if isinstance(start, datetime):
1017            where += "((start_time > '%s' and end_time < '%s') OR " % (start,
1018                                                                       end)
1019            where += "(start_time >= '%s' and end_time < '%s') OR " % (start,
1020                                                                       end)
1021            where += "(start_time > '%s' and end_time <= '%s'))" % (start, end)
1022        else:
1023            where += "((start_time > %i and end_time < %i) OR " % (start, end)
1024            where += "(start_time >= %i and end_time < %i) OR " % (start, end)
1025            where += "(start_time > %i and end_time <= %i))" % (start, end)
1026
1027    if use_overlap:
1028        if use_start or use_during:
1029            where += " OR "
1030
1031        if isinstance(start, datetime):
1032            where += "((start_time < '%s' and end_time > '%s' and end_time <" \
1033                     " '%s') OR " % (start, start, end)
1034            where += "(start_time < '%s' and start_time > '%s' and end_time " \
1035                     "> '%s'))" % (end, start, end)
1036        else:
1037            where += "((start_time < %i and end_time > %i and end_time < %i)" \
1038                     " OR " % (start, start, end)
1039            where += "(start_time < %i and start_time > %i and end_time > " \
1040                     "%i))" % (end, start, end)
1041
1042    if use_contain:
1043        if use_start or use_during or use_overlap:
1044            where += " OR "
1045
1046        if isinstance(start, datetime):
1047            where += "((start_time < '%s' and end_time > '%s') OR " % (start,
1048                                                                       end)
1049            where += "(start_time <= '%s' and end_time > '%s') OR " % (start,
1050                                                                       end)
1051            where += "(start_time < '%s' and end_time >= '%s'))" % (start, end)
1052        else:
1053            where += "((start_time < %i and end_time > %i) OR " % (start, end)
1054            where += "(start_time <= %i and end_time > %i) OR " % (start, end)
1055            where += "(start_time < %i and end_time >= %i))" % (start, end)
1056
1057    if use_equal:
1058        if use_start or use_during or use_overlap or use_contain:
1059            where += " OR "
1060
1061        if isinstance(start, datetime):
1062            where += "(start_time = '%s' and end_time = '%s')" % (start, end)
1063        else:
1064            where += "(start_time = %i and end_time = %i)" % (start, end)
1065
1066    if use_follows:
1067        if use_start or use_during or use_overlap or use_contain or use_equal:
1068            where += " OR "
1069
1070        if isinstance(start, datetime):
1071            where += "(start_time = '%s')" % (end)
1072        else:
1073            where += "(start_time = %i)" % (end)
1074
1075    if use_precedes:
1076        if use_start or use_during or use_overlap or use_contain or use_equal \
1077           or use_follows:
1078            where += " OR "
1079
1080        if isinstance(start, datetime):
1081            where += "(end_time = '%s')" % (start)
1082        else:
1083            where += "(end_time = %i)" % (start)
1084
1085    where += ")"
1086
1087    # Catch empty where statement
1088    if where == "()":
1089        where = None
1090
1091    return where
1092
1093###############################################################################
1094
1095if __name__ == "__main__":
1096    import doctest
1097    doctest.testmod()
1098