temporal_manager.py 20 KB

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