abstract_space_time_dataset.py 99 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474
  1. # -*- coding: utf-8 -*-
  2. """
  3. The abstract_space_time_dataset module provides the AbstractSpaceTimeDataset
  4. class that is the base class for all space time datasets.
  5. (C) 2011-2013 by the GRASS Development Team
  6. This program is free software under the GNU General Public
  7. License (>=v2). Read the file COPYING that comes with GRASS
  8. for details.
  9. :authors: Soeren Gebbert
  10. """
  11. from __future__ import print_function
  12. import sys
  13. import uuid
  14. import os
  15. import copy
  16. from datetime import datetime
  17. import gettext
  18. from abc import ABCMeta, abstractmethod
  19. from .core import init_dbif, get_sql_template_path, get_tgis_metadata, get_current_mapset, \
  20. get_enable_mapset_check
  21. from .abstract_dataset import AbstractDataset, AbstractDatasetComparisonKeyStartTime
  22. from .temporal_granularity import check_granularity_string, compute_absolute_time_granularity,\
  23. compute_relative_time_granularity
  24. from .spatio_temporal_relationships import count_temporal_topology_relationships, \
  25. print_spatio_temporal_topology_relationships, SpatioTemporalTopologyBuilder, \
  26. create_temporal_relation_sql_where_statement
  27. from .datetime_math import increment_datetime_by_string
  28. ###############################################################################
  29. class AbstractSpaceTimeDataset(AbstractDataset):
  30. """Abstract space time dataset class
  31. Base class for all space time datasets.
  32. This class represents an abstract space time dataset. Convenient functions
  33. to select, update, insert or delete objects of this type in the SQL
  34. temporal database exists as well as functions to register or unregister
  35. raster maps.
  36. Parts of the temporal logic are implemented in the SQL temporal
  37. database, like the computation of the temporal and spatial extent as
  38. well as the collecting of metadata.
  39. """
  40. __metaclass__ = ABCMeta
  41. def __init__(self, ident):
  42. AbstractDataset.__init__(self)
  43. self.reset(ident)
  44. self.map_counter = 0
  45. def create_map_register_name(self):
  46. """Create the name of the map register table of this space time
  47. dataset
  48. A uuid and the map type are used to create the table name
  49. ATTENTION: It must be assured that the base object has selected its
  50. content from the database.
  51. :return: The name of the map register table
  52. """
  53. uuid_rand = str(uuid.uuid4()).replace("-", "")
  54. table_name = self.get_new_map_instance(None).get_type() + "_map_register_" + uuid_rand
  55. return table_name
  56. @abstractmethod
  57. def get_new_map_instance(self, ident=None):
  58. """Return a new instance of a map which is associated
  59. with the type of this object
  60. :param ident: The unique identifier of the new object
  61. """
  62. @abstractmethod
  63. def get_map_register(self):
  64. """Return the name of the map register table
  65. :return: The map register table name
  66. """
  67. @abstractmethod
  68. def set_map_register(self, name):
  69. """Set the name of the map register table
  70. This table stores all map names which are registered
  71. in this space time dataset.
  72. This method only modifies this object and does not commit
  73. the modifications to the temporal database.
  74. :param name: The name of the register table
  75. """
  76. def print_self(self):
  77. """Print the content of the internal structure to stdout"""
  78. self.base.print_self()
  79. self.temporal_extent.print_self()
  80. self.spatial_extent.print_self()
  81. self.metadata.print_self()
  82. def print_info(self):
  83. """Print information about this class in human readable style"""
  84. if self.get_type() == "strds":
  85. # 1 2 3 4 5 6 7
  86. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  87. print(" +-------------------- Space Time Raster Dataset -----------------------------+")
  88. if self.get_type() == "str3ds":
  89. # 1 2 3 4 5 6 7
  90. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  91. print(" +-------------------- Space Time 3D Raster Dataset --------------------------+")
  92. if self.get_type() == "stvds":
  93. # 1 2 3 4 5 6 7
  94. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  95. print(" +-------------------- Space Time Vector Dataset -----------------------------+")
  96. print(" | |")
  97. self.base.print_info()
  98. self.temporal_extent.print_info()
  99. self.spatial_extent.print_info()
  100. self.metadata.print_info()
  101. print(" +----------------------------------------------------------------------------+")
  102. def print_shell_info(self):
  103. """Print information about this class in shell style"""
  104. self.base.print_shell_info()
  105. self.temporal_extent.print_shell_info()
  106. self.spatial_extent.print_shell_info()
  107. self.metadata.print_shell_info()
  108. def print_history(self):
  109. """Print history information about this class in human readable
  110. shell style
  111. """
  112. self.metadata.print_history()
  113. def set_initial_values(self, temporal_type, semantic_type=None,
  114. title=None, description=None):
  115. """Set the initial values of the space time dataset
  116. In addition the command creation string is generated
  117. an inserted into the metadata object.
  118. This method only modifies this object and does not commit
  119. the modifications to the temporal database.
  120. The insert() function must be called to commit
  121. this content into the temporal database.
  122. :param temporal_type: The temporal type of this space
  123. time dataset (absolute or relative)
  124. :param semantic_type: The semantic type of this dataset
  125. :param title: The title
  126. :param description: The description of this dataset
  127. """
  128. if temporal_type == "absolute":
  129. self.base.set_ttype("absolute")
  130. elif temporal_type == "relative":
  131. self.base.set_ttype("relative")
  132. else:
  133. self.msgr.fatal(_("Unknown temporal type \"%s\"") % (temporal_type))
  134. self.base.set_semantic_type(semantic_type)
  135. self.metadata.set_title(title)
  136. self.metadata.set_description(description)
  137. self.metadata.set_command(self.create_command_string())
  138. def set_aggregation_type(self, aggregation_type):
  139. """Set the aggregation type of the space time dataset
  140. :param aggregation_type: The aggregation type of the space time
  141. dataset
  142. """
  143. self.metadata.set_aggregation_type(aggregation_type)
  144. def update_command_string(self, dbif=None):
  145. """Append the current command string to any existing command string
  146. in the metadata class and calls metadata update
  147. :param dbif: The database interface to be used
  148. """
  149. self.metadata.select(dbif=dbif)
  150. command = self.metadata.get_command()
  151. if command is None:
  152. command = ""
  153. command += self.create_command_string()
  154. self.metadata.set_command(command)
  155. self.metadata.update(dbif=dbif)
  156. def create_command_string(self):
  157. """Create the command string that was used to create this
  158. space time dataset.
  159. The command string should be set with self.metadata.set_command()
  160. :return: The command string
  161. """
  162. # The grass module
  163. command = "# %s \n"%(str(datetime.today().strftime("%Y-%m-%d %H:%M:%S")))
  164. command += os.path.basename(sys.argv[0])
  165. # We will wrap the command line to fit into 80 character
  166. length = len(command)
  167. for token in sys.argv[1:]:
  168. # We need to remove specific characters
  169. token = token.replace("\'", " ")
  170. token = token.replace("\"", " ")
  171. # Check for sub strings
  172. if token.find("=") > 0:
  173. first = token.split("=")[0]
  174. second = ""
  175. flag = 0
  176. for t in token.split("=")[1:]:
  177. if flag == 0:
  178. second += t
  179. flag = 1
  180. else:
  181. second += "=" + t
  182. token = "%s=\"%s\"" % (first, second)
  183. if length + len(token) >= 76:
  184. command += "\n %s" % (token)
  185. length = len(token) + 4
  186. else:
  187. command += " %s" % (token)
  188. length += len(token) + 1
  189. command += "\n"
  190. return str(command)
  191. def get_semantic_type(self):
  192. """Return the semantic type of this dataset
  193. :return: The semantic type
  194. """
  195. return self.base.get_semantic_type()
  196. def get_initial_values(self):
  197. """Return the initial values: temporal_type,
  198. semantic_type, title, description"""
  199. temporal_type = self.get_temporal_type()
  200. semantic_type = self.base.get_semantic_type()
  201. title = self.metadata.get_title()
  202. description = self.metadata.get_description()
  203. return temporal_type, semantic_type, title, description
  204. def get_granularity(self):
  205. """Return the granularity of the space time dataset
  206. Granularity can be of absolute time or relative time.
  207. In case of absolute time a string containing an integer
  208. value and the time unit (years, months, days, hours, minuts,
  209. seconds). In case of relative time an integer value is expected.
  210. :return: The granularity
  211. """
  212. return self.temporal_extent.get_granularity()
  213. def set_granularity(self, granularity):
  214. """Set the granularity
  215. The granularity is usually computed by the space time dataset at
  216. runtime.
  217. Granularity can be of absolute time or relative time.
  218. In case of absolute time a string containing an integer
  219. value and the time unit (years, months, days, hours, minuts,
  220. seconds). In case of relative time an integer value is expected.
  221. This method only modifies this object and does not commit
  222. the modifications to the temporal database.
  223. :param granularity: The granularity of the dataset
  224. """
  225. temporal_type = self.get_temporal_type()
  226. check = check_granularity_string(granularity, temporal_type)
  227. if not check:
  228. self.msgr.fatal(_("Wrong granularity: \"%s\"") % str(granularity))
  229. if temporal_type == "absolute":
  230. self.base.set_ttype("absolute")
  231. elif temporal_type == "relative":
  232. self.base.set_ttype("relative")
  233. else:
  234. self.msgr.fatal(_("Unknown temporal type \"%s\"") % (temporal_type))
  235. self.temporal_extent.set_granularity(granularity)
  236. def set_relative_time_unit(self, unit):
  237. """Set the relative time unit which may be of type:
  238. years, months, days, hours, minutes or seconds
  239. All maps registered in a (relative time)
  240. space time dataset must have the same unit
  241. This method only modifies this object and does not commit
  242. the modifications to the temporal database.
  243. :param unit: The relative time unit
  244. """
  245. temporal_type = self.get_temporal_type()
  246. if temporal_type == "relative":
  247. if not self.check_relative_time_unit(unit):
  248. self.msgr.fatal(_("Unsupported temporal unit: %s") % (unit))
  249. self.relative_time.set_unit(unit)
  250. def insert(self, dbif=None, execute=True):
  251. """Insert the space time dataset content into the database from the internal
  252. structure
  253. The map register table will be created, so that maps
  254. can be registered.
  255. :param dbif: The database interface to be used
  256. :param execute: If True the SQL statements will be executed.
  257. If False the prepared SQL statements are
  258. returned and must be executed by the caller.
  259. :return: The SQL insert statement in case execute=False, or an
  260. empty string otherwise
  261. """
  262. dbif, connected = init_dbif(dbif)
  263. # We need to create the register table if it does not exist
  264. stds_register_table = self.get_map_register()
  265. # Create the map register table
  266. sql_path = get_sql_template_path()
  267. statement = ""
  268. # We need to create the map register table
  269. if stds_register_table is None:
  270. # Create table name
  271. stds_register_table = self.create_map_register_name()
  272. # Assure that the table and index do not exist
  273. #dbif.execute_transaction("DROP INDEX IF EXISTS %s; DROP TABLE IF EXISTS %s;"%(stds_register_table + "_index", stds_register_table))
  274. # Read the SQL template
  275. sql = open(os.path.join(sql_path,
  276. "stds_map_register_table_template.sql"),
  277. 'r').read()
  278. # Create a raster, raster3d or vector tables
  279. sql = sql.replace("SPACETIME_REGISTER_TABLE", stds_register_table)
  280. statement += sql
  281. if dbif.get_dbmi().__name__ == "sqlite3":
  282. statement += "CREATE INDEX %s_index ON %s (id);" % \
  283. (stds_register_table, stds_register_table)
  284. # Set the map register table name
  285. self.set_map_register(stds_register_table)
  286. self.msgr.debug(1, _("Created register table <%s> for space "
  287. "time %s dataset <%s>") %
  288. (stds_register_table,
  289. self.get_new_map_instance(None).get_type(),
  290. self.get_id()))
  291. statement += AbstractDataset.insert(self, dbif=dbif, execute=False)
  292. if execute:
  293. dbif.execute_transaction(statement)
  294. statement = ""
  295. if connected:
  296. dbif.close()
  297. return statement
  298. def get_map_time(self):
  299. """Return the type of the map time, interval, point, mixed or invalid
  300. """
  301. return self.temporal_extent.get_map_time()
  302. def count_temporal_types(self, maps=None, dbif=None):
  303. """Return the temporal type of the registered maps as dictionary
  304. The map list must be ordered by start time
  305. The temporal type can be:
  306. - point -> only the start time is present
  307. - interval -> start and end time
  308. - invalid -> No valid time point or interval found
  309. :param maps: A sorted (start_time) list of AbstractDataset objects
  310. :param dbif: The database interface to be used
  311. """
  312. if maps is None:
  313. maps = self.get_registered_maps_as_objects(
  314. where=None, order="start_time", dbif=dbif)
  315. time_invalid = 0
  316. time_point = 0
  317. time_interval = 0
  318. tcount = {}
  319. for i in range(len(maps)):
  320. # Check for point and interval data
  321. if maps[i].is_time_absolute():
  322. start, end = maps[i].get_absolute_time()
  323. if maps[i].is_time_relative():
  324. start, end, unit = maps[i].get_relative_time()
  325. if start is not None and end is not None:
  326. time_interval += 1
  327. elif start is not None and end is None:
  328. time_point += 1
  329. else:
  330. time_invalid += 1
  331. tcount["point"] = time_point
  332. tcount["interval"] = time_interval
  333. tcount["invalid"] = time_invalid
  334. return tcount
  335. def count_gaps(self, maps=None, dbif=None):
  336. """Count the number of gaps between temporal neighbors
  337. :param maps: A sorted (start_time) list of AbstractDataset objects
  338. :param dbif: The database interface to be used
  339. :return: The numbers of gaps between temporal neighbors
  340. """
  341. if maps is None:
  342. maps = self.get_registered_maps_as_objects(
  343. where=None, order="start_time", dbif=dbif)
  344. gaps = 0
  345. # Check for gaps
  346. for i in range(len(maps)):
  347. if i < len(maps) - 1:
  348. relation = maps[i + 1].temporal_relation(maps[i])
  349. if relation == "after":
  350. gaps += 1
  351. return gaps
  352. def print_spatio_temporal_relationships(self, maps=None, spatial=None,
  353. dbif=None):
  354. """Print the spatio-temporal relationships for each map of the space
  355. time dataset or for each map of the optional list of maps
  356. :param maps: a ordered by start_time list of map objects, if None
  357. the registred maps of the space time dataset are used
  358. :param spatial: This indicates if the spatial topology is created as
  359. well: spatial can be None (no spatial topology),
  360. "2D" using west, east, south, north or "3D" using
  361. west, east, south, north, bottom, top
  362. :param dbif: The database interface to be used
  363. """
  364. if maps is None:
  365. maps = self.get_registered_maps_as_objects(
  366. where=None, order="start_time", dbif=dbif)
  367. print_spatio_temporal_topology_relationships(maps1=maps, maps2=maps,
  368. spatial=spatial,
  369. dbif=dbif)
  370. def count_temporal_relations(self, maps=None, dbif=None):
  371. """Count the temporal relations between the registered maps.
  372. The map list must be ordered by start time.
  373. Temporal relations are counted by analysing the sparse upper right
  374. side temporal relationships matrix.
  375. :param maps: A sorted (start_time) list of AbstractDataset objects
  376. :param dbif: The database interface to be used
  377. :return: A dictionary with counted temporal relationships
  378. """
  379. if maps is None:
  380. maps = self.get_registered_maps_as_objects(
  381. where=None, order="start_time", dbif=dbif)
  382. return count_temporal_topology_relationships(maps1=maps, dbif=dbif)
  383. def check_temporal_topology(self, maps=None, dbif=None):
  384. """Check the temporal topology of all maps of the current space time
  385. dataset or of an optional list of maps
  386. Correct topology means, that time intervals are not overlap or
  387. that intervals does not contain other intervals.
  388. Equal time intervals are not allowed.
  389. The optional map list must be ordered by start time
  390. Allowed and not allowed temporal relationships for correct topology:
  391. - after -> allowed
  392. - precedes -> allowed
  393. - follows -> allowed
  394. - precedes -> allowed
  395. - equal -> not allowed
  396. - during -> not allowed
  397. - contains -> not allowed
  398. - overlaps -> not allowed
  399. - overlapped -> not allowed
  400. - starts -> not allowed
  401. - finishes -> not allowed
  402. - started -> not allowed
  403. - finished -> not allowed
  404. :param maps: An optional list of AbstractDataset objects, in case of
  405. None all maps of the space time dataset are checked
  406. :param dbif: The database interface to be used
  407. :return: True if topology is correct
  408. """
  409. if maps is None:
  410. maps = self.get_registered_maps_as_objects(
  411. where=None, order="start_time", dbif=dbif)
  412. relations = count_temporal_topology_relationships(maps1=maps,
  413. dbif=dbif)
  414. if relations is None:
  415. return False
  416. map_time = self.get_map_time()
  417. if map_time == "interval" or map_time == "mixed":
  418. if "equal" in relations and relations["equal"] > 0:
  419. return False
  420. if "during" in relations and relations["during"] > 0:
  421. return False
  422. if "contains" in relations and relations["contains"] > 0:
  423. return False
  424. if "overlaps" in relations and relations["overlaps"] > 0:
  425. return False
  426. if "overlapped" in relations and relations["overlapped"] > 0:
  427. return False
  428. if "starts" in relations and relations["starts"] > 0:
  429. return False
  430. if "finishes" in relations and relations["finishes"] > 0:
  431. return False
  432. if "started" in relations and relations["started"] > 0:
  433. return False
  434. if "finished" in relations and relations["finished"] > 0:
  435. return False
  436. elif map_time == "point":
  437. if "equal" in relations and relations["equal"] > 0:
  438. return False
  439. else:
  440. return False
  441. return True
  442. def sample_by_dataset(self, stds, method=None, spatial=False, dbif=None):
  443. """Sample this space time dataset with the temporal topology
  444. of a second space time dataset
  445. In case spatial is True, the spatial overlap between
  446. temporal related maps is performed. Only
  447. temporal related and spatial overlapping maps are returned.
  448. Return all registered maps as ordered (by start_time) object list.
  449. Each list entry is a list of map
  450. objects which are potentially located in temporal relation to the
  451. actual granule of the second space time dataset.
  452. Each entry in the object list is a dict. The actual sampler
  453. map and its temporal extent (the actual granule) and
  454. the list of samples are stored:
  455. .. code-block:: python
  456. list = self.sample_by_dataset(stds=sampler, method=[
  457. "during","overlap","contains","equal"])
  458. for entry in list:
  459. granule = entry["granule"]
  460. maplist = entry["samples"]
  461. for map in maplist:
  462. map.select()
  463. map.print_info()
  464. A valid temporal topology (no overlapping or inclusion allowed)
  465. is needed to get correct results in case of gaps in the sample
  466. dataset.
  467. Gaps between maps are identified as unregistered maps with id==None.
  468. The objects are initialized with their id's' and the spatio-temporal
  469. extent (temporal type, start time, end time, west, east, south,
  470. north, bottom and top).
  471. In case more map information are needed, use the select()
  472. method for each listed object.
  473. :param stds: The space time dataset to be used for temporal sampling
  474. :param method: This option specifies what sample method should be
  475. used. In case the registered maps are of temporal
  476. point type, only the start time is used for sampling.
  477. In case of mixed of interval data the user can chose
  478. between:
  479. - Example ["start", "during", "equals"]
  480. - start: Select maps of which the start time is
  481. located in the selection granule::
  482. map : s
  483. granule: s-----------------e
  484. map : s--------------------e
  485. granule: s-----------------e
  486. map : s--------e
  487. granule: s-----------------e
  488. - contains: Select maps which are temporal
  489. during the selection granule::
  490. map : s-----------e
  491. granule: s-----------------e
  492. - overlap: Select maps which temporal overlap
  493. the selection granule, this includes overlaps and
  494. overlapped::
  495. map : s-----------e
  496. granule: s-----------------e
  497. map : s-----------e
  498. granule: s----------e
  499. - during: Select maps which temporally contains
  500. the selection granule::
  501. map : s-----------------e
  502. granule: s-----------e
  503. - equals: Select maps which temporally equal
  504. to the selection granule::
  505. map : s-----------e
  506. granule: s-----------e
  507. - follows: Select maps which temporally follow
  508. the selection granule::
  509. map : s-----------e
  510. granule: s-----------e
  511. - precedes: Select maps which temporally precedes
  512. the selection granule::
  513. map : s-----------e
  514. granule: s-----------e
  515. All these methods can be combined. Method must be of
  516. type tuple including the identification strings.
  517. :param spatial: If set True additional the 2d spatial overlapping
  518. is used for selection -> spatio-temporal relation.
  519. The returned map objects will have temporal and
  520. spatial extents
  521. :param dbif: The database interface to be used
  522. :return: A list of lists of map objects or None in case nothing was
  523. found None
  524. """
  525. if self.get_temporal_type() != stds.get_temporal_type():
  526. self.msgr.error(_("The space time datasets must be of "
  527. "the same temporal type"))
  528. return None
  529. if stds.get_map_time() != "interval":
  530. self.msgr.error(_("The temporal map type of the sample "
  531. "dataset must be interval"))
  532. return None
  533. dbif, connected = init_dbif(dbif)
  534. relations = copy.deepcopy(method)
  535. # Tune the temporal relations
  536. if "start" in relations:
  537. if "overlapped" not in relations:
  538. relations.append("overlapped")
  539. if "starts" not in relations:
  540. relations.append("starts")
  541. if "started" not in relations:
  542. relations.append("started")
  543. if "finishes" not in relations:
  544. relations.append("finishes")
  545. if "contains" not in relations:
  546. relations.append("contains")
  547. if "equals" not in relations:
  548. relations.append("equals")
  549. if "overlap" in relations or "over" in relations:
  550. if "overlapped" not in relations:
  551. relations.append("overlapped")
  552. if "overlaps" not in relations:
  553. relations.append("overlaps")
  554. if "contain" in relations:
  555. if "contains" not in relations:
  556. relations.append("contains")
  557. # Remove start, equal, contain and overlap
  558. relations = [relation.upper().strip() for relation in relations
  559. if relation not in ["start", "overlap", "contain"]]
  560. # print(relations)
  561. tb = SpatioTemporalTopologyBuilder()
  562. if spatial:
  563. spatial = "2D"
  564. else:
  565. spatial = None
  566. mapsA = self.get_registered_maps_as_objects(dbif=dbif)
  567. mapsB = stds.get_registered_maps_as_objects_with_gaps(dbif=dbif)
  568. tb.build(mapsB, mapsA, spatial)
  569. obj_list = []
  570. for map in mapsB:
  571. result = {}
  572. maplist = []
  573. # Get map relations
  574. map_relations = map.get_temporal_relations()
  575. #print(map.get_temporal_extent_as_tuple())
  576. #for key in map_relations.keys():
  577. # if key not in ["NEXT", "PREV"]:
  578. # print(key, map_relations[key][0].get_temporal_extent_as_tuple())
  579. result["granule"] = map
  580. # Append the maps that fullfill the relations
  581. for relation in relations:
  582. if relation in map_relations.keys():
  583. for sample_map in map_relations[relation]:
  584. if sample_map not in maplist:
  585. maplist.append(sample_map)
  586. # Add an empty map if no map was found
  587. if not maplist:
  588. empty_map = self.get_new_map_instance(None)
  589. empty_map.set_spatial_extent(map.get_spatial_extent())
  590. empty_map.set_temporal_extent(map.get_temporal_extent())
  591. maplist.append(empty_map)
  592. result["samples"] = maplist
  593. obj_list.append(result)
  594. if connected:
  595. dbif.close()
  596. return obj_list
  597. def sample_by_dataset_sql(self, stds, method=None, spatial=False,
  598. dbif=None):
  599. """Sample this space time dataset with the temporal topology
  600. of a second space time dataset using SQL queries.
  601. This function is very slow for huge large space time datasets
  602. but can run several times in the same process without problems.
  603. The sample dataset must have "interval" as temporal map type,
  604. so all sample maps have valid interval time.
  605. In case spatial is True, the spatial overlap between
  606. temporal related maps is performed. Only
  607. temporal related and spatial overlapping maps are returned.
  608. Return all registered maps as ordered (by start_time) object list
  609. with "gap" map objects (id==None). Each list entry is a list of map
  610. objects which are potentially located in temporal relation to the
  611. actual granule of the second space time dataset.
  612. Each entry in the object list is a dict. The actual sampler
  613. map and its temporal extent (the actual granule) and
  614. the list of samples are stored:
  615. .. code-block:: python
  616. list = self.sample_by_dataset(stds=sampler, method=[
  617. "during","overlap","contain","equal"])
  618. for entry in list:
  619. granule = entry["granule"]
  620. maplist = entry["samples"]
  621. for map in maplist:
  622. map.select()
  623. map.print_info()
  624. A valid temporal topology (no overlapping or inclusion allowed)
  625. is needed to get correct results in case of gaps in the sample
  626. dataset.
  627. Gaps between maps are identified as unregistered maps with id==None.
  628. The objects are initialized with their id's' and the spatio-temporal
  629. extent (temporal type, start time, end time, west, east, south,
  630. north, bottom and top).
  631. In case more map information are needed, use the select()
  632. method for each listed object.
  633. :param stds: The space time dataset to be used for temporal sampling
  634. :param method: This option specifies what sample method should be
  635. used. In case the registered maps are of temporal
  636. point type, only the start time is used for sampling.
  637. In case of mixed of interval data the user can chose
  638. between:
  639. - Example ["start", "during", "equals"]
  640. - start: Select maps of which the start time is
  641. located in the selection granule::
  642. map : s
  643. granule: s-----------------e
  644. map : s--------------------e
  645. granule: s-----------------e
  646. map : s--------e
  647. granule: s-----------------e
  648. - contains: Select maps which are temporal
  649. during the selection granule::
  650. map : s-----------e
  651. granule: s-----------------e
  652. - overlap: Select maps which temporal overlap
  653. the selection granule, this includes overlaps and
  654. overlapped::
  655. map : s-----------e
  656. granule: s-----------------e
  657. map : s-----------e
  658. granule: s----------e
  659. - during: Select maps which temporally contains
  660. the selection granule::
  661. map : s-----------------e
  662. granule: s-----------e
  663. - equals: Select maps which temporally equal
  664. to the selection granule::
  665. map : s-----------e
  666. granule: s-----------e
  667. - follows: Select maps which temporally follow
  668. the selection granule::
  669. map : s-----------e
  670. granule: s-----------e
  671. - precedes: Select maps which temporally precedes
  672. the selection granule::
  673. map : s-----------e
  674. granule: s-----------e
  675. All these methods can be combined. Method must be of
  676. type tuple including the identification strings.
  677. :param spatial: If set True additional the 2d spatial overlapping
  678. is used for selection -> spatio-temporal relation.
  679. The returned map objects will have temporal and
  680. spatial extents
  681. :param dbif: The database interface to be used
  682. :return: A list of lists of map objects or None in case nothing was
  683. found None
  684. """
  685. use_start = False
  686. use_during = False
  687. use_overlap = False
  688. use_contain = False
  689. use_equal = False
  690. use_follows = False
  691. use_precedes = False
  692. # Initialize the methods
  693. if method is not None:
  694. for name in method:
  695. if name == "start":
  696. use_start = True
  697. if name == "during":
  698. use_during = True
  699. if name == "overlap":
  700. use_overlap = True
  701. if name == "contain" or name == "contains":
  702. use_contain = True
  703. if name == "equal" or name == "equals":
  704. use_equal = True
  705. if name == "follows":
  706. use_follows = True
  707. if name == "precedes":
  708. use_precedes = True
  709. else:
  710. use_during = True
  711. use_overlap = True
  712. use_contain = True
  713. use_equal = True
  714. if self.get_temporal_type() != stds.get_temporal_type():
  715. self.msgr.error(_("The space time datasets must be of "
  716. "the same temporal type"))
  717. return None
  718. if stds.get_map_time() != "interval":
  719. self.msgr.error(_("The temporal map type of the sample "
  720. "dataset must be interval"))
  721. return None
  722. # In case points of time are available, disable the interval specific
  723. # methods
  724. if self.get_map_time() == "point":
  725. use_start = True
  726. use_during = False
  727. use_overlap = False
  728. use_contain = False
  729. use_equal = False
  730. use_follows = False
  731. use_precedes = False
  732. dbif, connected = init_dbif(dbif)
  733. obj_list = []
  734. sample_maps = stds.get_registered_maps_as_objects_with_gaps(
  735. where=None, dbif=dbif)
  736. for granule in sample_maps:
  737. # Read the spatial extent
  738. if spatial:
  739. granule.spatial_extent.select(dbif)
  740. start, end = granule.get_temporal_extent_as_tuple()
  741. where = create_temporal_relation_sql_where_statement(
  742. start, end, use_start, use_during, use_overlap,
  743. use_contain, use_equal, use_follows, use_precedes)
  744. maps = self.get_registered_maps_as_objects(
  745. where, "start_time", dbif)
  746. result = {}
  747. result["granule"] = granule
  748. num_samples = 0
  749. maplist = []
  750. if maps is not None:
  751. for map in maps:
  752. # Read the spatial extent
  753. if spatial:
  754. map.spatial_extent.select(dbif)
  755. # Ignore spatial disjoint maps
  756. if not granule.spatial_overlapping(map):
  757. continue
  758. num_samples += 1
  759. maplist.append(copy.copy(map))
  760. # Fill with empty map in case no spatio-temporal relations found
  761. if maps is None or num_samples == 0:
  762. map = self.get_new_map_instance(None)
  763. if self.is_time_absolute():
  764. map.set_absolute_time(start, end)
  765. elif self.is_time_relative():
  766. map.set_relative_time(start, end,
  767. self.get_relative_time_unit())
  768. maplist.append(copy.copy(map))
  769. result["samples"] = maplist
  770. obj_list.append(copy.copy(result))
  771. if connected:
  772. dbif.close()
  773. return obj_list
  774. def get_registered_maps_as_objects_by_granularity(self, gran=None,
  775. dbif=None):
  776. """Return all registered maps as ordered (by start_time) object list
  777. with "gap" map objects (id==None) for spatio-temporal topological
  778. operations that require the temporal extent only.
  779. Each list entry is a list of AbstractMapDatasets objects
  780. which are potentially equal the actual granule, contain the
  781. actual granule or are located in the actual granule.
  782. Hence for each granule a list of AbstractMapDatasets can be
  783. expected.
  784. Maps that overlap the granule are ignored.
  785. The granularity of the space time dataset is used as increment in
  786. case the granule is not user defined.
  787. A valid temporal topology (no overlapping or inclusion allowed)
  788. is needed to get correct results.
  789. Space time datasets with interval time, time instances and mixed
  790. time are supported.
  791. Gaps between maps are identified as unregistered maps with id==None.
  792. The objects are initialized with their id's' and the spatio-temporal
  793. extent (temporal type, start time, end time, west, east, south,
  794. north, bottom and top).
  795. In case more map information are needed, use the select()
  796. method for each listed object.
  797. :param gran: The granularity string to be used, if None the
  798. granularity of the space time dataset is used.
  799. Absolute time has the format "number unit", relative
  800. time has the format "number".
  801. The unit in case of absolute time can be one of "second,
  802. seconds, minute, minutes, hour, hours, day, days, week,
  803. weeks, month, months, year, years". The unit of the
  804. relative time granule is always the space time dataset
  805. unit and can not be changed.
  806. :param dbif: The database interface to be used
  807. :return: ordered list of map lists. Each list represents a single
  808. granule, or None in case nothing found
  809. """
  810. dbif, connected = init_dbif(dbif)
  811. if gran is None:
  812. gran = self.get_granularity()
  813. check = check_granularity_string(gran, self.get_temporal_type())
  814. if not check:
  815. self.msgr.fatal(_("Wrong granularity: \"%s\"") % str(gran))
  816. start, end = self.get_temporal_extent_as_tuple()
  817. if start is None or end is None:
  818. return None
  819. maps = self.get_registered_maps_as_objects(dbif=dbif,
  820. order="start_time")
  821. if not maps:
  822. return None
  823. # We need to adjust the end time in case the the dataset has no
  824. # interval time, so we can catch time instances at the end
  825. if self.get_map_time() != "interval":
  826. if self.is_time_absolute():
  827. end = increment_datetime_by_string(end, gran)
  828. else:
  829. end = end + gran
  830. l = AbstractSpaceTimeDataset.resample_maplist_by_granularity(maps,
  831. start,
  832. end,
  833. gran)
  834. if connected:
  835. dbif.close()
  836. return l
  837. @staticmethod
  838. def resample_maplist_by_granularity(maps, start, end, gran):
  839. """Resample a list of AbstractMapDatasets by a given granularity
  840. The provided map list must be sorted by start time.
  841. A valid temporal topology (no overlapping or inclusion allowed)
  842. is needed to receive correct results.
  843. Maps with interval time, time instances and mixed
  844. time are supported.
  845. The temporal topology search order is as follows:
  846. 1. Maps that are equal to the actual granule are used
  847. 2. If no euqal found then maps that contain the actual granule
  848. are used
  849. 3. If no maps are found that contain the actual granule then maps
  850. are used that overlaps the actual granule
  851. 4. If no overlaps maps found then overlapped maps are used
  852. 5. If no overlapped maps are found then maps are used that are
  853. durin the actual granule
  854. Each entry in the resulting list is a list of
  855. AbstractMapDatasets objects.
  856. Hence for each granule a list of AbstractMapDatasets can be
  857. expected.
  858. Gaps between maps are identified as unregistered maps with id==None.
  859. :param maps: An ordered list (by start time) of AbstractMapDatasets
  860. objects. All maps must have the same temporal type
  861. and the same unit in case of relative time.
  862. :param start: The start time of the provided map list
  863. :param end: The end time of the provided map list
  864. :param gran: The granularity string to be used, if None the
  865. granularity of the space time dataset is used.
  866. Absolute time has the format "number unit", relative
  867. time has the format "number".
  868. The unit in case of absolute time can be one of "second,
  869. seconds, minute, minutes, hour, hours, day, days, week,
  870. weeks, month, months, year, years". The unit of the
  871. relative time granule is always the space time dataset
  872. unit and can not be changed.
  873. :return: ordered list of map lists. Each list represents a single
  874. granule, or None in case nothing found
  875. Usage:
  876. .. code-block:: python
  877. >>> import grass.temporal as tgis
  878. >>> maps = []
  879. >>> for i in xrange(3):
  880. ... map = tgis.RasterDataset("map%i@PERMANENT"%i)
  881. ... check = map.set_relative_time(i + 2, i + 3, "days")
  882. ... maps.append(map)
  883. >>> grans = tgis.AbstractSpaceTimeDataset.resample_maplist_by_granularity(maps,0,8,1)
  884. >>> for map_list in grans:
  885. ... print(map_list[0].get_id(), map_list[0].get_temporal_extent_as_tuple())
  886. None (0, 1)
  887. None (1, 2)
  888. map0@PERMANENT (2, 3)
  889. map1@PERMANENT (3, 4)
  890. map2@PERMANENT (4, 5)
  891. None (5, 6)
  892. None (6, 7)
  893. None (7, 8)
  894. >>> maps = []
  895. >>> map1 = tgis.RasterDataset("map1@PERMANENT")
  896. >>> check = map1.set_relative_time(2, 6, "days")
  897. >>> maps.append(map1)
  898. >>> map2 = tgis.RasterDataset("map2@PERMANENT")
  899. >>> check = map2.set_relative_time(7, 13, "days")
  900. >>> maps.append(map2)
  901. >>> grans = tgis.AbstractSpaceTimeDataset.resample_maplist_by_granularity(maps,0,16,2)
  902. >>> for map_list in grans:
  903. ... print(map_list[0].get_id(), map_list[0].get_temporal_extent_as_tuple())
  904. None (0, 2)
  905. map1@PERMANENT (2, 4)
  906. map1@PERMANENT (4, 6)
  907. map2@PERMANENT (6, 8)
  908. map2@PERMANENT (8, 10)
  909. map2@PERMANENT (10, 12)
  910. map2@PERMANENT (12, 14)
  911. None (14, 16)
  912. >>> maps = []
  913. >>> map1 = tgis.RasterDataset("map1@PERMANENT")
  914. >>> check = map1.set_relative_time(2, None, "days")
  915. >>> maps.append(map1)
  916. >>> map2 = tgis.RasterDataset("map2@PERMANENT")
  917. >>> check = map2.set_relative_time(7, None, "days")
  918. >>> maps.append(map2)
  919. >>> grans = tgis.AbstractSpaceTimeDataset.resample_maplist_by_granularity(maps,0,16,2)
  920. >>> for map_list in grans:
  921. ... print(map_list[0].get_id(), map_list[0].get_temporal_extent_as_tuple())
  922. None (0, 2)
  923. map1@PERMANENT (2, 4)
  924. None (4, 6)
  925. map2@PERMANENT (6, 8)
  926. None (8, 10)
  927. None (10, 12)
  928. None (12, 14)
  929. None (14, 16)
  930. >>> maps = []
  931. >>> map1 = tgis.RasterDataset("map1@PERMANENT")
  932. >>> check = map1.set_absolute_time(datetime(2000, 4,1), datetime(2000, 6, 1))
  933. >>> maps.append(map1)
  934. >>> map2 = tgis.RasterDataset("map2@PERMANENT")
  935. >>> check = map2.set_absolute_time(datetime(2000, 8,1), datetime(2000, 12, 1))
  936. >>> maps.append(map2)
  937. >>> grans = tgis.AbstractSpaceTimeDataset.resample_maplist_by_granularity(maps,datetime(2000,1,1),datetime(2001,4,1),"1 month")
  938. >>> for map_list in grans:
  939. ... print(map_list[0].get_id(), map_list[0].get_temporal_extent_as_tuple())
  940. None (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 2, 1, 0, 0))
  941. None (datetime.datetime(2000, 2, 1, 0, 0), datetime.datetime(2000, 3, 1, 0, 0))
  942. None (datetime.datetime(2000, 3, 1, 0, 0), datetime.datetime(2000, 4, 1, 0, 0))
  943. map1@PERMANENT (datetime.datetime(2000, 4, 1, 0, 0), datetime.datetime(2000, 5, 1, 0, 0))
  944. map1@PERMANENT (datetime.datetime(2000, 5, 1, 0, 0), datetime.datetime(2000, 6, 1, 0, 0))
  945. None (datetime.datetime(2000, 6, 1, 0, 0), datetime.datetime(2000, 7, 1, 0, 0))
  946. None (datetime.datetime(2000, 7, 1, 0, 0), datetime.datetime(2000, 8, 1, 0, 0))
  947. map2@PERMANENT (datetime.datetime(2000, 8, 1, 0, 0), datetime.datetime(2000, 9, 1, 0, 0))
  948. map2@PERMANENT (datetime.datetime(2000, 9, 1, 0, 0), datetime.datetime(2000, 10, 1, 0, 0))
  949. map2@PERMANENT (datetime.datetime(2000, 10, 1, 0, 0), datetime.datetime(2000, 11, 1, 0, 0))
  950. map2@PERMANENT (datetime.datetime(2000, 11, 1, 0, 0), datetime.datetime(2000, 12, 1, 0, 0))
  951. None (datetime.datetime(2000, 12, 1, 0, 0), datetime.datetime(2001, 1, 1, 0, 0))
  952. None (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2001, 2, 1, 0, 0))
  953. None (datetime.datetime(2001, 2, 1, 0, 0), datetime.datetime(2001, 3, 1, 0, 0))
  954. None (datetime.datetime(2001, 3, 1, 0, 0), datetime.datetime(2001, 4, 1, 0, 0))
  955. """
  956. if not maps:
  957. return None
  958. first = maps[0]
  959. # Build the gaplist
  960. gap_list = []
  961. while start < end:
  962. if first.is_time_absolute():
  963. next = increment_datetime_by_string(start, gran)
  964. else:
  965. next = start + gran
  966. map = first.get_new_instance(None)
  967. map.set_spatial_extent_from_values(0, 0, 0, 0, 0, 0)
  968. if first.is_time_absolute():
  969. map.set_absolute_time(start, next)
  970. else:
  971. map.set_relative_time(start, next,
  972. first.get_relative_time_unit())
  973. gap_list.append(copy.copy(map))
  974. start = next
  975. tb = SpatioTemporalTopologyBuilder()
  976. tb.build(gap_list, maps)
  977. relations_order = ["EQUAL", "DURING", "OVERLAPS", "OVERLAPPED",
  978. "CONTAINS"]
  979. gran_list = []
  980. for gap in gap_list:
  981. # If not temporal relations then gap
  982. if not gap.get_temporal_relations():
  983. gran_list.append([gap, ])
  984. else:
  985. relations = gap.get_temporal_relations()
  986. map_list = []
  987. for relation in relations_order:
  988. if relation in relations:
  989. map_list += relations[relation]
  990. break
  991. if map_list:
  992. new_maps = []
  993. for map in map_list:
  994. new_map = map.get_new_instance(map.get_id())
  995. new_map.set_temporal_extent(gap.get_temporal_extent())
  996. new_map.set_spatial_extent(map.get_spatial_extent())
  997. new_maps.append(new_map)
  998. gran_list.append(new_maps)
  999. else:
  1000. gran_list.append([gap, ])
  1001. if gran_list:
  1002. return gran_list
  1003. return None
  1004. def get_registered_maps_as_objects_with_gaps(self, where=None, dbif=None):
  1005. """Return all or a subset of the registered maps as
  1006. ordered (by start_time) object list with
  1007. "gap" map objects (id==None) for spatio-temporal topological
  1008. operations that require the spatio-temporal extent only.
  1009. Gaps between maps are identified as maps with id==None
  1010. The objects are initialized with their id's' and the spatio-temporal
  1011. extent (temporal type, start time, end time, west, east, south,
  1012. north, bottom and top).
  1013. In case more map information are needed, use the select()
  1014. method for each listed object.
  1015. :param where: The SQL where statement to select a
  1016. subset of the registered maps without "WHERE"
  1017. :param dbif: The database interface to be used
  1018. :return: ordered object list, in case nothing found None is returned
  1019. """
  1020. dbif, connected = init_dbif(dbif)
  1021. obj_list = []
  1022. maps = self.get_registered_maps_as_objects(where, "start_time", dbif)
  1023. if maps is not None and len(maps) > 0:
  1024. for i in range(len(maps)):
  1025. obj_list.append(maps[i])
  1026. # Detect and insert gaps
  1027. if i < len(maps) - 1:
  1028. relation = maps[i + 1].temporal_relation(maps[i])
  1029. if relation == "after":
  1030. start1, end1 = maps[i].get_temporal_extent_as_tuple()
  1031. start2, end2 = maps[i + 1].get_temporal_extent_as_tuple()
  1032. end = start2
  1033. if end1 is not None:
  1034. start = end1
  1035. else:
  1036. start = start1
  1037. map = self.get_new_map_instance(None)
  1038. if self.is_time_absolute():
  1039. map.set_absolute_time(start, end)
  1040. elif self.is_time_relative():
  1041. map.set_relative_time(start, end,
  1042. self.get_relative_time_unit())
  1043. map.set_spatial_extent_from_values(0, 0, 0, 0, 0, 0)
  1044. obj_list.append(copy.copy(map))
  1045. if connected:
  1046. dbif.close()
  1047. return obj_list
  1048. def get_registered_maps_as_objects_with_temporal_topology(self, where=None,
  1049. order="start_time",
  1050. dbif=None):
  1051. """Return all or a subset of the registered maps as ordered object
  1052. list with spatio-temporal topological relationship information.
  1053. The objects are initialized with their id's' and the spatio-temporal
  1054. extent (temporal type, start time, end time, west, east, south,
  1055. north, bottom and top).
  1056. In case more map information are needed, use the select()
  1057. method for each listed object.
  1058. :param where: The SQL where statement to select a subset of
  1059. the registered maps without "WHERE"
  1060. :param order: The SQL order statement to be used to order the
  1061. objects in the list without "ORDER BY"
  1062. :param dbif: The database interface to be used
  1063. :return: The ordered map object list,
  1064. In case nothing found None is returned
  1065. """
  1066. dbif, connected = init_dbif(dbif)
  1067. obj_list = self.get_registered_maps_as_objects(where, order, dbif)
  1068. tb = SpatioTemporalTopologyBuilder()
  1069. tb.build(obj_list)
  1070. if connected:
  1071. dbif.close()
  1072. return obj_list
  1073. def get_registered_maps_as_objects(self, where=None, order="start_time",
  1074. dbif=None):
  1075. """Return all or a subset of the registered maps as ordered object
  1076. list for spatio-temporal topological operations that require the
  1077. spatio-temporal extent only
  1078. The objects are initialized with their id's' and the spatio-temporal
  1079. extent (temporal type, start time, end time, west, east, south,
  1080. north, bottom and top).
  1081. In case more map information are needed, use the select()
  1082. method for each listed object.
  1083. :param where: The SQL where statement to select a subset of
  1084. the registered maps without "WHERE"
  1085. :param order: The SQL order statement to be used to order the
  1086. objects in the list without "ORDER BY"
  1087. :param dbif: The database interface to be used
  1088. :return: The ordered map object list,
  1089. In case nothing found None is returned
  1090. """
  1091. dbif, connected = init_dbif(dbif)
  1092. obj_list = []
  1093. # Older temporal databases have no bottom and top columns
  1094. # in their views so we need a work around to set the full
  1095. # spatial extent as well
  1096. rows = get_tgis_metadata(dbif)
  1097. db_version = 0
  1098. if rows:
  1099. for row in rows:
  1100. if row["key"] == "tgis_db_version":
  1101. db_version = int(float(row["value"]))
  1102. if db_version >= 1:
  1103. has_bt_columns = True
  1104. columns = "id,start_time,end_time, west,east,south,north,bottom,top"
  1105. else:
  1106. has_bt_columns = False
  1107. columns = "id,start_time,end_time, west,east,south,north"
  1108. rows = self.get_registered_maps(columns, where, order, dbif)
  1109. if rows is not None:
  1110. for row in rows:
  1111. map = self.get_new_map_instance(row["id"])
  1112. if self.is_time_absolute():
  1113. map.set_absolute_time(row["start_time"], row["end_time"])
  1114. elif self.is_time_relative():
  1115. map.set_relative_time(row["start_time"], row["end_time"],
  1116. self.get_relative_time_unit())
  1117. # The fast way
  1118. if has_bt_columns:
  1119. map.set_spatial_extent_from_values(west=row["west"],
  1120. east=row["east"],
  1121. south=row["south"],
  1122. top=row["top"],
  1123. north=row["north"],
  1124. bottom=row["bottom"])
  1125. # The slow work around
  1126. else:
  1127. map.spatial_extent.select(dbif)
  1128. obj_list.append(copy.copy(map))
  1129. if connected:
  1130. dbif.close()
  1131. return obj_list
  1132. def get_registered_maps(self, columns=None, where=None, order=None,
  1133. dbif=None):
  1134. """Return SQL rows of all registered maps.
  1135. In case columns are not specified, each row includes all columns
  1136. specified in the datatype specific view.
  1137. :param columns: Columns to be selected as SQL compliant string
  1138. :param where: The SQL where statement to select a subset
  1139. of the registered maps without "WHERE"
  1140. :param order: The SQL order statement to be used to order the
  1141. objects in the list without "ORDER BY"
  1142. :param dbif: The database interface to be used
  1143. :return: SQL rows of all registered maps,
  1144. In case nothing found None is returned
  1145. """
  1146. dbif, connected = init_dbif(dbif)
  1147. rows = None
  1148. if self.get_map_register() is not None:
  1149. # Use the correct temporal table
  1150. if self.get_temporal_type() == "absolute":
  1151. map_view = self.get_new_map_instance(
  1152. None).get_type() + "_view_abs_time"
  1153. else:
  1154. map_view = self.get_new_map_instance(
  1155. None).get_type() + "_view_rel_time"
  1156. if columns is not None and columns != "":
  1157. sql = "SELECT %s FROM %s WHERE %s.id IN (SELECT id FROM %s)" %\
  1158. (columns, map_view, map_view, self.get_map_register())
  1159. else:
  1160. sql = "SELECT * FROM %s WHERE %s.id IN (SELECT id FROM %s)" % \
  1161. (map_view, map_view, self.get_map_register())
  1162. if where is not None and where != "":
  1163. sql += " AND (%s)" % (where.split(";")[0])
  1164. if order is not None and order != "":
  1165. sql += " ORDER BY %s" % (order.split(";")[0])
  1166. try:
  1167. dbif.execute(sql, mapset=self.base.mapset)
  1168. rows = dbif.fetchall(mapset=self.base.mapset)
  1169. except:
  1170. if connected:
  1171. dbif.close()
  1172. self.msgr.error(_("Unable to get map ids from register table "
  1173. "<%s>") % (self.get_map_register()))
  1174. raise
  1175. if connected:
  1176. dbif.close()
  1177. return rows
  1178. @staticmethod
  1179. def shift_map_list(maps, gran):
  1180. """Temporally shift each map in the list with the provided granularity
  1181. This method does not perform any temporal database operations.
  1182. :param maps: A list of maps with initialized temporal extent
  1183. :param gran: The granularity to be used for shifting
  1184. :return: The modified map list, None if nothing to shift or wrong
  1185. granularity
  1186. .. code-block:: python
  1187. >>> import grass.temporal as tgis
  1188. >>> maps = []
  1189. >>> for i in range(5):
  1190. ... map = tgis.RasterDataset(None)
  1191. ... if i%2 == 0:
  1192. ... check = map.set_relative_time(i, i + 1, 'years')
  1193. ... else:
  1194. ... check = map.set_relative_time(i, None, 'years')
  1195. ... maps.append(map)
  1196. >>> for map in maps:
  1197. ... map.temporal_extent.print_info()
  1198. +-------------------- Relative time -----------------------------------------+
  1199. | Start time:................. 0
  1200. | End time:................... 1
  1201. | Relative time unit:......... years
  1202. +-------------------- Relative time -----------------------------------------+
  1203. | Start time:................. 1
  1204. | End time:................... None
  1205. | Relative time unit:......... years
  1206. +-------------------- Relative time -----------------------------------------+
  1207. | Start time:................. 2
  1208. | End time:................... 3
  1209. | Relative time unit:......... years
  1210. +-------------------- Relative time -----------------------------------------+
  1211. | Start time:................. 3
  1212. | End time:................... None
  1213. | Relative time unit:......... years
  1214. +-------------------- Relative time -----------------------------------------+
  1215. | Start time:................. 4
  1216. | End time:................... 5
  1217. | Relative time unit:......... years
  1218. >>> maps = tgis.AbstractSpaceTimeDataset.shift_map_list(maps, 5)
  1219. >>> for map in maps:
  1220. ... map.temporal_extent.print_info()
  1221. +-------------------- Relative time -----------------------------------------+
  1222. | Start time:................. 5
  1223. | End time:................... 6
  1224. | Relative time unit:......... years
  1225. +-------------------- Relative time -----------------------------------------+
  1226. | Start time:................. 6
  1227. | End time:................... None
  1228. | Relative time unit:......... years
  1229. +-------------------- Relative time -----------------------------------------+
  1230. | Start time:................. 7
  1231. | End time:................... 8
  1232. | Relative time unit:......... years
  1233. +-------------------- Relative time -----------------------------------------+
  1234. | Start time:................. 8
  1235. | End time:................... None
  1236. | Relative time unit:......... years
  1237. +-------------------- Relative time -----------------------------------------+
  1238. | Start time:................. 9
  1239. | End time:................... 10
  1240. | Relative time unit:......... years
  1241. """
  1242. if maps is None:
  1243. return None
  1244. if not check_granularity_string(gran, maps[-1].get_temporal_type()):
  1245. return None
  1246. for map in maps:
  1247. start, end = map.get_temporal_extent_as_tuple()
  1248. if map.is_time_absolute():
  1249. start = increment_datetime_by_string(start, gran)
  1250. if end is not None:
  1251. end = increment_datetime_by_string(end, gran)
  1252. map.set_absolute_time(start, end)
  1253. elif map.is_time_relative():
  1254. start = start + int(gran)
  1255. if end is not None:
  1256. end = end + int(gran)
  1257. map.set_relative_time(start, end, map.get_relative_time_unit())
  1258. return maps
  1259. def shift(self, gran, dbif=None):
  1260. """Temporally shift each registered map with the provided granularity
  1261. :param gran: The granularity to be used for shifting
  1262. :param dbif: The database interface to be used
  1263. :return: True something to shift, False if nothing to shift or wrong
  1264. granularity
  1265. """
  1266. if get_enable_mapset_check() is True and \
  1267. self.get_mapset() != get_current_mapset():
  1268. self.msgr.fatal(_("Unable to shift dataset <%(ds)s> of type "
  1269. "%(type)s in the temporal database. The mapset "
  1270. "of the dataset does not match the current "
  1271. "mapset") % ({"ds": self.get_id()},
  1272. {"type": self.get_type()}))
  1273. if not check_granularity_string(gran, self.get_temporal_type()):
  1274. self.msgr.error(_("Wrong granularity format: %s" % (gran)))
  1275. return False
  1276. dbif, connected = init_dbif(dbif)
  1277. maps = self.get_registered_maps_as_objects(dbif=dbif)
  1278. if maps is None:
  1279. return False
  1280. date_list = []
  1281. # We need to make a dry run to avoid a break
  1282. # in the middle of the update process when the increment
  1283. # results in wrong number of days in a month
  1284. for map in maps:
  1285. start, end = map.get_temporal_extent_as_tuple()
  1286. if self.is_time_absolute():
  1287. start = increment_datetime_by_string(start, gran)
  1288. if end is not None:
  1289. end = increment_datetime_by_string(end, gran)
  1290. elif self.is_time_relative():
  1291. start = start + int(gran)
  1292. if end is not None:
  1293. end = end + int(gran)
  1294. date_list.append((start, end))
  1295. self. _update_map_timestamps(maps, date_list, dbif)
  1296. if connected:
  1297. dbif.close()
  1298. @staticmethod
  1299. def snap_map_list(maps):
  1300. """For each map in the list snap the end time to the start time of its
  1301. temporal nearest neighbor in the future.
  1302. Maps with equal time stamps are not snapped.
  1303. The granularity of the map list will be used to create the end time
  1304. of the last map in case it has a time instance as timestamp.
  1305. This method does not perform any temporal database operations.
  1306. :param maps: A list of maps with initialized temporal extent
  1307. :return: The modified map list, None nothing to shift or wrong
  1308. granularity
  1309. Usage:
  1310. .. code-block:: python
  1311. >>> import grass.temporal as tgis
  1312. >>> maps = []
  1313. >>> for i in range(5):
  1314. ... map = tgis.RasterDataset(None)
  1315. ... if i%2 == 0:
  1316. ... check = map.set_relative_time(i, i + 1, 'years')
  1317. ... else:
  1318. ... check = map.set_relative_time(i, None, 'years')
  1319. ... maps.append(map)
  1320. >>> for map in maps:
  1321. ... map.temporal_extent.print_info()
  1322. +-------------------- Relative time -----------------------------------------+
  1323. | Start time:................. 0
  1324. | End time:................... 1
  1325. | Relative time unit:......... years
  1326. +-------------------- Relative time -----------------------------------------+
  1327. | Start time:................. 1
  1328. | End time:................... None
  1329. | Relative time unit:......... years
  1330. +-------------------- Relative time -----------------------------------------+
  1331. | Start time:................. 2
  1332. | End time:................... 3
  1333. | Relative time unit:......... years
  1334. +-------------------- Relative time -----------------------------------------+
  1335. | Start time:................. 3
  1336. | End time:................... None
  1337. | Relative time unit:......... years
  1338. +-------------------- Relative time -----------------------------------------+
  1339. | Start time:................. 4
  1340. | End time:................... 5
  1341. | Relative time unit:......... years
  1342. >>> maps = tgis.AbstractSpaceTimeDataset.snap_map_list(maps)
  1343. >>> for map in maps:
  1344. ... map.temporal_extent.print_info()
  1345. +-------------------- Relative time -----------------------------------------+
  1346. | Start time:................. 0
  1347. | End time:................... 1
  1348. | Relative time unit:......... years
  1349. +-------------------- Relative time -----------------------------------------+
  1350. | Start time:................. 1
  1351. | End time:................... 2
  1352. | Relative time unit:......... years
  1353. +-------------------- Relative time -----------------------------------------+
  1354. | Start time:................. 2
  1355. | End time:................... 3
  1356. | Relative time unit:......... years
  1357. +-------------------- Relative time -----------------------------------------+
  1358. | Start time:................. 3
  1359. | End time:................... 4
  1360. | Relative time unit:......... years
  1361. +-------------------- Relative time -----------------------------------------+
  1362. | Start time:................. 4
  1363. | End time:................... 5
  1364. | Relative time unit:......... years
  1365. """
  1366. if maps is None or len(maps) == 0:
  1367. return None
  1368. # We need to sort the maps temporally by start time
  1369. maps = sorted(maps, key=AbstractDatasetComparisonKeyStartTime)
  1370. for i in range(len(maps) - 1):
  1371. start, end = maps[i].get_temporal_extent_as_tuple()
  1372. start_next, end = maps[i + 1].get_temporal_extent_as_tuple()
  1373. # Maps with equal time stamps can not be snapped
  1374. if start != start_next:
  1375. if maps[i].is_time_absolute():
  1376. maps[i].set_absolute_time(start, start_next)
  1377. elif maps[i].is_time_relative():
  1378. maps[i].set_relative_time(start, start_next,
  1379. maps[i].get_relative_time_unit())
  1380. else:
  1381. if maps[i].is_time_absolute():
  1382. maps[i].set_absolute_time(start, end)
  1383. elif maps[i].is_time_relative():
  1384. maps[i].set_relative_time(start, end,
  1385. maps[i].get_relative_time_unit())
  1386. # Last map
  1387. start, end = maps[-1].get_temporal_extent_as_tuple()
  1388. # We increment the start time with the dataset
  1389. # granularity if the end time is None
  1390. if end is None:
  1391. if maps[-1].is_time_absolute():
  1392. gran = compute_absolute_time_granularity(maps)
  1393. end = increment_datetime_by_string(start, gran)
  1394. maps[-1].set_absolute_time(start, end)
  1395. elif maps[-1].is_time_relative():
  1396. gran = compute_relative_time_granularity(maps)
  1397. end = start + gran
  1398. maps[-1].set_relative_time(start, end,
  1399. maps[-1].get_relative_time_unit())
  1400. return maps
  1401. def snap(self, dbif=None):
  1402. """For each registered map snap the end time to the start time of
  1403. its temporal nearest neighbor in the future
  1404. Maps with equal time stamps are not snapped
  1405. :param dbif: The database interface to be used
  1406. """
  1407. if get_enable_mapset_check() is True and \
  1408. self.get_mapset() != get_current_mapset():
  1409. self.msgr.fatal(_("Unable to snap dataset <%(ds)s> of type "
  1410. "%(type)s in the temporal database. The mapset "
  1411. "of the dataset does not match the current "
  1412. "mapset") % ({"ds": self.get_id()},
  1413. {"type": self.get_type()}))
  1414. dbif, connected = init_dbif(dbif)
  1415. maps = self.get_registered_maps_as_objects(dbif=dbif)
  1416. if maps is None:
  1417. return
  1418. date_list = []
  1419. for i in range(len(maps) - 1):
  1420. start, end = maps[i].get_temporal_extent_as_tuple()
  1421. start_next, end = maps[i + 1].get_temporal_extent_as_tuple()
  1422. # Maps with equal time stamps can not be snapped
  1423. if start != start_next:
  1424. date_list.append((start, start_next))
  1425. else:
  1426. # Keep the original time stamps
  1427. date_list.append((start, end))
  1428. # Last map
  1429. start, end = maps[-1].get_temporal_extent_as_tuple()
  1430. # We increment the start time with the dataset
  1431. # granularity if the end time is None
  1432. if end is None:
  1433. if self.is_time_absolute():
  1434. end = increment_datetime_by_string(start,
  1435. self.get_granularity())
  1436. elif self.is_time_relative():
  1437. end = start + self.get_granularity()
  1438. date_list.append((start, end))
  1439. self._update_map_timestamps(maps, date_list, dbif)
  1440. if connected:
  1441. dbif.close()
  1442. def _update_map_timestamps(self, maps, date_list, dbif):
  1443. """Update the timestamps of maps with the start and end time
  1444. stored in the date_list.
  1445. The number of dates in the list must be equal to the number
  1446. of maps.
  1447. :param maps: A list of map objects
  1448. :param date_list: A list with date tuples (start_time, end_time)
  1449. :param dbif: The database interface to be used
  1450. """
  1451. datatsets_to_modify = {}
  1452. # Now update the maps
  1453. count = 0
  1454. for map in maps:
  1455. start = date_list[count][0]
  1456. end = date_list[count][1]
  1457. map.select(dbif)
  1458. count += 1
  1459. if self.is_time_absolute():
  1460. map.update_absolute_time(start_time=start, end_time=end,
  1461. dbif=dbif)
  1462. elif self.is_time_relative():
  1463. map.update_relative_time(start_time=start, end_time=end,
  1464. unit=self.get_relative_time_unit(),
  1465. dbif=dbif)
  1466. # Save the datasets that must be updated
  1467. datasets = map.get_registered_stds(dbif)
  1468. if datasets:
  1469. for dataset in datasets:
  1470. datatsets_to_modify[dataset] = dataset
  1471. self.update_from_registered_maps(dbif)
  1472. # Update affected datasets
  1473. if datatsets_to_modify:
  1474. for dataset in datatsets_to_modify:
  1475. if dataset != self.get_id():
  1476. ds = self.get_new_instance(ident=dataset)
  1477. ds.select(dbif)
  1478. ds.update_from_registered_maps(dbif)
  1479. def rename(self, ident, dbif=None):
  1480. """Rename the space time dataset
  1481. This method renames the space time dataset, the map register table
  1482. and updates the entries in registered maps stds register.
  1483. Renaming does not work with Postgresql yet.
  1484. :param ident: The new identifier "name@mapset"
  1485. :param dbif: The database interface to be used
  1486. """
  1487. if get_enable_mapset_check() is True and self.get_mapset() != get_current_mapset():
  1488. self.msgr.fatal(_("Unable to rename dataset <%(ds)s> of type "
  1489. "%(type)s in the temporal database. The mapset "
  1490. "of the dataset does not match the current "
  1491. "mapset") % ({"ds": self.get_id()},
  1492. {"type": self.get_type()}))
  1493. dbif, connected = init_dbif(dbif)
  1494. if dbif.get_dbmi().__name__ != "sqlite3":
  1495. self.msgr.fatal(_("Renaming of space time datasets is not "
  1496. "supported for PostgreSQL."))
  1497. # SELECT all needed information from the database
  1498. self.select(dbif)
  1499. # We need to select the registered maps here
  1500. maps = self.get_registered_maps_as_objects(None, "start_time", dbif)
  1501. # Safe old identifier
  1502. old_ident = self.get_id()
  1503. # We need to rename the old table
  1504. old_map_register_table = self.get_map_register()
  1505. # Set new identifier
  1506. self.set_id(ident)
  1507. # Create map register table name from new identifier
  1508. new_map_register_table = self.create_map_register_name()
  1509. # Set new map register table name
  1510. self.set_map_register(new_map_register_table)
  1511. # Get the update statement, we update the table entry of the old
  1512. # identifier
  1513. statement = self.update(dbif, execute=False, ident=old_ident)
  1514. # We need to rename the raster register table
  1515. statement += "ALTER TABLE %s RENAME TO \"%s\";\n" % \
  1516. (old_map_register_table, new_map_register_table)
  1517. # We need to take care of the stds index in the sqlite3 database
  1518. if dbif.get_dbmi().__name__ == "sqlite3":
  1519. statement += "DROP INDEX %s_index;\n" % (old_map_register_table)
  1520. statement += "CREATE INDEX %s_index ON %s (id);" % \
  1521. (new_map_register_table, new_map_register_table)
  1522. # We need to rename the space time dataset in the maps register table
  1523. if maps:
  1524. for map in maps:
  1525. map.remove_stds_from_register(stds_id=old_ident, dbif=dbif)
  1526. map.add_stds_to_register(stds_id=ident, dbif=dbif)
  1527. # Execute the accumulated statements
  1528. dbif.execute_transaction(statement)
  1529. if connected:
  1530. dbif.close()
  1531. def delete(self, dbif=None, execute=True):
  1532. """Delete a space time dataset from the temporal database
  1533. This method removes the space time dataset from the temporal
  1534. database and drops its map register table
  1535. :param dbif: The database interface to be used
  1536. :param execute: If True the SQL DELETE and DROP table
  1537. statements will be executed.
  1538. If False the prepared SQL statements are returned
  1539. and must be executed by the caller.
  1540. :return: The SQL statements if execute == False, else an empty
  1541. string
  1542. """
  1543. # First we need to check if maps are registered in this dataset and
  1544. # unregister them
  1545. self.msgr.verbose(_("Delete space time %s dataset <%s> from temporal "
  1546. "database") % (self.get_new_map_instance(ident=None).get_type(),
  1547. self.get_id()))
  1548. if get_enable_mapset_check() is True and \
  1549. self.get_mapset() != get_current_mapset():
  1550. self.msgr.fatal(_("Unable to delete dataset <%(ds)s> of type "
  1551. "%(type)s from the temporal database. The mapset"
  1552. " of the dataset does not match the current "
  1553. "mapset") % {"ds": self.get_id(),
  1554. "type": self.get_type()})
  1555. statement = ""
  1556. dbif, connected = init_dbif(dbif)
  1557. # SELECT all needed information from the database
  1558. self.metadata.select(dbif)
  1559. if self.get_map_register() is not None:
  1560. self.msgr.debug(1, _("Drop map register table: %s") % (
  1561. self.get_map_register()))
  1562. rows = self.get_registered_maps("id", None, None, dbif)
  1563. # Unregister each registered map in the table
  1564. if rows is not None:
  1565. for row in rows:
  1566. # Unregister map
  1567. map = self.get_new_map_instance(row["id"])
  1568. statement += self.unregister_map(
  1569. map=map, dbif=dbif, execute=False)
  1570. # Safe the DROP table statement
  1571. statement += "DROP TABLE IF EXISTS " + self.get_map_register() + ";\n"
  1572. # Remove the primary key, the foreign keys will be removed by trigger
  1573. statement += self.base.get_delete_statement()
  1574. if execute:
  1575. dbif.execute_transaction(statement)
  1576. self.reset(None)
  1577. if connected:
  1578. dbif.close()
  1579. if execute:
  1580. return ""
  1581. return statement
  1582. def is_map_registered(self, map_id, dbif=None):
  1583. """Check if a map is registered in the space time dataset
  1584. :param map_id: The map id
  1585. :param dbif: The database interface to be used
  1586. :return: True if success, False otherwise
  1587. """
  1588. stds_register_table = self.get_map_register()
  1589. dbif, connected = init_dbif(dbif)
  1590. is_registered = False
  1591. # Check if map is already registered
  1592. if stds_register_table is not None:
  1593. if dbif.get_dbmi().paramstyle == "qmark":
  1594. sql = "SELECT id FROM " + \
  1595. stds_register_table + " WHERE id = (?)"
  1596. else:
  1597. sql = "SELECT id FROM " + \
  1598. stds_register_table + " WHERE id = (%s)"
  1599. try:
  1600. dbif.execute(sql, (map_id,), mapset=self.base.mapset)
  1601. row = dbif.fetchone(mapset=self.base.mapset)
  1602. except:
  1603. self.msgr.warning(_("Error in register table request"))
  1604. raise
  1605. if row is not None and row[0] == map_id:
  1606. is_registered = True
  1607. if connected is True:
  1608. dbif.close()
  1609. return is_registered
  1610. def register_map(self, map, dbif=None):
  1611. """Register a map in the space time dataset.
  1612. This method takes care of the registration of a map
  1613. in a space time dataset.
  1614. In case the map is already registered this function
  1615. will break with a warning and return False.
  1616. This method raises a FatalError exception in case of a fatal error
  1617. :param map: The AbstractMapDataset object that should be registered
  1618. :param dbif: The database interface to be used
  1619. :return: True if success, False otherwise
  1620. """
  1621. if get_enable_mapset_check() is True and \
  1622. self.get_mapset() != get_current_mapset():
  1623. self.msgr.fatal(_("Unable to register map in dataset <%(ds)s> of "
  1624. "type %(type)s. The mapset of the dataset does "
  1625. "not match the current mapset") %
  1626. {"ds": self.get_id(), "type": self.get_type()})
  1627. dbif, connected = init_dbif(dbif)
  1628. if map.is_in_db(dbif) is False:
  1629. dbif.close()
  1630. self.msgr.fatal(_("Only a map that was inserted in the temporal "
  1631. "database can be registered in a space time "
  1632. "dataset"))
  1633. if map.get_layer():
  1634. self.msgr.debug(1, "Register %s map <%s> with layer %s in space "
  1635. "time %s dataset <%s>" % (map.get_type(),
  1636. map.get_map_id(),
  1637. map.get_layer(),
  1638. map.get_type(),
  1639. self.get_id()))
  1640. else:
  1641. self.msgr.debug(1, "Register %s map <%s> in space time %s "
  1642. "dataset <%s>" % (map.get_type(),
  1643. map.get_map_id(),
  1644. map.get_type(),
  1645. self.get_id()))
  1646. # First select all data from the database
  1647. map.select(dbif)
  1648. if not map.check_for_correct_time():
  1649. if map.get_layer():
  1650. self.msgr.fatal(_("Map <%(id)s> with layer %(l)s has invalid "
  1651. "time") % {'id': map.get_map_id(),
  1652. 'l': map.get_layer()})
  1653. else:
  1654. self.msgr.fatal(_("Map <%s> has invalid time") % (map.get_map_id()))
  1655. # Get basic info
  1656. map_id = map.base.get_id()
  1657. map_mapset = map.base.get_mapset()
  1658. map_rel_time_unit = map.get_relative_time_unit()
  1659. map_ttype = map.get_temporal_type()
  1660. stds_mapset = self.base.get_mapset()
  1661. stds_register_table = self.get_map_register()
  1662. stds_ttype = self.get_temporal_type()
  1663. # The gathered SQL statemets are stroed here
  1664. statement = ""
  1665. # Check temporal types
  1666. if stds_ttype != map_ttype:
  1667. if map.get_layer():
  1668. self.msgr.fatal(_("Temporal type of space time dataset "
  1669. "<%(id)s> and map <%(map)s> with layer %(l)s"
  1670. " are different") % {'id': self.get_id(),
  1671. 'map': map.get_map_id(),
  1672. 'l': map.get_layer()})
  1673. else:
  1674. self.msgr.fatal(_("Temporal type of space time dataset "
  1675. "<%(id)s> and map <%(map)s> are different")
  1676. % {'id': self.get_id(),
  1677. 'map': map.get_map_id()})
  1678. # In case no map has been registered yet, set the
  1679. # relative time unit from the first map
  1680. if (self.metadata.get_number_of_maps() is None or
  1681. self.metadata.get_number_of_maps() == 0) and \
  1682. self.map_counter == 0 and self.is_time_relative():
  1683. self.set_relative_time_unit(map_rel_time_unit)
  1684. statement += self.relative_time.get_update_all_statement_mogrified(
  1685. dbif)
  1686. self.msgr.debug(1, _("Set temporal unit for space time %s dataset "
  1687. "<%s> to %s") % (map.get_type(),
  1688. self.get_id(),
  1689. map_rel_time_unit))
  1690. stds_rel_time_unit = self.get_relative_time_unit()
  1691. # Check the relative time unit
  1692. if self.is_time_relative() and (stds_rel_time_unit != map_rel_time_unit):
  1693. if map.get_layer():
  1694. self.msgr.fatal(_("Relative time units of space time dataset "
  1695. "<%(id)s> and map <%(map)s> with layer %(l)s"
  1696. " are different") % {'id': self.get_id(),
  1697. 'map': map.get_map_id(),
  1698. 'l': map.get_layer()})
  1699. else:
  1700. self.msgr.fatal(_("Relative time units of space time dataset "
  1701. "<%(id)s> and map <%(map)s> are different") %
  1702. {'id': self.get_id(), 'map': map.get_map_id()})
  1703. if get_enable_mapset_check() is True and stds_mapset != map_mapset:
  1704. dbif.close()
  1705. self.msgr.fatal(_("Only maps from the same mapset can be registered"))
  1706. # Check if map is already registered
  1707. if self.is_map_registered(map_id, dbif=dbif):
  1708. if map.get_layer() is not None:
  1709. self.msgr.warning(_("Map <%(map)s> with layer %(l)s is already"
  1710. " registered.") % {'map': map.get_map_id(),
  1711. 'l': map.get_layer()})
  1712. else:
  1713. self.msgr.warning(_("Map <%s> is already registered.") %
  1714. (map.get_map_id()))
  1715. return False
  1716. # Register the stds in the map stds register table column
  1717. statement += map.add_stds_to_register(stds_id=self.base.get_id(),
  1718. dbif=dbif, execute=False)
  1719. # Now put the raster name in the stds map register table
  1720. if dbif.get_dbmi().paramstyle == "qmark":
  1721. sql = "INSERT INTO " + stds_register_table + \
  1722. " (id) " + "VALUES (?);\n"
  1723. else:
  1724. sql = "INSERT INTO " + stds_register_table + \
  1725. " (id) " + "VALUES (%s);\n"
  1726. statement += dbif.mogrify_sql_statement((sql, (map_id,)))
  1727. # Now execute the insert transaction
  1728. dbif.execute_transaction(statement)
  1729. if connected:
  1730. dbif.close()
  1731. # increase the counter
  1732. self.map_counter += 1
  1733. return True
  1734. def unregister_map(self, map, dbif=None, execute=True):
  1735. """Unregister a map from the space time dataset.
  1736. This method takes care of the un-registration of a map
  1737. from a space time dataset.
  1738. :param map: The map object to unregister
  1739. :param dbif: The database interface to be used
  1740. :param execute: If True the SQL DELETE and DROP table
  1741. statements will be executed.
  1742. If False the prepared SQL statements are
  1743. returned and must be executed by the caller.
  1744. :return: The SQL statements if execute == False, else an empty
  1745. string, None in case of a failure
  1746. """
  1747. if get_enable_mapset_check() is True and \
  1748. self.get_mapset() != get_current_mapset():
  1749. self.msgr.fatal(_("Unable to unregister map from dataset <%(ds)s>"
  1750. " of type %(type)s in the temporal database."
  1751. " The mapset of the dataset does not match the"
  1752. " current mapset") % {"ds": self.get_id(),
  1753. "type": self.get_type()})
  1754. statement = ""
  1755. dbif, connected = init_dbif(dbif)
  1756. # Check if the map is registered in the space time raster dataset
  1757. if self.is_map_registered(map.get_id(), dbif) is False:
  1758. if map.get_layer() is not None:
  1759. self.msgr.warning(_("Map <%(map)s> with layer %(l)s is not "
  1760. "registered in space time dataset "
  1761. "<%(base)s>") % {'map': map.get_map_id(),
  1762. 'l': map.get_layer(),
  1763. 'base': self.base.get_id()})
  1764. else:
  1765. self.msgr.warning(_("Map <%(map)s> is not registered in space "
  1766. "time dataset <%(base)s>") %
  1767. {'map': map.get_map_id(),
  1768. 'base': self.base.get_id()})
  1769. if connected is True:
  1770. dbif.close()
  1771. return ""
  1772. # Remove the space time dataset from the dataset register
  1773. # We need to execute the statement here, otherwise the space time
  1774. # dataset will not be removed correctly
  1775. map.remove_stds_from_register(self.base.get_id(),
  1776. dbif=dbif, execute=True)
  1777. # Remove the map from the space time dataset register
  1778. stds_register_table = self.get_map_register()
  1779. if stds_register_table is not None:
  1780. if dbif.get_dbmi().paramstyle == "qmark":
  1781. sql = "DELETE FROM " + stds_register_table + " WHERE id = ?;\n"
  1782. else:
  1783. sql = "DELETE FROM " + \
  1784. stds_register_table + " WHERE id = %s;\n"
  1785. statement += dbif.mogrify_sql_statement((sql, (map.get_id(), )))
  1786. if execute:
  1787. dbif.execute_transaction(statement)
  1788. statement = ""
  1789. if connected:
  1790. dbif.close()
  1791. # decrease the counter
  1792. self.map_counter -= 1
  1793. return statement
  1794. def update_from_registered_maps(self, dbif=None):
  1795. """This methods updates the modification time, the spatial and
  1796. temporal extent as well as type specific metadata. It should always
  1797. been called after maps are registered or unregistered/deleted from
  1798. the space time dataset.
  1799. The update of the temporal extent checks if the end time is set
  1800. correctly.
  1801. In case the registered maps have no valid end time (None) the
  1802. maximum start time
  1803. will be used. If the end time is earlier than the maximum start
  1804. time, it will be replaced by the maximum start time.
  1805. :param dbif: The database interface to be used
  1806. """
  1807. if get_enable_mapset_check() is True and \
  1808. self.get_mapset() != get_current_mapset():
  1809. self.msgr.fatal(_("Unable to update dataset <%(ds)s> of type "
  1810. "%(type)s in the temporal database. The mapset"
  1811. " of the dataset does not match the current "
  1812. "mapset") % {"ds": self.get_id(),
  1813. "type": self.get_type()})
  1814. self.msgr.verbose(_("Update metadata, spatial and temporal extent from"
  1815. " all registered maps of <%s>") % (self.get_id()))
  1816. # Nothing to do if the map register is not present
  1817. if not self.get_map_register():
  1818. return
  1819. dbif, connected = init_dbif(dbif)
  1820. map_time = None
  1821. use_start_time = False
  1822. # Get basic info
  1823. stds_name = self.base.get_name()
  1824. stds_mapset = self.base.get_mapset()
  1825. sql_path = get_sql_template_path()
  1826. stds_register_table = self.get_map_register()
  1827. # We create a transaction
  1828. sql_script = ""
  1829. # Update the spatial and temporal extent from registered maps
  1830. # Read the SQL template
  1831. sql = open(os.path.join(sql_path,
  1832. "update_stds_spatial_temporal_extent_template.sql"),
  1833. 'r').read()
  1834. sql = sql.replace(
  1835. "GRASS_MAP", self.get_new_map_instance(None).get_type())
  1836. sql = sql.replace("SPACETIME_REGISTER_TABLE", stds_register_table)
  1837. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  1838. sql = sql.replace("STDS", self.get_type())
  1839. sql_script += sql
  1840. sql_script += "\n"
  1841. # Update type specific metadata
  1842. sql = open(os.path.join(sql_path, "update_" +
  1843. self.get_type() +
  1844. "_metadata_template.sql"),
  1845. 'r').read()
  1846. sql = sql.replace("SPACETIME_REGISTER_TABLE", stds_register_table)
  1847. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  1848. sql_script += sql
  1849. sql_script += "\n"
  1850. dbif.execute_transaction(sql_script)
  1851. # Read and validate the selected end time
  1852. self.select(dbif)
  1853. if self.is_time_absolute():
  1854. start_time, end_time = self.get_absolute_time()
  1855. else:
  1856. start_time, end_time, unit = self.get_relative_time()
  1857. # In case no end time is set, use the maximum start time of
  1858. # all registered maps as end time
  1859. if end_time is None:
  1860. use_start_time = True
  1861. else:
  1862. # Check if the end time is smaller than the maximum start time
  1863. if self.is_time_absolute():
  1864. sql = """SELECT max(start_time) FROM GRASS_MAP_absolute_time
  1865. WHERE GRASS_MAP_absolute_time.id IN
  1866. (SELECT id FROM SPACETIME_REGISTER_TABLE);"""
  1867. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(
  1868. None).get_type())
  1869. sql = sql.replace("SPACETIME_REGISTER_TABLE",
  1870. stds_register_table)
  1871. else:
  1872. sql = """SELECT max(start_time) FROM GRASS_MAP_relative_time
  1873. WHERE GRASS_MAP_relative_time.id IN
  1874. (SELECT id FROM SPACETIME_REGISTER_TABLE);"""
  1875. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(
  1876. None).get_type())
  1877. sql = sql.replace("SPACETIME_REGISTER_TABLE",
  1878. stds_register_table)
  1879. dbif.execute(sql, mapset=self.base.mapset)
  1880. row = dbif.fetchone(mapset=self.base.mapset)
  1881. if row is not None:
  1882. # This seems to be a bug in sqlite3 Python driver
  1883. if dbif.get_dbmi().__name__ == "sqlite3":
  1884. tstring = row[0]
  1885. # Convert the unicode string into the datetime format
  1886. if self.is_time_absolute():
  1887. if tstring.find(":") > 0:
  1888. time_format = "%Y-%m-%d %H:%M:%S"
  1889. else:
  1890. time_format = "%Y-%m-%d"
  1891. max_start_time = datetime.strptime(
  1892. tstring, time_format)
  1893. else:
  1894. max_start_time = row[0]
  1895. else:
  1896. max_start_time = row[0]
  1897. if end_time < max_start_time:
  1898. use_start_time = True
  1899. # Set the maximum start time as end time
  1900. if use_start_time:
  1901. if self.is_time_absolute():
  1902. sql = """UPDATE STDS_absolute_time SET end_time =
  1903. (SELECT max(start_time) FROM GRASS_MAP_absolute_time WHERE
  1904. GRASS_MAP_absolute_time.id IN
  1905. (SELECT id FROM SPACETIME_REGISTER_TABLE)
  1906. ) WHERE id = 'SPACETIME_ID';"""
  1907. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(
  1908. None).get_type())
  1909. sql = sql.replace("SPACETIME_REGISTER_TABLE",
  1910. stds_register_table)
  1911. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  1912. sql = sql.replace("STDS", self.get_type())
  1913. elif self.is_time_relative():
  1914. sql = """UPDATE STDS_relative_time SET end_time =
  1915. (SELECT max(start_time) FROM GRASS_MAP_relative_time WHERE
  1916. GRASS_MAP_relative_time.id IN
  1917. (SELECT id FROM SPACETIME_REGISTER_TABLE)
  1918. ) WHERE id = 'SPACETIME_ID';"""
  1919. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(
  1920. None).get_type())
  1921. sql = sql.replace("SPACETIME_REGISTER_TABLE",
  1922. stds_register_table)
  1923. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  1924. sql = sql.replace("STDS", self.get_type())
  1925. dbif.execute_transaction(sql)
  1926. # Count the temporal map types
  1927. maps = self.get_registered_maps_as_objects(dbif=dbif)
  1928. tlist = self.count_temporal_types(maps)
  1929. if tlist["interval"] > 0 and tlist["point"] == 0 and \
  1930. tlist["invalid"] == 0:
  1931. map_time = "interval"
  1932. elif tlist["interval"] == 0 and tlist["point"] > 0 and \
  1933. tlist["invalid"] == 0:
  1934. map_time = "point"
  1935. elif tlist["interval"] > 0 and tlist["point"] > 0 and \
  1936. tlist["invalid"] == 0:
  1937. map_time = "mixed"
  1938. else:
  1939. map_time = "invalid"
  1940. # Compute the granularity
  1941. if map_time != "invalid":
  1942. # Smallest supported temporal resolution
  1943. if self.is_time_absolute():
  1944. gran = compute_absolute_time_granularity(maps)
  1945. elif self.is_time_relative():
  1946. gran = compute_relative_time_granularity(maps)
  1947. else:
  1948. gran = None
  1949. # Set the map time type and update the time objects
  1950. self.temporal_extent.select(dbif)
  1951. self.metadata.select(dbif)
  1952. if self.metadata.get_number_of_maps() > 0:
  1953. self.temporal_extent.set_map_time(map_time)
  1954. self.temporal_extent.set_granularity(gran)
  1955. else:
  1956. self.temporal_extent.set_map_time(None)
  1957. self.temporal_extent.set_granularity(None)
  1958. self.temporal_extent.update_all(dbif)
  1959. # Set the modification time
  1960. self.base.set_mtime(datetime.now())
  1961. self.base.update(dbif)
  1962. if connected:
  1963. dbif.close()
  1964. ###############################################################################
  1965. if __name__ == "__main__":
  1966. import doctest
  1967. doctest.testmod()