abstract_dataset.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. # -*- coding: utf-8 -*-
  2. """!@package grass.temporal
  3. @brief GRASS Python scripting module (temporal GIS functions)
  4. Temporal GIS related functions to be used in temporal GIS Python library package.
  5. Usage:
  6. @code
  7. >>> import grass.temporal as tgis
  8. >>> ad = AbstractDataset()
  9. >>> ad.reset(ident="soil@PERMANENT")
  10. Traceback (most recent call last):
  11. File "/usr/lib/python2.7/doctest.py", line 1289, in __run
  12. compileflags, 1) in test.globs
  13. File "<doctest __main__[2]>", line 1, in <module>
  14. ad.reset(ident="soil@PERMANENT")
  15. File "AbstractDataset.py", line 53, in reset
  16. raise ImplementationError("This method must be implemented in the subclasses")
  17. ImplementationError: 'This method must be implemented in the subclasses'
  18. @endcode
  19. (C) 2011-2012 by the GRASS Development Team
  20. This program is free software under the GNU General Public
  21. License (>=v2). Read the file COPYING that comes with GRASS
  22. for details.
  23. @author Soeren Gebbert
  24. """
  25. import uuid
  26. import copy
  27. from temporal_extent import *
  28. from spatial_extent import *
  29. from metadata import *
  30. class ImplementationError(Exception):
  31. """!Exception raised for the calling of methods that should be implemented in
  32. sub classes.
  33. """
  34. def __init__(self, msg):
  35. self.msg = msg
  36. def __str__(self):
  37. return repr(self.msg)
  38. ###############################################################################
  39. class AbstractDataset(object):
  40. """!This is the base class for all datasets
  41. (raster, vector, raster3d, strds, stvds, str3ds)"""
  42. def __init__(self):
  43. pass
  44. def reset(self, ident):
  45. """!Reset the internal structure and set the identifier
  46. @param ident The identifier of the dataset that "name@mapset" or in case of vector maps "name:layer@mapset"
  47. """
  48. raise ImplementationError("This method must be implemented in the subclasses")
  49. def get_type(self):
  50. """!Return the type of this class as string
  51. The type can be "vect", "rast", "rast3d", "stvds", "strds" or "str3ds"
  52. @return "vect", "rast", "rast3d", "stvds", "strds" or "str3ds"
  53. """
  54. raise ImplementationError("This method must be implemented in the subclasses")
  55. def get_new_instance(self, ident):
  56. """!Return a new instance with the type of this class
  57. @param ident The identifier of the new dataset instance
  58. @return A new instance with the type of this object
  59. """
  60. raise ImplementationError("This method must be implemented in the subclasses")
  61. def spatial_overlapping(self, dataset):
  62. """!Return True if the spatial extents overlap
  63. @param dataset The abstract dataset to check spatial overlapping
  64. @return True if self and the provided dataset spatial overlap
  65. """
  66. raise ImplementationError("This method must be implemented in the subclasses")
  67. def spatial_relation(self, dataset):
  68. """!Return the spatial relationship between self and dataset
  69. @param dataset The abstract dataset to compute the spatial relation with self
  70. @return The spatial relationship as string
  71. """
  72. raise ImplementationError("This method must be implemented in the subclasses")
  73. def print_info(self):
  74. """!Print information about this class in human readable style"""
  75. raise ImplementationError("This method must be implemented in the subclasses")
  76. def print_shell_info(self):
  77. """!Print information about this class in shell style"""
  78. raise ImplementationError("This method must be implemented in the subclasses")
  79. def print_self(self):
  80. """!Print the content of the internal structure to stdout"""
  81. raise ImplementationError("This method must be implemented in the subclasses")
  82. def set_id(self, ident):
  83. self.base.set_id(ident)
  84. if self.is_time_absolute():
  85. self.absolute_time.set_id(ident)
  86. if self.is_time_relative():
  87. self.relative_time.set_id(ident)
  88. self.spatial_extent.set_id(ident)
  89. self.metadata.set_id(ident)
  90. def get_id(self):
  91. """!Return the unique identifier of the dataset
  92. @return The id of the dataset "name(:layer)@mapset" as string
  93. """
  94. return self.base.get_id()
  95. def get_name(self):
  96. """!Return the name
  97. @return The name of the dataset as string
  98. """
  99. return self.base.get_name()
  100. def get_mapset(self):
  101. """!Return the mapset
  102. @return The mapset in which the dataset was created as string
  103. """
  104. return self.base.get_mapset()
  105. def get_valid_time(self):
  106. """!Returns a tuple of the valid start and end time
  107. Start and end time can be either of type datetime or of type integer,
  108. depending on the temporal type.
  109. @return A tuple of (start_time, end_time)
  110. """
  111. start = None
  112. end = None
  113. if self.is_time_absolute():
  114. start = self.absolute_time.get_start_time()
  115. end = self.absolute_time.get_end_time()
  116. if self.is_time_relative():
  117. start = self.relative_time.get_start_time()
  118. end = self.relative_time.get_end_time()
  119. return (start, end)
  120. def get_absolute_time(self):
  121. """!Returns the start time, the end
  122. time and the timezone of the map as tuple
  123. @attention: The timezone is currently not used.
  124. The start time is of type datetime.
  125. The end time is of type datetime in case of interval time,
  126. or None on case of a time instance.
  127. @return A tuple of (start_time, end_time, timezone)
  128. """
  129. start = self.absolute_time.get_start_time()
  130. end = self.absolute_time.get_end_time()
  131. tz = self.absolute_time.get_timezone()
  132. return (start, end, tz)
  133. def get_relative_time(self):
  134. """!Returns the start time, the end
  135. time and the temporal unit of the dataset as tuple
  136. The start time is of type integer.
  137. The end time is of type integer in case of interval time,
  138. or None on case of a time instance.
  139. @return A tuple of (start_time, end_time, unit)
  140. """
  141. start = self.relative_time.get_start_time()
  142. end = self.relative_time.get_end_time()
  143. unit = self.relative_time.get_unit()
  144. return (start, end, unit)
  145. def get_relative_time_unit(self):
  146. """!Returns the relative time unit
  147. @return The relative time unit as string, None if not present
  148. """
  149. return self.relative_time.get_unit()
  150. def check_relative_time_unit(self, unit):
  151. """!Check if unit is of type year(s), month(s), day(s), hour(s),
  152. minute(s) or second(s)
  153. @param unit The unit string
  154. @return True if success, False otherwise
  155. """
  156. # Check unit
  157. units = ["year", "years", "month", "months", "day", "days", "hour",
  158. "hours", "minute", "minutes", "second", "seconds"]
  159. if unit not in units:
  160. return False
  161. return True
  162. def get_temporal_type(self):
  163. """!Return the temporal type of this dataset
  164. The temporal type can be absolute or relative
  165. @return The temporal type of the dataset as string
  166. """
  167. return self.base.get_ttype()
  168. def get_spatial_extent(self):
  169. """!Return the spatial extent as tuple
  170. Top and bottom are set to 0 in case of a two dimensional spatial extent.
  171. @return A the spatial extent as tuple (north, south, east, west, top, bottom)
  172. """
  173. return self.spatial_extent.get_spatial_extent()
  174. def select(self, dbif=None):
  175. """!Select temporal dataset entry from database and fill
  176. the internal structure
  177. The content of every dataset is stored in the temporal database.
  178. This method must be used to fill this object with the content
  179. from the temporal database.
  180. @param dbif The database interface to be used
  181. """
  182. dbif, connected = init_dbif(dbif)
  183. self.base.select(dbif)
  184. if self.is_time_absolute():
  185. self.absolute_time.select(dbif)
  186. if self.is_time_relative():
  187. self.relative_time.select(dbif)
  188. self.spatial_extent.select(dbif)
  189. self.metadata.select(dbif)
  190. if connected:
  191. dbif.close()
  192. def is_in_db(self, dbif=None):
  193. """!Check if the dataset is registered in the database
  194. @param dbif The database interface to be used
  195. @return True if the dataset is registered in the database
  196. """
  197. return self.base.is_in_db(dbif)
  198. def delete(self):
  199. """!Delete dataset from database if it exists"""
  200. raise ImplementationError("This method must be implemented in the subclasses")
  201. def insert(self, dbif=None, execute=True):
  202. """!Insert dataset into database
  203. @param dbif The database interface to be used
  204. @param execute If True the SQL statements will be executed.
  205. If False the prepared SQL statements are returned
  206. and must be executed by the caller.
  207. @return The SQL insert statement in case execute=False, or an empty string otherwise
  208. """
  209. dbif, connected = init_dbif(dbif)
  210. # Build the INSERT SQL statement
  211. statement = self.base.get_insert_statement_mogrified(dbif)
  212. if self.is_time_absolute():
  213. statement += self.absolute_time.get_insert_statement_mogrified(
  214. dbif)
  215. if self.is_time_relative():
  216. statement += self.relative_time.get_insert_statement_mogrified(
  217. dbif)
  218. statement += self.spatial_extent.get_insert_statement_mogrified(dbif)
  219. statement += self.metadata.get_insert_statement_mogrified(dbif)
  220. if execute:
  221. dbif.execute_transaction(statement)
  222. if connected:
  223. dbif.close()
  224. return ""
  225. if connected:
  226. dbif.close()
  227. return statement
  228. def update(self, dbif=None, execute=True, ident=None):
  229. """!Update the dataset entry in the database from the internal structure
  230. excluding None variables
  231. @param dbif The database interface to be used
  232. @param execute If True the SQL statements will be executed.
  233. If False the prepared SQL statements are returned
  234. and must be executed by the caller.
  235. @param ident The identifier to be updated, useful for renaming
  236. @return The SQL update statement in case execute=False, or an empty string otherwise
  237. """
  238. dbif, connected = init_dbif(dbif)
  239. # Build the UPDATE SQL statement
  240. statement = self.base.get_update_statement_mogrified(dbif, ident)
  241. if self.is_time_absolute():
  242. statement += self.absolute_time.get_update_statement_mogrified(
  243. dbif, ident)
  244. if self.is_time_relative():
  245. statement += self.relative_time.get_update_statement_mogrified(
  246. dbif, ident)
  247. statement += self.spatial_extent.get_update_statement_mogrified(dbif,
  248. ident)
  249. statement += self.metadata.get_update_statement_mogrified(dbif, ident)
  250. if execute:
  251. dbif.execute_transaction(statement)
  252. if connected:
  253. dbif.close()
  254. return ""
  255. if connected:
  256. dbif.close()
  257. return statement
  258. def update_all(self, dbif=None, execute=True, ident=None):
  259. """!Update the dataset entry in the database from the internal structure
  260. and include None variables.
  261. @param dbif The database interface to be used
  262. @param execute If True the SQL statements will be executed.
  263. If False the prepared SQL statements are returned
  264. and must be executed by the caller.
  265. @param ident The identifier to be updated, useful for renaming
  266. @return The SQL update statement in case execute=False, or an empty string otherwise
  267. """
  268. dbif, connected = init_dbif(dbif)
  269. # Build the UPDATE SQL statement
  270. statement = self.base.get_update_all_statement_mogrified(dbif, ident)
  271. if self.is_time_absolute():
  272. statement += self.absolute_time.get_update_all_statement_mogrified(
  273. dbif, ident)
  274. if self.is_time_relative():
  275. statement += self.relative_time.get_update_all_statement_mogrified(
  276. dbif, ident)
  277. statement += self.spatial_extent.get_update_all_statement_mogrified(
  278. dbif, ident)
  279. statement += self.metadata.get_update_all_statement_mogrified(dbif, ident)
  280. if execute:
  281. dbif.execute_transaction(statement)
  282. if connected:
  283. dbif.close()
  284. return ""
  285. if connected:
  286. dbif.close()
  287. return statement
  288. def set_time_to_absolute(self):
  289. """!Set the temporal type to absolute"""
  290. self.base.set_ttype("absolute")
  291. def set_time_to_relative(self):
  292. """!Set the temporal type to relative"""
  293. self.base.set_ttype("relative")
  294. def is_time_absolute(self):
  295. """!Return True in case the temporal type is absolute
  296. @return True if temporal type is absolute, False otherwise
  297. """
  298. if "temporal_type" in self.base.D:
  299. return self.base.get_ttype() == "absolute"
  300. else:
  301. return None
  302. def is_time_relative(self):
  303. """!Return True in case the temporal type is relative
  304. @return True if temporal type is relative, False otherwise
  305. """
  306. if "temporal_type" in self.base.D:
  307. return self.base.get_ttype() == "relative"
  308. else:
  309. return None
  310. def temporal_relation(self, dataset):
  311. """!Return the temporal relation of self and the provided dataset
  312. @return The temporal relation as string
  313. """
  314. if self.is_time_absolute() and dataset.is_time_absolute():
  315. return self.absolute_time.temporal_relation(dataset.absolute_time)
  316. if self.is_time_relative() and dataset.is_time_relative():
  317. return self.relative_time.temporal_relation(dataset.relative_time)
  318. return None
  319. def temporal_intersection(self, dataset):
  320. """!Intersect self with the provided datasetand
  321. return a new temporal extent with the new start and end time
  322. @param dataset The abstract dataset to temporal intersect with
  323. @return The new temporal extent with start and end time,
  324. or None in case of no intersection
  325. """
  326. if self.is_time_absolute() and dataset.is_time_absolute():
  327. return self.absolute_time.intersect(dataset.absolute_time)
  328. if self.is_time_relative() and dataset.is_time_relative():
  329. return self.relative_time.intersect(dataset.relative_time)
  330. return None
  331. ###############################################################################
  332. class AbstractDatasetComparisonKeyStartTime(object):
  333. """!This comparison key can be used to sort lists of abstract datasets
  334. by start time
  335. Example:
  336. # Return all maps in a space time raster dataset as map objects
  337. map_list = strds.get_registered_maps_as_objects()
  338. # Sort the maps in the list by start time
  339. sorted_map_list = sorted(
  340. map_list, key=AbstractDatasetComparisonKeyStartTime)
  341. """
  342. def __init__(self, obj, *args):
  343. self.obj = obj
  344. def __lt__(self, other):
  345. startA, endA = self.obj.get_valid_time()
  346. startB, endB = other.obj.get_valid_time()
  347. return startA < startB
  348. def __gt__(self, other):
  349. startA, endA = self.obj.get_valid_time()
  350. startB, endB = other.obj.get_valid_time()
  351. return startA > startB
  352. def __eq__(self, other):
  353. startA, endA = self.obj.get_valid_time()
  354. startB, endB = other.obj.get_valid_time()
  355. return startA == startB
  356. def __le__(self, other):
  357. startA, endA = self.obj.get_valid_time()
  358. startB, endB = other.obj.get_valid_time()
  359. return startA <= startB
  360. def __ge__(self, other):
  361. startA, endA = self.obj.get_valid_time()
  362. startB, endB = other.obj.get_valid_time()
  363. return startA >= startB
  364. def __ne__(self, other):
  365. startA, endA = self.obj.get_valid_time()
  366. startB, endB = other.obj.get_valid_time()
  367. return startA != startB
  368. ###############################################################################
  369. class AbstractDatasetComparisonKeyEndTime(object):
  370. """!This comparison key can be used to sort lists of abstract datasets
  371. by end time
  372. Example:
  373. # Return all maps in a space time raster dataset as map objects
  374. map_list = strds.get_registered_maps_as_objects()
  375. # Sort the maps in the list by end time
  376. sorted_map_list = sorted(
  377. map_list, key=AbstractDatasetComparisonKeyEndTime)
  378. """
  379. def __init__(self, obj, *args):
  380. self.obj = obj
  381. def __lt__(self, other):
  382. startA, endA = self.obj.get_valid_time()
  383. startB, endB = other.obj.get_valid_time()
  384. return endA < endB
  385. def __gt__(self, other):
  386. startA, endA = self.obj.get_valid_time()
  387. startB, endB = other.obj.get_valid_time()
  388. return endA > endB
  389. def __eq__(self, other):
  390. startA, endA = self.obj.get_valid_time()
  391. startB, endB = other.obj.get_valid_time()
  392. return endA == endB
  393. def __le__(self, other):
  394. startA, endA = self.obj.get_valid_time()
  395. startB, endB = other.obj.get_valid_time()
  396. return endA <= endB
  397. def __ge__(self, other):
  398. startA, endA = self.obj.get_valid_time()
  399. startB, endB = other.obj.get_valid_time()
  400. return endA >= endB
  401. def __ne__(self, other):
  402. startA, endA = self.obj.get_valid_time()
  403. startB, endB = other.obj.get_valid_time()
  404. return endA != endB
  405. ###############################################################################
  406. if __name__ == "__main__":
  407. import doctest
  408. doctest.testmod()