core.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. """!@package grass.temporal
  2. @brief GRASS Python scripting module (temporal GIS functions)
  3. Temporal GIS core functions to be used in Python scripts.
  4. Usage:
  5. @code
  6. import grass.temporal as tgis
  7. tgis.create_temporal_database()
  8. ...
  9. @endcode
  10. (C) 2008-2011 by the GRASS Development Team
  11. This program is free software under the GNU General Public
  12. License (>=v2). Read the file COPYING that comes with GRASS
  13. for details.
  14. @author Soeren Gebbert
  15. """
  16. import os
  17. import copy
  18. from datetime import datetime, date, time, timedelta
  19. import grass.script.core as core
  20. ###############################################################################
  21. # The chosen DBMI back-end can be defined on runtime
  22. # Check the grass environment before import
  23. core.run_command("t.connect", flags="c")
  24. kv = core.parse_command("t.connect", flags="pg")
  25. if kv.has_key("driver"):
  26. if kv["driver"] == "sqlite":
  27. import sqlite3 as dbmi
  28. elif kv["driver"] == "pg":
  29. import psycopg2 as dbmi
  30. # Needed for dictionary like cursors
  31. import psycopg2.extras
  32. else:
  33. core.fatal(_("Unable to initialize the temporal DBMI interface. Use t.connect to specify the driver and the database string"))
  34. else:
  35. # Use the default sqlite variable
  36. core.run_command("t.connect", flags="d")
  37. import sqlite3 as dbmi
  38. ###############################################################################
  39. def get_temporal_dbmi_init_string():
  40. kv = core.parse_command("t.connect", flags="pg")
  41. grassenv = core.gisenv()
  42. if dbmi.__name__ == "sqlite3":
  43. if kv.has_key("database"):
  44. string = kv["database"]
  45. string = string.replace("$GISDBASE", grassenv["GISDBASE"])
  46. string = string.replace("$LOCATION_NAME", grassenv["LOCATION_NAME"])
  47. return string
  48. else:
  49. core.fatal(_("Unable to initialize the temporal GIS DBMI interface. Use t.connect to specify the driver and the database string"))
  50. elif dbmi.__name__ == "psycopg2":
  51. if kv.has_key("database"):
  52. string = kv["database"]
  53. return string
  54. else:
  55. core.fatal(_("Unable to initialize the temporal GIS DBMI interface. Use t.connect to specify the driver and the database string"))
  56. return "dbname=grass_test user=soeren password=abcdefgh"
  57. ###############################################################################
  58. def get_sql_template_path():
  59. base = os.getenv("GISBASE")
  60. base_etc = os.path.join(base, "etc")
  61. return os.path.join(base_etc, "sql")
  62. ###############################################################################
  63. def create_temporal_database():
  64. """!This function creates the grass location database structure for raster, vector and raster3d maps
  65. as well as for the space-time datasets strds, str3ds and stvds
  66. This functions must be called before any spatio-temporal processing is started
  67. """
  68. database = get_temporal_dbmi_init_string()
  69. db_exists = False
  70. # Check if the database already exists
  71. if dbmi.__name__ == "sqlite3":
  72. # Check path of the sqlite database
  73. if os.path.exists(database):
  74. db_exists = True
  75. elif dbmi.__name__ == "psycopg2":
  76. # Connect to database
  77. connection = dbmi.connect(database)
  78. cursor = connection.cursor()
  79. # Check for raster_base table
  80. cursor.execute("SELECT EXISTS(SELECT * FROM information_schema.tables WHERE table_name=%s)", ('raster_base',))
  81. db_exists = cursor.fetchone()[0]
  82. connection.commit()
  83. cursor.close()
  84. if db_exists == True:
  85. return
  86. core.message(_("Create temporal database: %s"%(database)))
  87. # Read all SQL scripts and templates
  88. map_tables_template_sql = open(os.path.join(get_sql_template_path(), "map_tables_template.sql"), 'r').read()
  89. raster_metadata_sql = open(os.path.join(get_sql_template_path(), "raster_metadata_table.sql"), 'r').read()
  90. raster3d_metadata_sql = open(os.path.join(get_sql_template_path(), "raster3d_metadata_table.sql"), 'r').read()
  91. vector_metadata_sql = open(os.path.join(get_sql_template_path(), "vector_metadata_table.sql"), 'r').read()
  92. stds_tables_template_sql = open(os.path.join(get_sql_template_path(), "stds_tables_template.sql"), 'r').read()
  93. strds_metadata_sql = open(os.path.join(get_sql_template_path(), "strds_metadata_table.sql"), 'r').read()
  94. str3ds_metadata_sql = open(os.path.join(get_sql_template_path(), "str3ds_metadata_table.sql"), 'r').read()
  95. stvds_metadata_sql = open(os.path.join(get_sql_template_path(), "stvds_metadata_table.sql"), 'r').read()
  96. # Create the raster, raster3d and vector tables
  97. raster_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "raster")
  98. vector_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "vector")
  99. raster3d_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "raster3d")
  100. # Create the space-time raster, raster3d and vector dataset tables
  101. strds_tables_sql = stds_tables_template_sql.replace("STDS", "strds")
  102. stvds_tables_sql = stds_tables_template_sql.replace("STDS", "stvds")
  103. str3ds_tables_sql = stds_tables_template_sql.replace("STDS", "str3ds")
  104. # Connect to database
  105. connection = dbmi.connect(database)
  106. cursor = connection.cursor()
  107. if dbmi.__name__ == "sqlite3":
  108. sqlite3_delete_trigger_sql = open(os.path.join(get_sql_template_path(), "sqlite3_delete_trigger.sql"), 'r').read()
  109. # Execute the SQL statements for sqlite
  110. # Create the global tables for the native grass datatypes
  111. cursor.executescript(raster_tables_sql)
  112. cursor.executescript(raster_metadata_sql)
  113. cursor.executescript(vector_tables_sql)
  114. cursor.executescript(vector_metadata_sql)
  115. cursor.executescript(raster3d_tables_sql)
  116. cursor.executescript(raster3d_metadata_sql)
  117. # Create the tables for the new space-time datatypes
  118. cursor.executescript(strds_tables_sql)
  119. cursor.executescript(strds_metadata_sql)
  120. cursor.executescript(stvds_tables_sql)
  121. cursor.executescript(stvds_metadata_sql)
  122. cursor.executescript(str3ds_tables_sql)
  123. cursor.executescript(str3ds_metadata_sql)
  124. cursor.executescript(sqlite3_delete_trigger_sql)
  125. elif dbmi.__name__ == "psycopg2":
  126. # Execute the SQL statements for postgresql
  127. # Create the global tables for the native grass datatypes
  128. cursor.execute(raster_tables_sql)
  129. cursor.execute(raster_metadata_sql)
  130. cursor.execute(vector_tables_sql)
  131. cursor.execute(vector_metadata_sql)
  132. cursor.execute(raster3d_tables_sql)
  133. cursor.execute(raster3d_metadata_sql)
  134. # Create the tables for the new space-time datatypes
  135. cursor.execute(strds_tables_sql)
  136. cursor.execute(strds_metadata_sql)
  137. cursor.execute(stvds_tables_sql)
  138. cursor.execute(stvds_metadata_sql)
  139. cursor.execute(str3ds_tables_sql)
  140. cursor.execute(str3ds_metadata_sql)
  141. connection.commit()
  142. cursor.close()
  143. ###############################################################################
  144. class sql_database_interface_connection():
  145. """!This class represents the database interface connection
  146. The following DBMS are supported:
  147. * sqlite via the sqlite3 standard library
  148. * postgresql via psycopg2
  149. """
  150. def __init__(self):
  151. self.connected = False
  152. def connect(self):
  153. """!Connect to the DBMI to execute SQL statements
  154. Supported backends are sqlite3 and postgresql
  155. """
  156. init = get_temporal_dbmi_init_string()
  157. #print "Connect to", self.database
  158. if dbmi.__name__ == "sqlite3":
  159. self.connection = dbmi.connect(init, detect_types=dbmi.PARSE_DECLTYPES|dbmi.PARSE_COLNAMES)
  160. self.connection.row_factory = dbmi.Row
  161. self.connection.isolation_level = None
  162. self.cursor = self.connection.cursor()
  163. self.cursor.execute("PRAGMA synchronous = OFF")
  164. self.cursor.execute("PRAGMA journal_mode = MEMORY")
  165. elif dbmi.__name__ == "psycopg2":
  166. self.connection = dbmi.connect(init)
  167. #self.connection.set_isolation_level(dbmi.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
  168. self.cursor = self.connection.cursor(cursor_factory=dbmi.extras.DictCursor)
  169. self.connected = True
  170. def close(self):
  171. """!Close the DBMI connection"""
  172. #print "Close connection to", self.database
  173. self.connection.commit()
  174. self.cursor.close()
  175. self.connected = False
  176. def mogrify_sql_statement(self, content):
  177. """!Return the SQL statement and arguments as executable SQL string
  178. """
  179. sql = content[0]
  180. args = content[1]
  181. if dbmi.__name__ == "psycopg2":
  182. if len(args) == 0:
  183. return sql
  184. else:
  185. if self.connected:
  186. try:
  187. return self.cursor.mogrify(sql, args)
  188. except:
  189. print sql, args
  190. raise
  191. else:
  192. self.connect()
  193. statement = self.cursor.mogrify(sql, args)
  194. self.close()
  195. return statement
  196. elif dbmi.__name__ == "sqlite3":
  197. if len(args) == 0:
  198. return sql
  199. else:
  200. # Unfortunately as sqlite does not support
  201. # the transformation of sql strings and qmarked or
  202. # named arguments we must make our hands dirty
  203. # and do it by ourself. :(
  204. # Doors are open for SQL injection because of the
  205. # limited python sqlite3 implementation!!!
  206. pos = 0
  207. count = 0
  208. maxcount = 100
  209. statement = sql
  210. while count < maxcount:
  211. pos = statement.find("?", pos + 1)
  212. if pos == -1:
  213. break
  214. if args[count] == None:
  215. statement = "%sNULL%s"%(statement[0:pos], statement[pos+1:])
  216. elif isinstance(args[count], (int, long)):
  217. statement = "%s%d%s"%(statement[0:pos], args[count],statement[pos+1:])
  218. elif isinstance(args[count], float):
  219. statement = "%s%f%s"%(statement[0:pos], args[count],statement[pos+1:])
  220. else:
  221. # Default is a string, this works for datetime objects too
  222. statement = "%s\'%s\'%s"%(statement[0:pos], str(args[count]),statement[pos+1:])
  223. count += 1
  224. return statement
  225. def execute_transaction(self, statement):
  226. """!Execute a transactional SQL statement
  227. The BEGIN and END TRANSACTION statements will be added automatically
  228. to the sql statement
  229. @param statement The executable SQL statement or SQL script
  230. """
  231. connect = False
  232. if self.connected == False:
  233. self.connect()
  234. connect = True
  235. sql_script = ""
  236. sql_script += "BEGIN TRANSACTION;\n"
  237. sql_script += statement
  238. sql_script += "END TRANSACTION;"
  239. try:
  240. if dbmi.__name__ == "sqlite3":
  241. self.cursor.executescript(statement)
  242. else:
  243. self.cursor.execute(statement)
  244. self.connection.commit()
  245. except:
  246. if connect == True:
  247. self.close()
  248. core.error(_("Unable to execute transaction:\n %s") % (statement))
  249. raise
  250. if connect:
  251. self.close()
  252. ###############################################################################
  253. def init_dbif(dbif):
  254. """!This method checks if the database interface connection exists, if not a new one
  255. will be created, connected and True will be returned
  256. Usage code sample:
  257. dbif, connect = tgis.init_dbif(dbif)
  258. if connect:
  259. dbif.close()
  260. """
  261. if dbif == None:
  262. dbif = sql_database_interface_connection()
  263. dbif.connect()
  264. return dbif, True
  265. return dbif, False