tgis_core.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. """!@package grass.script.tgis_core
  2. @brief GRASS Python scripting module (temporal GIS functions)
  3. Temporal GIS core functions to be used in Python tgis packages.
  4. This class provides the SQL interface for serialization and deserialization
  5. of map and space time dataset data.
  6. Usage:
  7. @code
  8. from grass.script import tgis_core as grass
  9. grass.create_temporal_database()
  10. ...
  11. @endcode
  12. (C) 2008-2011 by the GRASS Development Team
  13. This program is free software under the GNU General Public
  14. License (>=v2). Read the file COPYING that comes with GRASS
  15. for details.
  16. @author Soeren Gebbert
  17. """
  18. import os
  19. import sqlite3
  20. import core
  21. import copy
  22. from datetime import datetime, date, time, timedelta
  23. ###############################################################################
  24. def get_grass_location_db_path():
  25. grassenv = core.gisenv()
  26. dbpath = os.path.join(grassenv["GISDBASE"], grassenv["LOCATION_NAME"])
  27. return os.path.join(dbpath, "grass.db")
  28. ###############################################################################
  29. def get_sql_template_path():
  30. base = os.getenv("GISBASE")
  31. base_etc = os.path.join(base, "etc")
  32. return os.path.join(base_etc, "sql")
  33. def test_increment_datetime_by_string():
  34. dt = datetime(2001, 9, 1, 0, 0, 0)
  35. string = "60 seconds, 4 minutes, 12 hours, 10 days, 1 weeks, 5 months, 1 years"
  36. dt1 = datetime(2003,2,18,12,5,0)
  37. dt2 = increment_datetime_by_string(dt, string)
  38. delta = dt1 -dt2
  39. if delta.days != 0 or delta.seconds != 0:
  40. core.fatal("increment computation is wrong")
  41. def increment_datetime_by_string(mydate, increment, mult = 1):
  42. """Return a new datetime object incremented with the provided relative dates specified as string.
  43. Additional a multiplier can be specified to multiply the increment bevor adding to the provided datetime object.
  44. @mydate A datetime object to incremented
  45. @increment A string providing increment information:
  46. The string may include comma separated values of type seconds, minutes, hours, days, weeks, months and years
  47. Example: Increment the datetime 2001-01-01 00:00:00 with "60 seconds, 4 minutes, 12 hours, 10 days, 1 weeks, 5 months, 1 years"
  48. will result in the datetime 2003-02-18 12:05:00
  49. @mult A multiplier, default is 1
  50. """
  51. if increment:
  52. seconds = 0
  53. minutes = 0
  54. hours = 0
  55. days = 0
  56. weeks = 0
  57. months = 0
  58. years = 0
  59. inclist = []
  60. # Split the increment string
  61. incparts = increment.split(",")
  62. for incpart in incparts:
  63. inclist.append(incpart.strip().split(" "))
  64. for inc in inclist:
  65. if inc[1].find("seconds") >= 0:
  66. seconds = mult * int(inc[0])
  67. elif inc[1].find("minutes") >= 0:
  68. minutes = mult * int(inc[0])
  69. elif inc[1].find("hours") >= 0:
  70. hours = mult * int(inc[0])
  71. elif inc[1].find("days") >= 0:
  72. days = mult * int(inc[0])
  73. elif inc[1].find("weeks") >= 0:
  74. weeks = mult * int(inc[0])
  75. elif inc[1].find("months") >= 0:
  76. months = mult * int(inc[0])
  77. elif inc[1].find("years") >= 0:
  78. years = mult * int(inc[0])
  79. else:
  80. core.fatal("Wrong increment format: " + increment)
  81. return increment_datetime(mydate, years, months, weeks, days, hours, minutes, seconds)
  82. return mydate
  83. ###############################################################################
  84. def increment_datetime(mydate, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0):
  85. """Return a new datetime object incremented with the provided relative dates"""
  86. tdelta_seconds = timedelta(seconds=seconds)
  87. tdelta_minutes = timedelta(minutes=minutes)
  88. tdelta_hours = timedelta(hours=hours)
  89. tdelta_days = timedelta(days=days)
  90. tdelta_weeks = timedelta(weeks=weeks)
  91. tdelta_months = timedelta(0)
  92. tdelta_years = timedelta(0)
  93. if months > 0:
  94. # Compute the actual number of days in the month to add as timedelta
  95. year = mydate.year
  96. month = mydate.month
  97. all_months = int(months + month)
  98. years_to_add = int(all_months/12)
  99. residual_months = all_months%12
  100. # Make a deep copy of the datetime object
  101. dt1 = copy.copy(mydate)
  102. # Make sure the montha starts with a 1
  103. if residual_months == 0:
  104. residual_months = 1
  105. dt1 = dt1.replace(year = year + years_to_add, month = residual_months)
  106. tdelta_months = dt1 - mydate
  107. if years > 0:
  108. # Make a deep copy of the datetime object
  109. dt1 = copy.copy(mydate)
  110. # Compute the number of days
  111. dt1 = dt1.replace(year=mydate.year + int(years))
  112. tdelta_years = dt1 - mydate
  113. return mydate + tdelta_seconds + tdelta_minutes + tdelta_hours + \
  114. tdelta_days + tdelta_weeks + tdelta_months + tdelta_years
  115. ###############################################################################
  116. def create_temporal_database():
  117. """This function creates the grass location database structure for raster, vector and raster3d maps
  118. as well as for the space-time datasets strds, str3ds and stvds"""
  119. database = get_grass_location_db_path()
  120. # Check if it already exists
  121. if os.path.exists(database):
  122. return False
  123. # Read all SQL scripts and templates
  124. map_tables_template_sql = open(os.path.join(get_sql_template_path(), "map_tables_template.sql"), 'r').read()
  125. raster_metadata_sql = open(os.path.join(get_sql_template_path(), "raster_metadata_table.sql"), 'r').read()
  126. raster3d_metadata_sql = open(os.path.join(get_sql_template_path(), "raster3d_metadata_table.sql"), 'r').read()
  127. vector_metadata_sql = open(os.path.join(get_sql_template_path(), "vector_metadata_table.sql"), 'r').read()
  128. stds_tables_template_sql = open(os.path.join(get_sql_template_path(), "stds_tables_template.sql"), 'r').read()
  129. strds_metadata_sql = open(os.path.join(get_sql_template_path(), "strds_metadata_table.sql"), 'r').read()
  130. str3ds_metadata_sql = open(os.path.join(get_sql_template_path(), "str3ds_metadata_table.sql"), 'r').read()
  131. stvds_metadata_sql = open(os.path.join(get_sql_template_path(), "stvds_metadata_table.sql"), 'r').read()
  132. # Create the raster, raster3d and vector tables
  133. raster_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "raster")
  134. vector_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "vector")
  135. raster3d_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "raster3d")
  136. # Create the space-time raster, raster3d and vector dataset tables
  137. strds_tables_sql = stds_tables_template_sql.replace("STDS", "strds")
  138. stvds_tables_sql = stds_tables_template_sql.replace("STDS", "stvds")
  139. str3ds_tables_sql = stds_tables_template_sql.replace("STDS", "str3ds")
  140. # Check for completion
  141. sqlite3.complete_statement(raster_tables_sql)
  142. sqlite3.complete_statement(vector_tables_sql)
  143. sqlite3.complete_statement(raster3d_tables_sql)
  144. sqlite3.complete_statement(raster_metadata_sql)
  145. sqlite3.complete_statement(vector_metadata_sql)
  146. sqlite3.complete_statement(raster3d_metadata_sql)
  147. sqlite3.complete_statement(strds_tables_sql)
  148. sqlite3.complete_statement(stvds_tables_sql)
  149. sqlite3.complete_statement(str3ds_tables_sql)
  150. sqlite3.complete_statement(strds_metadata_sql)
  151. sqlite3.complete_statement(stvds_metadata_sql)
  152. sqlite3.complete_statement(str3ds_metadata_sql)
  153. # Connect to database
  154. connection = sqlite3.connect(database)
  155. cursor = connection.cursor()
  156. # Execute the SQL statements
  157. # Create the global tables for the native grass datatypes
  158. cursor.executescript(raster_tables_sql)
  159. cursor.executescript(raster_metadata_sql)
  160. cursor.executescript(vector_tables_sql)
  161. cursor.executescript(vector_metadata_sql)
  162. cursor.executescript(raster3d_tables_sql)
  163. cursor.executescript(raster3d_metadata_sql)
  164. # Create the tables for the new space-time datatypes
  165. cursor.executescript(strds_tables_sql)
  166. cursor.executescript(strds_metadata_sql)
  167. cursor.executescript(stvds_tables_sql)
  168. cursor.executescript(stvds_metadata_sql)
  169. cursor.executescript(str3ds_tables_sql)
  170. cursor.executescript(str3ds_metadata_sql)
  171. connection.commit()
  172. cursor.close()
  173. ###############################################################################
  174. class dict_sql_serializer(object):
  175. def __init__(self):
  176. self.D = {}
  177. def serialize(self, type, table, where=None):
  178. """Convert the internal dictionary into a string of semicolon separated SQL statements
  179. The keys are the colum names and the values are the row entries
  180. @type must be SELECT. INSERT, UPDATE
  181. @table The name of the table to select, insert or update
  182. @where The optinal where statment
  183. @return the sql string
  184. """
  185. sql = ""
  186. args = []
  187. # Create ordered select statement
  188. if type == "SELECT":
  189. sql += 'SELECT '
  190. count = 0
  191. for key in self.D.keys():
  192. if count == 0:
  193. sql += ' %s ' % key
  194. else:
  195. sql += ' , %s ' % key
  196. count += 1
  197. sql += ' FROM ' + table + ' '
  198. if where:
  199. sql += where
  200. # Create insert statement
  201. if type =="INSERT":
  202. count = 0
  203. sql += 'INSERT INTO ' + table + ' ('
  204. for key in self.D.keys():
  205. if count == 0:
  206. sql += ' %s ' % key
  207. else:
  208. sql += ' ,%s ' % key
  209. count += 1
  210. count = 0
  211. sql += ') VALUES ('
  212. for key in self.D.keys():
  213. if count == 0:
  214. sql += '?'
  215. else:
  216. sql += ',?'
  217. count += 1
  218. args.append(self.D[key])
  219. sql += ') '
  220. if where:
  221. sql += where
  222. # Create update statement
  223. if type =="UPDATE":
  224. count = 0
  225. sql += 'UPDATE ' + table + ' SET '
  226. for key in self.D.keys():
  227. # Update only entries which are not None
  228. if self.D[key] != None:
  229. if count == 0:
  230. sql += ' %s = ? ' % key
  231. else:
  232. sql += ' ,%s = ? ' % key
  233. count += 1
  234. args.append(self.D[key])
  235. if where:
  236. sql += where
  237. return sql, tuple(args)
  238. def deserialize(self, row):
  239. """Convert the content of the sqlite row into the internal dictionary"""
  240. self.D = {}
  241. for key in row.keys():
  242. self.D[key] = row[key]
  243. def clear(self):
  244. """Remove all the content of this class"""
  245. self.D = {}
  246. def print_self(self):
  247. print self.D
  248. def test(self):
  249. t = dict_sql_serializer()
  250. t.D["id"] = "soil@PERMANENT"
  251. t.D["name"] = "soil"
  252. t.D["mapset"] = "PERMANENT"
  253. t.D["creator"] = "soeren"
  254. t.D["creation_time"] = datetime.now()
  255. t.D["modification_time"] = datetime.now()
  256. t.D["revision"] = 1
  257. sql, values = t.serialize(type="SELECT", table="raster_base")
  258. print sql, '\n', values
  259. sql, values = t.serialize(type="INSERT", table="raster_base")
  260. print sql, '\n', values
  261. sql, values = t.serialize(type="UPDATE", table="raster_base")
  262. print sql, '\n', values
  263. ###############################################################################
  264. class sql_database_interface(dict_sql_serializer):
  265. """This is the sql database interface to sqlite3"""
  266. def __init__(self, table=None, ident=None, database=None):
  267. dict_sql_serializer.__init__(self)
  268. self.table = table # Name of the table, set in the subclass
  269. if database == None:
  270. self.database = get_grass_location_db_path()
  271. else:
  272. self.database = database
  273. self.ident = ident
  274. def get_table_name(self):
  275. return self.table
  276. def connect(self):
  277. self.connection = sqlite3.connect(self.database, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
  278. self.connection.row_factory = sqlite3.Row
  279. self.cursor = self.connection.cursor()
  280. def close(self):
  281. self.connection.commit()
  282. self.cursor.close()
  283. def get_delete_statement(self):
  284. return "DELETE FROM " + self.get_table_name() + " WHERE id = \"" + str(self.ident) + "\""
  285. def delete(self):
  286. self.connect()
  287. sql = self.get_delete_statement()
  288. #print sql
  289. self.cursor.execute(sql)
  290. self.close()
  291. def get_is_in_db_statement(self):
  292. return "SELECT id FROM " + self.get_table_name() + " WHERE id = \"" + str(self.ident) + "\""
  293. def is_in_db(self):
  294. self.connect()
  295. sql = self.get_is_in_db_statement()
  296. #print sql
  297. self.cursor.execute(sql)
  298. row = self.cursor.fetchone()
  299. self.close()
  300. # Nothing found
  301. if row == None:
  302. return False
  303. return True
  304. def get_select_statement(self):
  305. return self.serialize("SELECT", self.get_table_name(), "WHERE id = \"" + str(self.ident) + "\"")
  306. def select(self):
  307. self.connect()
  308. sql, args = self.get_select_statement()
  309. #print sql
  310. #print args
  311. if len(args) == 0:
  312. self.cursor.execute(sql)
  313. else:
  314. self.cursor.execute(sql, args)
  315. row = self.cursor.fetchone()
  316. # Nothing found
  317. if row == None:
  318. return False
  319. if len(row) > 0:
  320. self.deserialize(row)
  321. else:
  322. raise IOError
  323. self.close()
  324. return True
  325. def get_insert_statement(self):
  326. return self.serialize("INSERT", self.get_table_name())
  327. def insert(self):
  328. self.connect()
  329. sql, args = self.get_insert_statement()
  330. #print sql
  331. #print args
  332. self.cursor.execute(sql, args)
  333. self.close()
  334. def get_update_statement(self):
  335. return self.serialize("UPDATE", self.get_table_name(), "WHERE id = \"" + str(self.ident) + "\"")
  336. def update(self):
  337. if self.ident == None:
  338. raise IOError("Missing identifer");
  339. sql, args = self.get_update_statement()
  340. #print sql
  341. #print args
  342. self.connect()
  343. self.cursor.execute(sql, args)
  344. self.close()