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