abstract_dataset.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. """
  2. The abstract_dataset module provides the AbstractDataset class
  3. that is the base class for all map layer and Space Time Datasets.
  4. (C) 2011-2013 by the GRASS Development Team
  5. This program is free software under the GNU General Public
  6. License (>=v2). Read the file COPYING that comes with GRASS
  7. for details.
  8. :authors: Soeren Gebbert
  9. """
  10. from abc import ABCMeta, abstractmethod
  11. from .core import (
  12. get_tgis_message_interface,
  13. init_dbif,
  14. get_current_mapset,
  15. )
  16. from .temporal_topology_dataset_connector import TemporalTopologyDatasetConnector
  17. from .spatial_topology_dataset_connector import SpatialTopologyDatasetConnector
  18. ###############################################################################
  19. class AbstractDataset(
  20. SpatialTopologyDatasetConnector, TemporalTopologyDatasetConnector
  21. ):
  22. """This is the base class for all datasets
  23. (raster, vector, raster3d, strds, stvds, str3ds)"""
  24. __metaclass__ = ABCMeta
  25. def __init__(self):
  26. SpatialTopologyDatasetConnector.__init__(self)
  27. TemporalTopologyDatasetConnector.__init__(self)
  28. self.msgr = get_tgis_message_interface()
  29. def reset_topology(self):
  30. """Reset any information about temporal topology"""
  31. self.reset_spatial_topology()
  32. self.reset_temporal_topology()
  33. def get_number_of_relations(self):
  34. """Return a dictionary in which the keys are the relation names and the
  35. value are the number of relations.
  36. The following relations are available:
  37. Spatial relations:
  38. - equivalent
  39. - overlap
  40. - in
  41. - contain
  42. - meet
  43. - cover
  44. - covered
  45. Temporal relations:
  46. - equal
  47. - follows
  48. - precedes
  49. - overlaps
  50. - overlapped
  51. - during (including starts, finishes)
  52. - contains (including started, finished)
  53. - starts
  54. - started
  55. - finishes
  56. - finished
  57. To access topological information the spatial, temporal or booth
  58. topologies must be build first using the SpatioTemporalTopologyBuilder.
  59. :return: The dictionary with relations as keys and number as values or
  60. None in case the topology wasn't build
  61. """
  62. if self.is_temporal_topology_build() and not self.is_spatial_topology_build():
  63. return self.get_number_of_temporal_relations()
  64. elif self.is_spatial_topology_build() and not self.is_temporal_topology_build():
  65. self.get_number_of_spatial_relations()
  66. else:
  67. return (
  68. self.get_number_of_temporal_relations()
  69. + self.get_number_of_spatial_relations()
  70. )
  71. return None
  72. def set_topology_build_true(self):
  73. """Use this method when the spatio-temporal topology was build"""
  74. self.set_spatial_topology_build_true()
  75. self.set_temporal_topology_build_true()
  76. def set_topology_build_false(self):
  77. """Use this method when the spatio-temporal topology was not build"""
  78. self.set_spatial_topology_build_false()
  79. self.set_temporal_topology_build_false()
  80. def is_topology_build(self):
  81. """Check if the spatial and temporal topology was build
  82. :return: A dictionary with "spatial" and "temporal" as keys that
  83. have boolen values
  84. """
  85. d = {}
  86. d["spatial"] = self.is_spatial_topology_build()
  87. d["temporal"] = self.is_temporal_topology_build()
  88. return d
  89. def print_topology_info(self):
  90. if self.is_temporal_topology_build():
  91. self.print_temporal_topology_info()
  92. if self.is_spatial_topology_build():
  93. self.print_spatial_topology_info()
  94. def print_topology_shell_info(self):
  95. if self.is_temporal_topology_build():
  96. self.print_temporal_topology_shell_info()
  97. if self.is_spatial_topology_build():
  98. self.print_spatial_topology_shell_info()
  99. @abstractmethod
  100. def reset(self, ident):
  101. """Reset the internal structure and set the identifier
  102. This method creates the dataset specific internal objects
  103. that store the base information, the spatial and temporal extent
  104. and the metadata. It must be implemented in the dataset
  105. specific subclasses. This is the code for the
  106. vector dataset:
  107. .. code-block:: python
  108. self.base = VectorBase(ident=ident)
  109. self.absolute_time = VectorAbsoluteTime(ident=ident)
  110. self.relative_time = VectorRelativeTime(ident=ident)
  111. self.spatial_extent = VectorSpatialExtent(ident=ident)
  112. self.metadata = VectorMetadata(ident=ident)
  113. :param ident: The identifier of the dataset that "name@mapset" or
  114. in case of vector maps "name:layer@mapset"
  115. """
  116. @abstractmethod
  117. def is_stds(self):
  118. """Return True if this class is a space time dataset
  119. :return: True if this class is a space time dataset, False otherwise
  120. """
  121. @abstractmethod
  122. def get_type(self):
  123. """Return the type of this class as string
  124. The type can be "vector", "raster", "raster3d", "stvds", "strds" or "str3ds"
  125. :return: "vector", "raster", "raster3d", "stvds", "strds" or "str3ds"
  126. """
  127. @abstractmethod
  128. def get_new_instance(self, ident):
  129. """Return a new instance with the type of this class
  130. :param ident: The identifier of the new dataset instance
  131. :return: A new instance with the type of this object
  132. """
  133. @abstractmethod
  134. def spatial_overlapping(self, dataset):
  135. """Return True if the spatial extents overlap
  136. :param dataset: The abstract dataset to check spatial overlapping
  137. :return: True if self and the provided dataset spatial overlap
  138. """
  139. @abstractmethod
  140. def spatial_intersection(self, dataset):
  141. """Return the spatial intersection as spatial_extent
  142. object or None in case no intersection was found.
  143. :param dataset: The abstract dataset to intersect with
  144. :return: The intersection spatial extent
  145. """
  146. @abstractmethod
  147. def spatial_union(self, dataset):
  148. """Return the spatial union as spatial_extent
  149. object or None in case the extents does not overlap or meet.
  150. :param dataset: The abstract dataset to create a union with
  151. :return: The union spatial extent
  152. """
  153. @abstractmethod
  154. def spatial_disjoint_union(self, dataset):
  155. """Return the spatial union as spatial_extent object.
  156. :param dataset: The abstract dataset to create a union with
  157. :return: The union spatial extent
  158. """
  159. @abstractmethod
  160. def spatial_relation(self, dataset):
  161. """Return the spatial relationship between self and dataset
  162. :param dataset: The abstract dataset to compute the spatial
  163. relation with self
  164. :return: The spatial relationship as string
  165. """
  166. @abstractmethod
  167. def print_info(self):
  168. """Print information about this class in human readable style"""
  169. @abstractmethod
  170. def print_shell_info(self):
  171. """Print information about this class in shell style"""
  172. @abstractmethod
  173. def print_self(self):
  174. """Print the content of the internal structure to stdout"""
  175. def set_id(self, ident):
  176. """Set the identifier of the dataset"""
  177. self.base.set_id(ident)
  178. self.temporal_extent.set_id(ident)
  179. self.spatial_extent.set_id(ident)
  180. self.metadata.set_id(ident)
  181. if self.is_stds() is False:
  182. self.stds_register.set_id(ident)
  183. def get_id(self):
  184. """Return the unique identifier of the dataset
  185. :return: The id of the dataset "name(:layer)@mapset" as string
  186. """
  187. return self.base.get_id()
  188. def get_name(self):
  189. """Return the name
  190. :return: The name of the dataset as string
  191. """
  192. return self.base.get_name()
  193. def get_mapset(self):
  194. """Return the mapset
  195. :return: The mapset in which the dataset was created as string
  196. """
  197. return self.base.get_mapset()
  198. def get_temporal_extent_as_tuple(self):
  199. """Returns a tuple of the valid start and end time
  200. Start and end time can be either of type datetime or of type
  201. integer, depending on the temporal type.
  202. :return: A tuple of (start_time, end_time)
  203. """
  204. start = self.temporal_extent.get_start_time()
  205. end = self.temporal_extent.get_end_time()
  206. return (start, end)
  207. def get_absolute_time(self):
  208. """Returns the start time, the end
  209. time of the map as tuple
  210. The start time is of type datetime.
  211. The end time is of type datetime in case of interval time,
  212. or None on case of a time instance.
  213. :return: A tuple of (start_time, end_time)
  214. """
  215. start = self.absolute_time.get_start_time()
  216. end = self.absolute_time.get_end_time()
  217. return (start, end)
  218. def get_relative_time(self):
  219. """Returns the start time, the end
  220. time and the temporal unit of the dataset as tuple
  221. The start time is of type integer.
  222. The end time is of type integer in case of interval time,
  223. or None on case of a time instance.
  224. :return: A tuple of (start_time, end_time, unit)
  225. """
  226. start = self.relative_time.get_start_time()
  227. end = self.relative_time.get_end_time()
  228. unit = self.relative_time.get_unit()
  229. return (start, end, unit)
  230. def get_relative_time_unit(self):
  231. """Returns the relative time unit
  232. :return: The relative time unit as string, None if not present
  233. """
  234. return self.relative_time.get_unit()
  235. def check_relative_time_unit(self, unit):
  236. """Check if unit is of type year(s), month(s), day(s), hour(s),
  237. minute(s) or second(s)
  238. :param unit: The unit string
  239. :return: True if success, False otherwise
  240. """
  241. # Check unit
  242. units = [
  243. "year",
  244. "years",
  245. "month",
  246. "months",
  247. "day",
  248. "days",
  249. "hour",
  250. "hours",
  251. "minute",
  252. "minutes",
  253. "second",
  254. "seconds",
  255. ]
  256. if unit not in units:
  257. return False
  258. return True
  259. def get_temporal_type(self):
  260. """Return the temporal type of this dataset
  261. The temporal type can be absolute or relative
  262. :return: The temporal type of the dataset as string
  263. """
  264. return self.base.get_ttype()
  265. def get_spatial_extent_as_tuple(self):
  266. """Return the spatial extent as tuple
  267. Top and bottom are set to 0 in case of a two dimensional spatial
  268. extent.
  269. :return: A the spatial extent as tuple (north, south, east, west,
  270. top, bottom)
  271. """
  272. return self.spatial_extent.get_spatial_extent_as_tuple()
  273. def get_spatial_extent(self):
  274. """Return the spatial extent"""
  275. return self.spatial_extent
  276. def select(self, dbif=None, mapset=None):
  277. """Select temporal dataset entry from database and fill
  278. the internal structure
  279. The content of every dataset is stored in the temporal database.
  280. This method must be used to fill this object with the content
  281. from the temporal database.
  282. :param dbif: The database interface to be used
  283. :param mapset: The dbif connection to be used
  284. """
  285. dbif, connection_state_changed = init_dbif(dbif)
  286. # default mapset is mapset of this instance
  287. if mapset is None:
  288. mapset = self.get_mapset()
  289. self.base.select(dbif, mapset=mapset)
  290. self.temporal_extent.select(dbif, mapset=mapset)
  291. self.spatial_extent.select(dbif, mapset=mapset)
  292. self.metadata.select(dbif, mapset=mapset)
  293. if self.is_stds() is False:
  294. self.stds_register.select(dbif, mapset=mapset)
  295. if connection_state_changed:
  296. dbif.close()
  297. def is_in_db(self, dbif=None, mapset=None):
  298. """Check if the dataset is registered in the database
  299. :param dbif: The database interface to be used
  300. :param mapset: The dbif connection to be used
  301. :return: True if the dataset is registered in the database
  302. """
  303. return self.base.is_in_db(dbif, mapset)
  304. @abstractmethod
  305. def delete(self):
  306. """Delete dataset from database if it exists"""
  307. def insert(self, dbif=None, execute=True):
  308. """Insert dataset into database
  309. :param dbif: The database interface to be used
  310. :param execute: If True the SQL statements will be executed.
  311. If False the prepared SQL statements are returned
  312. and must be executed by the caller.
  313. :return: The SQL insert statement in case execute=False, or an
  314. empty string otherwise
  315. """
  316. # it must be possible to insert a map from a different
  317. # mapset in the temporal database of the current mapset
  318. # the temporal database must be in the current mapset
  319. dbif, connection_state_changed = init_dbif(dbif)
  320. self.msgr.debug(2, "AbstractDataset.insert...")
  321. # only modify database in current mapset
  322. mapset = get_current_mapset()
  323. # Build the INSERT SQL statement
  324. statement = self.base.get_insert_statement_mogrified(dbif)
  325. statement += self.temporal_extent.get_insert_statement_mogrified(dbif)
  326. statement += self.spatial_extent.get_insert_statement_mogrified(dbif)
  327. statement += self.metadata.get_insert_statement_mogrified(dbif)
  328. if self.is_stds() is False:
  329. statement += self.stds_register.get_insert_statement_mogrified(dbif)
  330. self.msgr.debug(2, "insert with %s" % statement)
  331. if execute:
  332. # database to be modified must be in the current mapset
  333. dbif.execute_transaction(statement, mapset=mapset)
  334. if connection_state_changed:
  335. dbif.close()
  336. return ""
  337. if connection_state_changed:
  338. dbif.close()
  339. return statement
  340. def update(self, dbif=None, execute=True, ident=None):
  341. """Update the dataset entry in the database from the internal structure
  342. excluding None variables
  343. :param dbif: The database interface to be used
  344. :param execute: If True the SQL statements will be executed.
  345. If False the prepared SQL statements are returned
  346. and must be executed by the caller.
  347. :param ident: The identifier to be updated, useful for renaming
  348. :return: The SQL update statement in case execute=False, or an
  349. empty string otherwise
  350. """
  351. dbif, connection_state_changed = init_dbif(dbif)
  352. # Build the UPDATE SQL statement
  353. statement = self.base.get_update_statement_mogrified(dbif, ident)
  354. statement += self.temporal_extent.get_update_statement_mogrified(dbif, ident)
  355. statement += self.spatial_extent.get_update_statement_mogrified(dbif, ident)
  356. statement += self.metadata.get_update_statement_mogrified(dbif, ident)
  357. if self.is_stds() is False:
  358. statement += self.stds_register.get_update_statement_mogrified(dbif, ident)
  359. if execute:
  360. dbif.execute_transaction(statement)
  361. if connection_state_changed:
  362. dbif.close()
  363. return ""
  364. if connection_state_changed:
  365. dbif.close()
  366. return statement
  367. def update_all(self, dbif=None, execute=True, ident=None):
  368. """Update the dataset entry in the database from the internal structure
  369. and include None variables.
  370. :param dbif: The database interface to be used
  371. :param execute: If True the SQL statements will be executed.
  372. If False the prepared SQL statements are returned
  373. and must be executed by the caller.
  374. :param ident: The identifier to be updated, useful for renaming
  375. :return: The SQL update statement in case execute=False, or an
  376. empty string otherwise
  377. """
  378. dbif, connection_state_changed = init_dbif(dbif)
  379. # Build the UPDATE SQL statement
  380. statement = self.base.get_update_all_statement_mogrified(dbif, ident)
  381. statement += self.temporal_extent.get_update_all_statement_mogrified(
  382. dbif, ident
  383. )
  384. statement += self.spatial_extent.get_update_all_statement_mogrified(dbif, ident)
  385. statement += self.metadata.get_update_all_statement_mogrified(dbif, ident)
  386. if self.is_stds() is False:
  387. statement += self.stds_register.get_update_all_statement_mogrified(
  388. dbif, ident
  389. )
  390. if execute:
  391. dbif.execute_transaction(statement)
  392. if connection_state_changed:
  393. dbif.close()
  394. return ""
  395. if connection_state_changed:
  396. dbif.close()
  397. return statement
  398. def is_time_absolute(self):
  399. """Return True in case the temporal type is absolute
  400. :return: True if temporal type is absolute, False otherwise
  401. """
  402. if "temporal_type" in self.base.D:
  403. return self.base.get_ttype() == "absolute"
  404. else:
  405. return None
  406. def is_time_relative(self):
  407. """Return True in case the temporal type is relative
  408. :return: True if temporal type is relative, False otherwise
  409. """
  410. if "temporal_type" in self.base.D:
  411. return self.base.get_ttype() == "relative"
  412. else:
  413. return None
  414. def get_temporal_extent(self):
  415. """Return the temporal extent of the correct internal type"""
  416. if self.is_time_absolute():
  417. return self.absolute_time
  418. if self.is_time_relative():
  419. return self.relative_time
  420. return None
  421. temporal_extent = property(fget=get_temporal_extent)
  422. def temporal_relation(self, dataset):
  423. """Return the temporal relation of self and the provided dataset
  424. :return: The temporal relation as string
  425. """
  426. return self.temporal_extent.temporal_relation(dataset.temporal_extent)
  427. def temporal_intersection(self, dataset):
  428. """Intersect self with the provided dataset and
  429. return a new temporal extent with the new start and end time
  430. :param dataset: The abstract dataset to temporal intersect with
  431. :return: The new temporal extent with start and end time,
  432. or None in case of no intersection
  433. """
  434. return self.temporal_extent.intersect(dataset.temporal_extent)
  435. def temporal_union(self, dataset):
  436. """Creates a union with the provided dataset and
  437. return a new temporal extent with the new start and end time.
  438. :param dataset: The abstract dataset to create temporal union with
  439. :return: The new temporal extent with start and end time,
  440. or None in case of no intersection
  441. """
  442. return self.temporal_extent.union(dataset.temporal_extent)
  443. def temporal_disjoint_union(self, dataset):
  444. """Creates a union with the provided dataset and
  445. return a new temporal extent with the new start and end time.
  446. :param dataset: The abstract dataset to create temporal union with
  447. :return: The new temporal extent with start and end time
  448. """
  449. return self.temporal_extent.disjoint_union(dataset.temporal_extent)
  450. ###############################################################################
  451. class AbstractDatasetComparisonKeyStartTime(object):
  452. """This comparison key can be used to sort lists of abstract datasets
  453. by start time
  454. Example:
  455. .. code-block:: python
  456. # Return all maps in a space time raster dataset as map objects
  457. map_list = strds.get_registered_maps_as_objects()
  458. # Sort the maps in the list by start time
  459. sorted_map_list = sorted(map_list, key=AbstractDatasetComparisonKeyStartTime)
  460. """
  461. def __init__(self, obj, *args):
  462. self.obj = obj
  463. def __lt__(self, other):
  464. startA, endA = self.obj.get_temporal_extent_as_tuple()
  465. startB, endB = other.obj.get_temporal_extent_as_tuple()
  466. return startA < startB
  467. def __gt__(self, other):
  468. startA, endA = self.obj.get_temporal_extent_as_tuple()
  469. startB, endB = other.obj.get_temporal_extent_as_tuple()
  470. return startA > startB
  471. def __eq__(self, other):
  472. startA, endA = self.obj.get_temporal_extent_as_tuple()
  473. startB, endB = other.obj.get_temporal_extent_as_tuple()
  474. return startA == startB
  475. def __le__(self, other):
  476. startA, endA = self.obj.get_temporal_extent_as_tuple()
  477. startB, endB = other.obj.get_temporal_extent_as_tuple()
  478. return startA <= startB
  479. def __ge__(self, other):
  480. startA, endA = self.obj.get_temporal_extent_as_tuple()
  481. startB, endB = other.obj.get_temporal_extent_as_tuple()
  482. return startA >= startB
  483. def __ne__(self, other):
  484. startA, endA = self.obj.get_temporal_extent_as_tuple()
  485. startB, endB = other.obj.get_temporal_extent_as_tuple()
  486. return startA != startB
  487. ###############################################################################
  488. class AbstractDatasetComparisonKeyEndTime(object):
  489. """This comparison key can be used to sort lists of abstract datasets
  490. by end time
  491. Example:
  492. .. code-block:: python
  493. # Return all maps in a space time raster dataset as map objects
  494. map_list = strds.get_registered_maps_as_objects()
  495. # Sort the maps in the list by end time
  496. sorted_map_list = sorted(map_list, key=AbstractDatasetComparisonKeyEndTime)
  497. """
  498. def __init__(self, obj, *args):
  499. self.obj = obj
  500. def __lt__(self, other):
  501. startA, endA = self.obj.get_temporal_extent_as_tuple()
  502. startB, endB = other.obj.get_temporal_extent_as_tuple()
  503. return endA < endB
  504. def __gt__(self, other):
  505. startA, endA = self.obj.get_temporal_extent_as_tuple()
  506. startB, endB = other.obj.get_temporal_extent_as_tuple()
  507. return endA > endB
  508. def __eq__(self, other):
  509. startA, endA = self.obj.get_temporal_extent_as_tuple()
  510. startB, endB = other.obj.get_temporal_extent_as_tuple()
  511. return endA == endB
  512. def __le__(self, other):
  513. startA, endA = self.obj.get_temporal_extent_as_tuple()
  514. startB, endB = other.obj.get_temporal_extent_as_tuple()
  515. return endA <= endB
  516. def __ge__(self, other):
  517. startA, endA = self.obj.get_temporal_extent_as_tuple()
  518. startB, endB = other.obj.get_temporal_extent_as_tuple()
  519. return endA >= endB
  520. def __ne__(self, other):
  521. startA, endA = self.obj.get_temporal_extent_as_tuple()
  522. startB, endB = other.obj.get_temporal_extent_as_tuple()
  523. return endA != endB
  524. ###############################################################################
  525. if __name__ == "__main__":
  526. import doctest
  527. doctest.testmod()