temporal_manager.py 21 KB

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