1""" 2@package animation.temporal_manager 3 4@brief Management of temporal datasets used in animation 5 6Classes: 7 - temporal_manager::DataMode 8 - temporal_manager::GranularityMode 9 - temporal_manager::TemporalManager 10 11 12(C) 2012 by the GRASS Development Team 13 14This program is free software under the GNU General Public License 15(>=v2). Read the file COPYING that comes with GRASS for details. 16 17@author Anna Kratochvilova <kratochanna gmail.com> 18""" 19 20from __future__ import print_function 21 22import os 23import datetime 24 25import grass.script as grass 26import grass.temporal as tgis 27from core.gcmd import GException 28from core.settings import UserSettings 29from animation.utils import validateTimeseriesName, TemporalType 30 31 32class DataMode: 33 SIMPLE = 1 34 MULTIPLE = 2 35 36 37class GranularityMode: 38 ONE_UNIT = 1 39 ORIGINAL = 2 40 41 42class TemporalManager(object): 43 """Class for temporal data processing.""" 44 45 def __init__(self): 46 self.timeseriesList = [] 47 self.timeseriesInfo = {} 48 49 self.dataMode = None 50 self.temporalType = None 51 52 self.granularityMode = GranularityMode.ORIGINAL 53 54 def GetTemporalType(self): 55 """Get temporal type (TemporalType.ABSOLUTE, 56 TemporalType.RELATIVE) 57 """ 58 return self._temporalType 59 60 def SetTemporalType(self, ttype): 61 self._temporalType = ttype 62 63 temporalType = property(fget=GetTemporalType, fset=SetTemporalType) 64 65 def AddTimeSeries(self, timeseries, etype): 66 """Add space time dataset 67 and collect basic information about it. 68 69 Raises GException (e.g. with invalid topology). 70 71 :param timeseries: name of timeseries (with or without mapset) 72 :param etype: element type (strds, stvds) 73 """ 74 self._gatherInformation( 75 timeseries, 76 etype, 77 self.timeseriesList, 78 self.timeseriesInfo) 79 80 def EvaluateInputData(self): 81 """Checks if all timeseries are compatible (raises GException). 82 83 Sets internal variables. 84 """ 85 timeseriesCount = len(self.timeseriesList) 86 87 if timeseriesCount == 1: 88 self.dataMode = DataMode.SIMPLE 89 elif timeseriesCount > 1: 90 self.dataMode = DataMode.MULTIPLE 91 else: 92 self.dataMode = None 93 94 ret, message = self._setTemporalState() 95 if not ret: 96 raise GException(message) 97 if message: # warning 98 return message 99 100 return None 101 102 def _setTemporalState(self): 103 # check for absolute x relative 104 absolute, relative = 0, 0 105 for infoDict in self.timeseriesInfo.values(): 106 if infoDict['temporal_type'] == 'absolute': 107 absolute += 1 108 else: 109 relative += 1 110 if bool(absolute) == bool(relative): 111 message = _("It is not allowed to display data with different " 112 "temporal types (absolute and relative).") 113 return False, message 114 if absolute: 115 self.temporalType = TemporalType.ABSOLUTE 116 else: 117 self.temporalType = TemporalType.RELATIVE 118 119 # check for units for relative type 120 if relative: 121 units = set() 122 for infoDict in self.timeseriesInfo.values(): 123 units.add(infoDict['unit']) 124 if len(units) > 1: 125 message = _( 126 "It is not allowed to display data with different units (%s).") % ','.join(units) 127 return False, message 128 129 # check for interval x point 130 interval, point = 0, 0 131 for infoDict in self.timeseriesInfo.values(): 132 if infoDict['map_time'] == 'interval': 133 interval += 1 134 else: 135 point += 1 136 if bool(interval) == bool(point): 137 message = _( 138 "You are going to display data with different " 139 "temporal types of maps (interval and point)." 140 " It is recommended to use data of one temporal type to avoid confusion.") 141 return True, message # warning 142 143 return True, None 144 145 def GetGranularity(self): 146 """Returns temporal granularity of currently loaded timeseries. 147 """ 148 if self.dataMode == DataMode.SIMPLE: 149 gran = self.timeseriesInfo[self.timeseriesList[0]]['granularity'] 150 if 'unit' in self.timeseriesInfo[ 151 self.timeseriesList[0]]: # relative: 152 granNum = gran 153 unit = self.timeseriesInfo[self.timeseriesList[0]]['unit'] 154 if self.granularityMode == GranularityMode.ONE_UNIT: 155 granNum = 1 156 else: # absolute 157 granNum, unit = gran.split() 158 if self.granularityMode == GranularityMode.ONE_UNIT: 159 granNum = 1 160 161 return (int(granNum), unit) 162 163 if self.dataMode == DataMode.MULTIPLE: 164 return self._getCommonGranularity() 165 166 def _getCommonGranularity(self): 167 allMaps = [] 168 for dataset in self.timeseriesList: 169 maps = self.timeseriesInfo[dataset]['maps'] 170 allMaps.extend(maps) 171 172 if self.temporalType == TemporalType.ABSOLUTE: 173 gran = tgis.compute_absolute_time_granularity(allMaps) 174 granNum, unit = gran.split() 175 if self.granularityMode == GranularityMode.ONE_UNIT: 176 granNum = 1 177 return int(granNum), unit 178 if self.temporalType == TemporalType.RELATIVE: 179 unit = self.timeseriesInfo[self.timeseriesList[0]]['unit'] 180 granNum = tgis.compute_relative_time_granularity(allMaps) 181 if self.granularityMode == GranularityMode.ONE_UNIT: 182 granNum = 1 183 return (granNum, unit) 184 185 def GetLabelsAndMaps(self): 186 """Returns time labels and map names. 187 """ 188 mapLists = [] 189 labelLists = [] 190 labelListSet = set() 191 for dataset in self.timeseriesList: 192 grassLabels, listOfMaps = self._getLabelsAndMaps(dataset) 193 mapLists.append(listOfMaps) 194 labelLists.append(tuple(grassLabels)) 195 labelListSet.update(grassLabels) 196 # combine all timeLabels and fill missing maps with None 197 # BUT this does not work properly if the datasets have 198 # no temporal overlap! We would need to sample all datasets 199 # by a temporary dataset, I don't know how it would work with point 200 # data 201 if self.temporalType == TemporalType.ABSOLUTE: 202 timestamps = sorted(list(labelListSet), key=lambda x: x[0]) 203 else: 204 timestamps = sorted(list(labelListSet), key=lambda x: x[0]) 205 206 newMapLists = [] 207 for mapList, labelList in zip(mapLists, labelLists): 208 newMapList = [None] * len(timestamps) 209 i = 0 210 # compare start time 211 while timestamps[i][0] != labelList[0][0]: # compare 212 i += 1 213 newMapList[i:i + len(mapList)] = mapList 214 newMapLists.append(newMapList) 215 216 mapDict = {} 217 for i, dataset in enumerate(self.timeseriesList): 218 mapDict[dataset] = newMapLists[i] 219 220 if self.temporalType == TemporalType.ABSOLUTE: 221 # ('1996-01-01 00:00:00', '1997-01-01 00:00:00', 'year'), 222 formatString = UserSettings.Get( 223 group='animation', key='temporal', subkey='format') 224 timestamps = [ 225 (datetime.datetime.strftime( 226 st, formatString), datetime.datetime.strftime( 227 end, formatString) if end is not None else None, unit) for ( 228 st, end, unit) in timestamps] 229 else: 230 # ('15', '16', u'years'), 231 timestamps = [(str(st), end if end is None else str(end), unit) 232 for st, end, unit in timestamps] 233 return timestamps, mapDict 234 235 def _getLabelsAndMaps(self, timeseries): 236 """Returns time labels and map names (done by sampling) 237 for both interval and point data. 238 """ 239 sp = tgis.dataset_factory( 240 self.timeseriesInfo[timeseries]['etype'], timeseries) 241 if sp.is_in_db() is False: 242 raise GException( 243 _("Space time dataset <%s> not found.") % 244 timeseries) 245 sp.select() 246 247 listOfMaps = [] 248 timeLabels = [] 249 granNum, unit = self.GetGranularity() 250 if self.temporalType == TemporalType.ABSOLUTE: 251 if self.granularityMode == GranularityMode.ONE_UNIT: 252 gran = '%(one)d %(unit)s' % {'one': 1, 'unit': unit} 253 else: 254 gran = '%(num)d %(unit)s' % {'num': granNum, 'unit': unit} 255 256 elif self.temporalType == TemporalType.RELATIVE: 257 unit = self.timeseriesInfo[timeseries]['unit'] 258 if self.granularityMode == GranularityMode.ONE_UNIT: 259 gran = 1 260 else: 261 gran = granNum 262 # start sampling - now it can be used for both interval and point data 263 # after instance, there can be a gap or an interval 264 # if it is a gap we remove it and put there the previous instance instead 265 # however the first gap must be removed to avoid duplication 266 maps = sp.get_registered_maps_as_objects_by_granularity(gran=gran) 267 if maps and len(maps) > 0: 268 lastTimeseries = None 269 followsPoint = False # indicates that we are just after finding a point 270 afterPoint = False # indicates that we are after finding a point 271 for mymap in maps: 272 if isinstance(mymap, list): 273 if len(mymap) > 0: 274 map = mymap[0] 275 else: 276 map = mymap 277 278 series = map.get_id() 279 280 start, end = map.get_temporal_extent_as_tuple() 281 if self.timeseriesInfo[timeseries]['map_time'] == 'point': 282 # point data 283 listOfMaps.append(series) 284 afterPoint = True 285 followsPoint = True 286 lastTimeseries = series 287 end = None 288 else: 289 end = end 290 # interval data 291 if series: 292 # map exists, stop point mode 293 listOfMaps.append(series) 294 afterPoint = False 295 else: 296 # check point mode 297 if afterPoint: 298 if followsPoint: 299 # skip this one, already there 300 followsPoint = False 301 continue 302 else: 303 # append the last one (of point time) 304 listOfMaps.append(lastTimeseries) 305 end = None 306 else: 307 # append series which is None 308 listOfMaps.append(series) 309 timeLabels.append((start, end, unit)) 310 311 return timeLabels, listOfMaps 312 313 def _pretifyTimeLabels(self, labels): 314 """Convert absolute time labels to grass time and 315 leave only datum when time is 0. 316 """ 317 grassLabels = [] 318 isTime = False 319 for start, end, unit in labels: 320 start = tgis.string_to_datetime(start) 321 start = tgis.datetime_to_grass_datetime_string(start) 322 if end is not None: 323 end = tgis.string_to_datetime(end) 324 end = tgis.datetime_to_grass_datetime_string(end) 325 grassLabels.append((start, end, unit)) 326 if '00:00:00' not in start or ( 327 end is not None and '00:00:00' not in end): 328 isTime = True 329 if not isTime: 330 for i, (start, end, unit) in enumerate(grassLabels): 331 start = start.replace('00:00:00', '').strip() 332 if end is not None: 333 end = end.replace('00:00:00', '').strip() 334 grassLabels[i] = (start, end, unit) 335 return grassLabels 336 337 def _gatherInformation(self, timeseries, etype, timeseriesList, infoDict): 338 """Get info about timeseries and check topology (raises GException)""" 339 id = validateTimeseriesName(timeseries, etype) 340 sp = tgis.dataset_factory(etype, id) 341 # Insert content from db 342 sp.select() 343 # Get ordered map list 344 maps = sp.get_registered_maps_as_objects() 345 346 if not sp.check_temporal_topology(maps): 347 raise GException( 348 _("Topology of Space time dataset %s is invalid." % id)) 349 350 timeseriesList.append(id) 351 infoDict[id] = {} 352 infoDict[id]['etype'] = etype 353 infoDict[id]['temporal_type'] = sp.get_temporal_type() 354 if sp.is_time_relative(): 355 infoDict[id]['unit'] = sp.get_relative_time_unit() 356 infoDict[id]['granularity'] = sp.get_granularity() 357 infoDict[id]['map_time'] = sp.get_map_time() 358 infoDict[id]['maps'] = maps 359 360 361def test(): 362 from pprint import pprint 363 # Make sure the temporal database exists 364 tgis.init() 365 366 temp = TemporalManager() 367# timeseries = createAbsolutePoint() 368# timeseries = createRelativePoint() 369# timeseries1, timeseries2 = createAbsoluteInterval() 370 timeseries1, timeseries2 = createRelativeInterval() 371 372 temp.AddTimeSeries(timeseries1, 'strds') 373 temp.AddTimeSeries(timeseries2, 'strds') 374 375 try: 376 warn = temp.EvaluateInputData() 377 print(warn) 378 except GException as e: 379 print(e) 380 return 381 382 print('///////////////////////////') 383 gran = temp.GetGranularity() 384 print("granularity: " + str(gran)) 385 pprint(temp.GetLabelsAndMaps()) 386 387 388def createAbsoluteInterval(): 389 grass.run_command( 390 'g.region', 391 s=0, 392 n=80, 393 w=0, 394 e=120, 395 b=0, 396 t=50, 397 res=10, 398 res3=10, 399 flags='p3', 400 quiet=True) 401 402 grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite=True) 403 grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite=True) 404 grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite=True) 405 grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite=True) 406 grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite=True) 407 grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite=True) 408 409 grass.mapcalc(exp="temp_1 = rand(0, 550)", overwrite=True) 410 grass.mapcalc(exp="temp_2 = rand(0, 450)", overwrite=True) 411 grass.mapcalc(exp="temp_3 = rand(0, 320)", overwrite=True) 412 grass.mapcalc(exp="temp_4 = rand(0, 510)", overwrite=True) 413 grass.mapcalc(exp="temp_5 = rand(0, 300)", overwrite=True) 414 grass.mapcalc(exp="temp_6 = rand(0, 650)", overwrite=True) 415 416 n1 = grass.read_command("g.tempfile", pid=1, flags='d').strip() 417 fd = open(n1, 'w') 418 fd.write( 419 "prec_1|2001-01-01|2001-02-01\n" 420 "prec_2|2001-04-01|2001-05-01\n" 421 "prec_3|2001-05-01|2001-09-01\n" 422 "prec_4|2001-09-01|2002-01-01\n" 423 "prec_5|2002-01-01|2002-05-01\n" 424 "prec_6|2002-05-01|2002-07-01\n" 425 ) 426 fd.close() 427 428 n2 = grass.read_command("g.tempfile", pid=2, flags='d').strip() 429 fd = open(n2, 'w') 430 fd.write( 431 "temp_1|2000-10-01|2001-01-01\n" 432 "temp_2|2001-04-01|2001-05-01\n" 433 "temp_3|2001-05-01|2001-09-01\n" 434 "temp_4|2001-09-01|2002-01-01\n" 435 "temp_5|2002-01-01|2002-05-01\n" 436 "temp_6|2002-05-01|2002-07-01\n" 437 ) 438 fd.close() 439 name1 = 'absinterval1' 440 name2 = 'absinterval2' 441 grass.run_command('t.unregister', type='raster', 442 maps='prec_1,prec_2,prec_3,prec_4,prec_5,prec_6,' 443 'temp_1,temp_2,temp_3,temp_4,temp_5,temp_6') 444 for name, fname in zip((name1, name2), (n1, n2)): 445 grass.run_command( 446 't.create', 447 overwrite=True, 448 type='strds', 449 temporaltype='absolute', 450 output=name, 451 title="A test with input files", 452 descr="A test with input files") 453 grass.run_command( 454 't.register', 455 flags='i', 456 input=name, 457 file=fname, 458 overwrite=True) 459 460 return name1, name2 461 462 463def createRelativeInterval(): 464 grass.run_command( 465 'g.region', 466 s=0, 467 n=80, 468 w=0, 469 e=120, 470 b=0, 471 t=50, 472 res=10, 473 res3=10, 474 flags='p3', 475 quiet=True) 476 477 grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite=True) 478 grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite=True) 479 grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite=True) 480 grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite=True) 481 grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite=True) 482 grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite=True) 483 484 grass.mapcalc(exp="temp_1 = rand(0, 550)", overwrite=True) 485 grass.mapcalc(exp="temp_2 = rand(0, 450)", overwrite=True) 486 grass.mapcalc(exp="temp_3 = rand(0, 320)", overwrite=True) 487 grass.mapcalc(exp="temp_4 = rand(0, 510)", overwrite=True) 488 grass.mapcalc(exp="temp_5 = rand(0, 300)", overwrite=True) 489 grass.mapcalc(exp="temp_6 = rand(0, 650)", overwrite=True) 490 491 n1 = grass.read_command("g.tempfile", pid=1, flags='d').strip() 492 fd = open(n1, 'w') 493 fd.write( 494 "prec_1|1|4\n" 495 "prec_2|6|7\n" 496 "prec_3|7|10\n" 497 "prec_4|10|11\n" 498 "prec_5|11|14\n" 499 "prec_6|14|17\n" 500 ) 501 fd.close() 502 503 n2 = grass.read_command("g.tempfile", pid=2, flags='d').strip() 504 fd = open(n2, 'w') 505 fd.write( 506 "temp_1|5|6\n" 507 "temp_2|6|7\n" 508 "temp_3|7|10\n" 509 "temp_4|10|11\n" 510 "temp_5|11|18\n" 511 "temp_6|19|22\n" 512 ) 513 fd.close() 514 name1 = 'relinterval1' 515 name2 = 'relinterval2' 516 grass.run_command('t.unregister', type='raster', 517 maps='prec_1,prec_2,prec_3,prec_4,prec_5,prec_6,' 518 'temp_1,temp_2,temp_3,temp_4,temp_5,temp_6') 519 for name, fname in zip((name1, name2), (n1, n2)): 520 grass.run_command( 521 't.create', 522 overwrite=True, 523 type='strds', 524 temporaltype='relative', 525 output=name, 526 title="A test with input files", 527 descr="A test with input files") 528 grass.run_command( 529 't.register', 530 flags='i', 531 input=name, 532 file=fname, 533 unit="years", 534 overwrite=True) 535 return name1, name2 536 537 538def createAbsolutePoint(): 539 grass.run_command( 540 'g.region', 541 s=0, 542 n=80, 543 w=0, 544 e=120, 545 b=0, 546 t=50, 547 res=10, 548 res3=10, 549 flags='p3', 550 quiet=True) 551 552 grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite=True) 553 grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite=True) 554 grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite=True) 555 grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite=True) 556 grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite=True) 557 grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite=True) 558 559 n1 = grass.read_command("g.tempfile", pid=1, flags='d').strip() 560 fd = open(n1, 'w') 561 fd.write( 562 "prec_1|2001-01-01\n" 563 "prec_2|2001-03-01\n" 564 "prec_3|2001-04-01\n" 565 "prec_4|2001-05-01\n" 566 "prec_5|2001-08-01\n" 567 "prec_6|2001-09-01\n" 568 ) 569 fd.close() 570 name = 'abspoint' 571 grass.run_command( 572 't.create', 573 overwrite=True, 574 type='strds', 575 temporaltype='absolute', 576 output=name, 577 title="A test with input files", 578 descr="A test with input files") 579 580 grass.run_command( 581 't.register', 582 flags='i', 583 input=name, 584 file=n1, 585 overwrite=True) 586 return name 587 588 589def createRelativePoint(): 590 grass.run_command( 591 'g.region', 592 s=0, 593 n=80, 594 w=0, 595 e=120, 596 b=0, 597 t=50, 598 res=10, 599 res3=10, 600 flags='p3', 601 quiet=True) 602 603 grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite=True) 604 grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite=True) 605 grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite=True) 606 grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite=True) 607 grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite=True) 608 grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite=True) 609 610 n1 = grass.read_command("g.tempfile", pid=1, flags='d').strip() 611 fd = open(n1, 'w') 612 fd.write( 613 "prec_1|1\n" 614 "prec_2|3\n" 615 "prec_3|5\n" 616 "prec_4|7\n" 617 "prec_5|11\n" 618 "prec_6|13\n" 619 ) 620 fd.close() 621 name = 'relpoint' 622 grass.run_command( 623 't.create', 624 overwrite=True, 625 type='strds', 626 temporaltype='relative', 627 output=name, 628 title="A test with input files", 629 descr="A test with input files") 630 631 grass.run_command( 632 't.register', 633 unit="day", 634 input=name, 635 file=n1, 636 overwrite=True) 637 return name 638 639if __name__ == '__main__': 640 641 test() 642