temporal_manager.py 21 KB

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