t.rast.accdetect.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. #!/usr/bin/env python3
  2. ############################################################################
  3. #
  4. # MODULE: t.rast.accdetect
  5. # AUTHOR(S): Soeren Gebbert
  6. #
  7. # PURPOSE: Detect accumulation pattern in temporally accumulated space time raster datasets created by t.rast.accumulate.
  8. # COPYRIGHT: (C) 2013-2017 by the GRASS Development Team
  9. #
  10. # This program is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation; either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. #############################################################################
  21. # %module
  22. # % description: Detects accumulation patterns in temporally accumulated space time raster datasets created by t.rast.accumulate.
  23. # % keyword: temporal
  24. # % keyword: accumulation
  25. # % keyword: raster
  26. # % keyword: time
  27. # %end
  28. # %option G_OPT_STRDS_INPUT
  29. # %end
  30. # %option G_OPT_STRDS_INPUT
  31. # % key: minimum
  32. # % description: Input space time raster dataset that specifies the minimum values to detect the accumulation pattern
  33. # % required: no
  34. # %end
  35. # %option G_OPT_STRDS_INPUT
  36. # % key: maximum
  37. # % description: Input space time raster dataset that specifies the maximum values to detect the accumulation pattern
  38. # % required: no
  39. # %end
  40. # %option G_OPT_STRDS_OUTPUT
  41. # % key: occurrence
  42. # % description: The output space time raster dataset that stores the occurrence of the the accumulation pattern using the provided data range
  43. # % required: yes
  44. # %end
  45. # %option G_OPT_STRDS_OUTPUT
  46. # % key: indicator
  47. # % description: The output space time raster dataset that stores the indication of the start, intermediate and end of the specified data range
  48. # % required: no
  49. # %end
  50. # %option
  51. # % key: start
  52. # % type: string
  53. # % description: The temporal starting point to begin the accumulation, eg '2001-01-01'
  54. # % required: yes
  55. # % multiple: no
  56. # %end
  57. # %option
  58. # % key: stop
  59. # % type: string
  60. # % description: The temporal date to stop the accumulation, eg '2009-01-01'
  61. # % required: no
  62. # % multiple: no
  63. # %end
  64. # %option
  65. # % key: cycle
  66. # % type: string
  67. # % description: The temporal cycle to restart the accumulation, eg '12 months'
  68. # % required: yes
  69. # % multiple: no
  70. # %end
  71. # %option
  72. # % key: offset
  73. # % type: string
  74. # % description: The temporal offset to the begin of the next cycle, eg '6 months'
  75. # % required: no
  76. # % multiple: no
  77. # %end
  78. # %option
  79. # % key: basename
  80. # % type: string
  81. # % label: Basename of the new generated output maps
  82. # % description: A numerical suffix separated by an underscore will be attached to create a unique identifier
  83. # % required: yes
  84. # % multiple: no
  85. # % gisprompt:
  86. # %end
  87. # %option
  88. # % key: suffix
  89. # % type: string
  90. # % description: Suffix to add at basename: set 'gran' for granularity, 'time' for the full time format, 'count' for numerical suffix with a specific number of digits (default %05)
  91. # % answer: gran
  92. # % required: no
  93. # % multiple: no
  94. # %end
  95. # %option
  96. # % key: range
  97. # % type: double
  98. # % key_desc: min,max
  99. # % description: The minimum and maximum value of the occurrence of accumulated values, these values will be used if the min/max space time raster datasets are not specified
  100. # % required: no
  101. # % multiple: no
  102. # %end
  103. # %option
  104. # % key: staend
  105. # % type: integer
  106. # % key_desc: start,intermediate,end
  107. # % description: The user defined values that indicate start, intermediate and end status in the indicator output space time raster dataset
  108. # % answer: 1,2,3
  109. # % required: no
  110. # % multiple: no
  111. # %end
  112. # %flag
  113. # % key: n
  114. # % description: Register empty maps in the output space time raster dataset, otherwise they will be deleted
  115. # %end
  116. # %flag
  117. # % key: r
  118. # % description: Reverse time direction in cyclic accumulation
  119. # %end
  120. import grass.script as grass
  121. # lazy imports at the end of the file
  122. ############################################################################
  123. range_relations = ["EQUALS", "DURING", "OVERLAPS", "OVERLAPPING", "CONTAINS"]
  124. def main():
  125. # Get the options
  126. input = options["input"]
  127. start = options["start"]
  128. stop = options["stop"]
  129. base = options["basename"]
  130. cycle = options["cycle"]
  131. offset = options["offset"]
  132. minimum = options["minimum"]
  133. maximum = options["maximum"]
  134. occurrence = options["occurrence"]
  135. range_ = options["range"]
  136. indicator = options["indicator"]
  137. staend = options["staend"]
  138. register_null = flags["n"]
  139. reverse = flags["r"]
  140. time_suffix = options["suffix"]
  141. grass.set_raise_on_error(True)
  142. # Make sure the temporal database exists
  143. tgis.init()
  144. # We need a database interface
  145. dbif = tgis.SQLDatabaseInterfaceConnection()
  146. dbif.connect()
  147. mapset = tgis.get_current_mapset()
  148. if input.find("@") >= 0:
  149. id = input
  150. else:
  151. id = input + "@" + mapset
  152. input_strds = tgis.SpaceTimeRasterDataset(id)
  153. if not input_strds.is_in_db():
  154. dbif.close()
  155. grass.fatal(
  156. _("Space time %s dataset <%s> not found")
  157. % (input_strds.get_output_map_instance(None).get_type(), id)
  158. )
  159. input_strds.select(dbif)
  160. dummy = input_strds.get_new_map_instance(None)
  161. # The occurrence space time raster dataset
  162. if occurrence:
  163. if not minimum or not maximum:
  164. if not range_:
  165. dbif.close()
  166. grass.fatal(
  167. _(
  168. "You need to set the range to compute the occurrence"
  169. " space time raster dataset"
  170. )
  171. )
  172. if occurrence.find("@") >= 0:
  173. occurrence_id = occurrence
  174. else:
  175. occurrence_id = occurrence + "@" + mapset
  176. occurrence_strds = tgis.SpaceTimeRasterDataset(occurrence_id)
  177. if occurrence_strds.is_in_db(dbif):
  178. if not grass.overwrite():
  179. dbif.close()
  180. grass.fatal(
  181. _(
  182. "Space time raster dataset <%s> is already in the "
  183. "database, use overwrite flag to overwrite"
  184. )
  185. % occurrence_id
  186. )
  187. # The indicator space time raster dataset
  188. if indicator:
  189. if not occurrence:
  190. dbif.close()
  191. grass.fatal(
  192. _(
  193. "You need to set the occurrence to compute the indicator"
  194. " space time raster dataset"
  195. )
  196. )
  197. if not staend:
  198. dbif.close()
  199. grass.fatal(
  200. _(
  201. "You need to set the staend options to compute the indicator"
  202. " space time raster dataset"
  203. )
  204. )
  205. if indicator.find("@") >= 0:
  206. indicator = indicator
  207. else:
  208. indicator_id = indicator + "@" + mapset
  209. indicator_strds = tgis.SpaceTimeRasterDataset(indicator_id)
  210. if indicator_strds.is_in_db(dbif):
  211. if not grass.overwrite():
  212. dbif.close()
  213. grass.fatal(
  214. _(
  215. "Space time raster dataset <%s> is already in the "
  216. "database, use overwrite flag to overwrite"
  217. )
  218. % indicator_id
  219. )
  220. staend = staend.split(",")
  221. indicator_start = int(staend[0])
  222. indicator_mid = int(staend[1])
  223. indicator_end = int(staend[2])
  224. # The minimum threshold space time raster dataset
  225. minimum_strds = None
  226. if minimum:
  227. if minimum.find("@") >= 0:
  228. minimum_id = minimum
  229. else:
  230. minimum_id = minimum + "@" + mapset
  231. minimum_strds = tgis.SpaceTimeRasterDataset(minimum_id)
  232. if not minimum_strds.is_in_db():
  233. dbif.close()
  234. grass.fatal(
  235. _("Space time raster dataset <%s> not found") % (minimum_strds.get_id())
  236. )
  237. if minimum_strds.get_temporal_type() != input_strds.get_temporal_type():
  238. dbif.close()
  239. grass.fatal(
  240. _("Temporal type of input strds and minimum strds must be equal")
  241. )
  242. minimum_strds.select(dbif)
  243. # The maximum threshold space time raster dataset
  244. maximum_strds = None
  245. if maximum:
  246. if maximum.find("@") >= 0:
  247. maximum_id = maximum
  248. else:
  249. maximum_id = maximum + "@" + mapset
  250. maximum_strds = tgis.SpaceTimeRasterDataset(maximum_id)
  251. if not maximum_strds.is_in_db():
  252. dbif.close()
  253. grass.fatal(
  254. _("Space time raster dataset <%s> not found") % (maximum_strds.get_id())
  255. )
  256. if maximum_strds.get_temporal_type() != input_strds.get_temporal_type():
  257. dbif.close()
  258. grass.fatal(
  259. _("Temporal type of input strds and maximum strds must be equal")
  260. )
  261. maximum_strds.select(dbif)
  262. input_strds_start, input_strds_end = input_strds.get_temporal_extent_as_tuple()
  263. if input_strds.is_time_absolute():
  264. start = tgis.string_to_datetime(start)
  265. if stop:
  266. stop = tgis.string_to_datetime(stop)
  267. else:
  268. stop = input_strds_end
  269. else:
  270. start = int(start)
  271. if stop:
  272. stop = int(stop)
  273. else:
  274. stop = input_strds_end
  275. if input_strds.is_time_absolute():
  276. end = tgis.increment_datetime_by_string(start, cycle)
  277. else:
  278. end = start + cycle
  279. count = 1
  280. indi_count = 1
  281. occurrence_maps = {}
  282. indicator_maps = {}
  283. while input_strds_end > start and stop > start:
  284. # Make sure that the cyclic computation will stop at the correct time
  285. if stop and end > stop:
  286. end = stop
  287. where = "start_time >= '%s' AND start_time < '%s'" % (str(start), str(end))
  288. input_maps = input_strds.get_registered_maps_as_objects(where=where, dbif=dbif)
  289. grass.debug(len(input_maps))
  290. input_topo = tgis.SpatioTemporalTopologyBuilder()
  291. input_topo.build(input_maps, input_maps)
  292. if len(input_maps) == 0:
  293. continue
  294. grass.message(_("Processing cycle %s - %s" % (str(start), str(end))))
  295. count = compute_occurrence(
  296. occurrence_maps,
  297. input_strds,
  298. input_maps,
  299. start,
  300. base,
  301. count,
  302. time_suffix,
  303. mapset,
  304. where,
  305. reverse,
  306. range_,
  307. minimum_strds,
  308. maximum_strds,
  309. dbif,
  310. )
  311. # Indicator computation is based on the occurrence so we need to start it after
  312. # the occurrence cycle
  313. if indicator:
  314. num_maps = len(input_maps)
  315. for i in range(num_maps):
  316. if reverse:
  317. map = input_maps[num_maps - i - 1]
  318. else:
  319. map = input_maps[i]
  320. if (
  321. input_strds.get_temporal_type() == "absolute"
  322. and time_suffix == "gran"
  323. ):
  324. suffix = tgis.create_suffix_from_datetime(
  325. map.temporal_extent.get_start_time(),
  326. input_strds.get_granularity(),
  327. )
  328. indicator_map_name = "{ba}_indicator_{su}".format(
  329. ba=base, su=suffix
  330. )
  331. elif (
  332. input_strds.get_temporal_type() == "absolute"
  333. and time_suffix == "time"
  334. ):
  335. suffix = tgis.create_time_suffix(map)
  336. indicator_map_name = "{ba}_indicator_{su}".format(
  337. ba=base, su=suffix
  338. )
  339. else:
  340. indicator_map_name = tgis.create_numeric_suffix(
  341. base + "_indicator", indi_count, time_suffix
  342. )
  343. indicator_map_id = dummy.build_id(indicator_map_name, mapset)
  344. indicator_map = input_strds.get_new_map_instance(indicator_map_id)
  345. # Check if new map is in the temporal database
  346. if indicator_map.is_in_db(dbif):
  347. if grass.overwrite():
  348. # Remove the existing temporal database entry
  349. indicator_map.delete(dbif)
  350. indicator_map = input_strds.get_new_map_instance(
  351. indicator_map_id
  352. )
  353. else:
  354. grass.fatal(
  355. _(
  356. "Map <%s> is already registered in the temporal"
  357. " database, use overwrite flag to overwrite."
  358. )
  359. % (indicator_map.get_map_id())
  360. )
  361. curr_map = occurrence_maps[map.get_id()].get_name()
  362. # Reverse time
  363. if reverse:
  364. if i == 0:
  365. prev_map = curr_map
  366. subexpr1 = "null()"
  367. subexpr3 = "%i" % (indicator_start)
  368. elif i > 0 and i < num_maps - 1:
  369. prev_map = occurrence_maps[map.next().get_id()].get_name()
  370. next_map = occurrence_maps[map.prev().get_id()].get_name()
  371. # In case the previous map is null() set null() or the start indicator
  372. subexpr1 = "if(isnull(%s), null(), %i)" % (
  373. curr_map,
  374. indicator_start,
  375. )
  376. # In case the previous map was not null() if the current map is null() set null()
  377. # if the current map is not null() and the next map is not null() set
  378. # intermediate indicator, if the next map is null set the end indicator
  379. subexpr2 = "if(isnull(%s), %i, %i)" % (
  380. next_map,
  381. indicator_end,
  382. indicator_mid,
  383. )
  384. subexpr3 = "if(isnull(%s), null(), %s)" % (curr_map, subexpr2)
  385. expression = "%s = if(isnull(%s), %s, %s)" % (
  386. indicator_map_name,
  387. prev_map,
  388. subexpr1,
  389. subexpr3,
  390. )
  391. else:
  392. prev_map = occurrence_maps[map.next().get_id()].get_name()
  393. subexpr1 = "if(isnull(%s), null(), %i)" % (
  394. curr_map,
  395. indicator_start,
  396. )
  397. subexpr3 = "if(isnull(%s), null(), %i)" % (
  398. curr_map,
  399. indicator_mid,
  400. )
  401. else:
  402. if i == 0:
  403. prev_map = curr_map
  404. subexpr1 = "null()"
  405. subexpr3 = "%i" % (indicator_start)
  406. elif i > 0 and i < num_maps - 1:
  407. prev_map = occurrence_maps[map.prev().get_id()].get_name()
  408. next_map = occurrence_maps[map.next().get_id()].get_name()
  409. # In case the previous map is null() set null() or the start indicator
  410. subexpr1 = "if(isnull(%s), null(), %i)" % (
  411. curr_map,
  412. indicator_start,
  413. )
  414. # In case the previous map was not null() if the current map is null() set null()
  415. # if the current map is not null() and the next map is not null() set
  416. # intermediate indicator, if the next map is null set the end indicator
  417. subexpr2 = "if(isnull(%s), %i, %i)" % (
  418. next_map,
  419. indicator_end,
  420. indicator_mid,
  421. )
  422. subexpr3 = "if(isnull(%s), null(), %s)" % (curr_map, subexpr2)
  423. expression = "%s = if(isnull(%s), %s, %s)" % (
  424. indicator_map_name,
  425. prev_map,
  426. subexpr1,
  427. subexpr3,
  428. )
  429. else:
  430. prev_map = occurrence_maps[map.prev().get_id()].get_name()
  431. subexpr1 = "if(isnull(%s), null(), %i)" % (
  432. curr_map,
  433. indicator_start,
  434. )
  435. subexpr3 = "if(isnull(%s), null(), %i)" % (
  436. curr_map,
  437. indicator_mid,
  438. )
  439. expression = "%s = if(isnull(%s), %s, %s)" % (
  440. indicator_map_name,
  441. prev_map,
  442. subexpr1,
  443. subexpr3,
  444. )
  445. grass.debug(expression)
  446. grass.mapcalc(expression, overwrite=True)
  447. map_start, map_end = map.get_temporal_extent_as_tuple()
  448. if map.is_time_absolute():
  449. indicator_map.set_absolute_time(map_start, map_end)
  450. else:
  451. indicator_map.set_relative_time(
  452. map_start, map_end, map.get_relative_time_unit()
  453. )
  454. indicator_maps[map.get_id()] = indicator_map
  455. indi_count += 1
  456. # Increment the cycle
  457. start = end
  458. if input_strds.is_time_absolute():
  459. start = end
  460. if offset:
  461. start = tgis.increment_datetime_by_string(end, offset)
  462. end = tgis.increment_datetime_by_string(start, cycle)
  463. else:
  464. if offset:
  465. start = end + offset
  466. end = start + cycle
  467. empty_maps = []
  468. create_strds_register_maps(
  469. input_strds, occurrence_strds, occurrence_maps, register_null, empty_maps, dbif
  470. )
  471. if indicator:
  472. create_strds_register_maps(
  473. input_strds,
  474. indicator_strds,
  475. indicator_maps,
  476. register_null,
  477. empty_maps,
  478. dbif,
  479. )
  480. dbif.close()
  481. # Remove empty maps
  482. if len(empty_maps) > 0:
  483. for map in empty_maps:
  484. grass.run_command(
  485. "g.remove", flags="f", type="raster", name=map.get_name(), quiet=True
  486. )
  487. ############################################################################
  488. def create_strds_register_maps(
  489. in_strds, out_strds, out_maps, register_null, empty_maps, dbif
  490. ):
  491. out_id = out_strds.get_id()
  492. if out_strds.is_in_db(dbif):
  493. if grass.overwrite():
  494. out_strds.delete(dbif)
  495. out_strds = in_strds.get_new_instance(out_id)
  496. temporal_type, semantic_type, title, description = in_strds.get_initial_values()
  497. out_strds.set_initial_values(temporal_type, semantic_type, title, description)
  498. out_strds.insert(dbif)
  499. # Register the maps in the database
  500. count = 0
  501. for map in out_maps.values():
  502. count += 1
  503. if count % 10 == 0:
  504. grass.percent(count, len(out_maps), 1)
  505. # Read the raster map data
  506. map.load()
  507. # In case of a empty map continue, do not register empty maps
  508. if not register_null:
  509. if map.metadata.get_min() is None and map.metadata.get_max() is None:
  510. empty_maps.append(map)
  511. continue
  512. # Insert map in temporal database
  513. map.insert(dbif)
  514. out_strds.register_map(map, dbif)
  515. out_strds.update_from_registered_maps(dbif)
  516. grass.percent(1, 1, 1)
  517. ############################################################################
  518. def compute_occurrence(
  519. occurrence_maps,
  520. input_strds,
  521. input_maps,
  522. start,
  523. base,
  524. count,
  525. tsuffix,
  526. mapset,
  527. where,
  528. reverse,
  529. range_,
  530. minimum_strds,
  531. maximum_strds,
  532. dbif,
  533. ):
  534. if minimum_strds:
  535. input_maps_minimum = input_strds.get_registered_maps_as_objects(
  536. where=where, dbif=dbif
  537. )
  538. minimum_maps = minimum_strds.get_registered_maps_as_objects(dbif=dbif)
  539. minimum_topo = tgis.SpatioTemporalTopologyBuilder()
  540. minimum_topo.build(input_maps_minimum, minimum_maps)
  541. if maximum_strds:
  542. input_maps_maximum = input_strds.get_registered_maps_as_objects(
  543. where=where, dbif=dbif
  544. )
  545. maximum_maps = maximum_strds.get_registered_maps_as_objects(dbif=dbif)
  546. maximum_topo = tgis.SpatioTemporalTopologyBuilder()
  547. maximum_topo.build(input_maps_maximum, maximum_maps)
  548. # Aggregate
  549. num_maps = len(input_maps)
  550. for i in range(num_maps):
  551. if reverse:
  552. map = input_maps[num_maps - i - 1]
  553. else:
  554. map = input_maps[i]
  555. # Compute the days since start
  556. input_start, input_end = map.get_temporal_extent_as_tuple()
  557. td = input_start - start
  558. if map.is_time_absolute():
  559. days = tgis.time_delta_to_relative_time(td)
  560. else:
  561. days = td
  562. if input_strds.get_temporal_type() == "absolute" and tsuffix == "gran":
  563. suffix = tgis.create_suffix_from_datetime(
  564. map.temporal_extent.get_start_time(), input_strds.get_granularity()
  565. )
  566. occurrence_map_name = "{ba}_{su}".format(ba=base, su=suffix)
  567. elif input_strds.get_temporal_type() == "absolute" and tsuffix == "time":
  568. suffix = tgis.create_time_suffix(map)
  569. occurrence_map_name = "{ba}_{su}".format(ba=base, su=suffix)
  570. else:
  571. occurrence_map_name = tgis.create_numeric_suffix(base, count, tsuffix)
  572. occurrence_map_id = map.build_id(occurrence_map_name, mapset)
  573. occurrence_map = input_strds.get_new_map_instance(occurrence_map_id)
  574. # Check if new map is in the temporal database
  575. if occurrence_map.is_in_db(dbif):
  576. if grass.overwrite():
  577. # Remove the existing temporal database entry
  578. occurrence_map.delete(dbif)
  579. occurrence_map = input_strds.get_new_map_instance(occurrence_map_id)
  580. else:
  581. grass.fatal(
  582. _(
  583. "Map <%s> is already registered in the temporal"
  584. " database, use overwrite flag to overwrite."
  585. )
  586. % (occurrence_map.get_map_id())
  587. )
  588. range_vals = range_.split(",")
  589. min = range_vals[0]
  590. max = range_vals[1]
  591. if minimum_strds:
  592. relations = input_maps_minimum[i].get_temporal_relations()
  593. for relation in range_relations:
  594. if relation in relations:
  595. min = str(relations[relation][0].get_id())
  596. break
  597. if maximum_strds:
  598. relations = input_maps_maximum[i].get_temporal_relations()
  599. for relation in range_relations:
  600. if relation in relations:
  601. max = str(relations[relation][0].get_id())
  602. break
  603. expression = "%s = if(%s > %s && %s < %s, %s, null())" % (
  604. occurrence_map_name,
  605. map.get_name(),
  606. min,
  607. map.get_name(),
  608. max,
  609. days,
  610. )
  611. grass.debug(expression)
  612. grass.mapcalc(expression, overwrite=True)
  613. map_start, map_end = map.get_temporal_extent_as_tuple()
  614. if map.is_time_absolute():
  615. occurrence_map.set_absolute_time(map_start, map_end)
  616. else:
  617. occurrence_map.set_relative_time(
  618. map_start, map_end, map.get_relative_time_unit()
  619. )
  620. # Store the new maps
  621. occurrence_maps[map.get_id()] = occurrence_map
  622. count += 1
  623. return count
  624. ############################################################################
  625. if __name__ == "__main__":
  626. options, flags = grass.parser()
  627. # lazy imports
  628. import grass.temporal as tgis
  629. main()