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