temporal_manager.py 21 KB

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