1"""
2Map layer and space time dataset classes
3
4(C) 2012-2013 by the GRASS Development Team
5This program is free software under the GNU General Public
6License (>=v2). Read the file COPYING that comes with GRASS
7for details.
8
9:authors: Soeren Gebbert
10"""
11import getpass
12from datetime import datetime
13from .core import get_current_mapset
14from .abstract_map_dataset import AbstractMapDataset
15from .abstract_space_time_dataset import AbstractSpaceTimeDataset
16from .base import Raster3DBase, RasterBase, VectorBase, STR3DSBase, STVDSBase, STRDSBase,\
17    VectorSTDSRegister, Raster3DSTDSRegister, RasterSTDSRegister
18from .metadata import Raster3DMetadata, RasterMetadata, VectorMetadata, STRDSMetadata,\
19    STR3DSMetadata, STVDSMetadata
20from .spatial_extent import RasterSpatialExtent, Raster3DSpatialExtent, VectorSpatialExtent,\
21    STRDSSpatialExtent, STR3DSSpatialExtent, STVDSSpatialExtent
22from .temporal_extent import RasterAbsoluteTime, RasterRelativeTime, Raster3DAbsoluteTime, \
23    Raster3DRelativeTime, VectorAbsoluteTime, VectorRelativeTime, STRDSAbsoluteTime,\
24    STRDSRelativeTime, STR3DSAbsoluteTime, STR3DSRelativeTime, STVDSAbsoluteTime, STVDSRelativeTime
25import grass.script.array as garray
26from .core import init
27from datetime import datetime
28
29###############################################################################
30
31
32class RasterDataset(AbstractMapDataset):
33    """Raster dataset class
34
35        This class provides functions to select, update, insert or delete raster
36        map information and valid time stamps into the SQL temporal database.
37
38        Usage:
39
40        .. code-block:: python
41
42            >>> import grass.script as grass
43            >>> import grass.temporal as tgis
44            >>> init()
45            >>> grass.use_temp_region()
46            >>> grass.run_command("g.region", n=80.0, s=0.0, e=120.0, w=0.0,
47            ... t=1.0, b=0.0, res=10.0)
48            0
49            >>> grass.run_command("r.mapcalc", overwrite=True, quiet=True,
50            ... expression="strds_map_test_case = 1")
51            0
52            >>> grass.run_command("r.timestamp", map="strds_map_test_case",
53            ...                   date="15 jan 1999", quiet=True)
54            0
55            >>> mapset = tgis.get_current_mapset()
56            >>> name = "strds_map_test_case"
57            >>> identifier = "%s@%s" % (name, mapset)
58            >>> rmap = RasterDataset(identifier)
59            >>> rmap.map_exists()
60            True
61            >>> rmap.read_timestamp_from_grass()
62            True
63            >>> rmap.get_temporal_extent_as_tuple()
64            (datetime.datetime(1999, 1, 15, 0, 0), None)
65            >>> rmap.load()
66            True
67            >>> rmap.spatial_extent.print_info()
68             +-------------------- Spatial extent ----------------------------------------+
69             | North:...................... 80.0
70             | South:...................... 0.0
71             | East:.. .................... 120.0
72             | West:....................... 0.0
73             | Top:........................ 0.0
74             | Bottom:..................... 0.0
75            >>> rmap.absolute_time.print_info()
76             +-------------------- Absolute time -----------------------------------------+
77             | Start time:................. 1999-01-15 00:00:00
78             | End time:................... None
79            >>> rmap.metadata.print_info()
80             +-------------------- Metadata information ----------------------------------+
81             | Datatype:................... CELL
82             | Number of columns:.......... 8
83             | Number of rows:............. 12
84             | Number of cells:............ 96
85             | North-South resolution:..... 10.0
86             | East-west resolution:....... 10.0
87             | Minimum value:.............. 1.0
88             | Maximum value:.............. 1.0
89
90            >>> grass.run_command("r.timestamp", map="strds_map_test_case",
91            ...                   date="2 years", quiet=True)
92            0
93            >>> rmap.read_timestamp_from_grass()
94            True
95            >>> rmap.get_temporal_extent_as_tuple()
96            (2, None)
97            >>> rmap.get_relative_time_unit()
98            'years'
99            >>> rmap.is_in_db()
100            False
101            >>> rmap.is_stds()
102            False
103
104            >>> newmap = rmap.get_new_instance("new@PERMANENT")
105            >>> isinstance(newmap, RasterDataset)
106            True
107            >>> newstrds = rmap.get_new_stds_instance("new@PERMANENT")
108            >>> isinstance(newstrds, SpaceTimeRasterDataset)
109            True
110            >>> rmap.get_type()
111            'raster'
112            >>> rmap.set_absolute_time(start_time=datetime(2001,1,1),
113            ...                        end_time=datetime(2012,1,1))
114            True
115            >>> rmap.get_absolute_time()
116            (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
117            >>> rmap.get_temporal_extent_as_tuple()
118            (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
119            >>> rmap.get_name()
120            'strds_map_test_case'
121            >>> rmap.get_mapset() == mapset
122            True
123            >>> rmap.get_temporal_type()
124            'absolute'
125            >>> rmap.get_spatial_extent_as_tuple()
126            (80.0, 0.0, 120.0, 0.0, 0.0, 0.0)
127            >>> rmap.is_time_absolute()
128            True
129            >>> rmap.is_time_relative()
130            False
131
132            >>> grass.run_command("g.remove", flags="f", type="raster", name=name, quiet=True)
133            0
134            >>> grass.del_temp_region()
135
136    """
137    def __init__(self, ident):
138        AbstractMapDataset.__init__(self)
139        self.reset(ident)
140
141    def is_stds(self):
142        """Return True if this class is a space time dataset
143
144           :return: True if this class is a space time dataset, False otherwise
145        """
146        return False
147
148    def get_type(self):
149        return 'raster'
150
151    def get_new_instance(self, ident):
152        """Return a new instance with the type of this class"""
153        return RasterDataset(ident)
154
155    def get_new_stds_instance(self, ident):
156        """Return a new space time dataset instance in which maps
157        are stored with the type of this class"""
158        return SpaceTimeRasterDataset(ident)
159
160    def spatial_overlapping(self, dataset):
161        """Return True if the spatial extents 2d overlap"""
162        return self.spatial_extent.overlapping_2d(dataset.spatial_extent)
163
164    def spatial_relation(self, dataset):
165        """Return the two dimensional spatial relation"""
166        return self.spatial_extent.spatial_relation_2d(dataset.spatial_extent)
167
168    def spatial_intersection(self, dataset):
169        """Return the two dimensional intersection as spatial_extent
170           object or None in case no intersection was found.
171
172           :param dataset: The abstract dataset to intersect with
173           :return: The intersection spatial extent or None
174        """
175        return self.spatial_extent.intersect_2d(dataset.spatial_extent)
176
177    def spatial_union(self, dataset):
178        """Return the two dimensional union as spatial_extent
179           object or None in case the extents does not overlap or meet.
180
181           :param dataset :The abstract dataset to create a union with
182           :return: The union spatial extent or None
183        """
184        return self.spatial_extent.union_2d(dataset.spatial_extent)
185
186    def spatial_disjoint_union(self, dataset):
187        """Return the two dimensional union as spatial_extent object.
188
189           :param dataset: The abstract dataset to create a union with
190           :return: The union spatial extent
191        """
192        return self.spatial_extent.disjoint_union_2d(dataset.spatial_extent)
193
194    def get_np_array(self):
195        """Return this raster map as memmap numpy style array to access the raster
196           values in numpy style without loading the whole map in the RAM.
197
198           In case this raster map does exists in the grass spatial database,
199           the map will be exported using r.out.bin to a temporary location
200           and assigned to the memmap object that is returned by this function.
201
202           In case the raster map does not exist, an empty temporary
203           binary file will be created and assigned to the memap object.
204
205           You need to call the write function to write the memmap
206           array back into grass.
207        """
208
209        a = garray.array()
210
211        if self.map_exists():
212            a.read(self.get_map_id())
213
214        return a
215
216    def reset(self, ident):
217        """Reset the internal structure and set the identifier"""
218        self.base = RasterBase(ident=ident)
219        self.absolute_time = RasterAbsoluteTime(ident=ident)
220        self.relative_time = RasterRelativeTime(ident=ident)
221        self.spatial_extent = RasterSpatialExtent(ident=ident)
222        self.metadata = RasterMetadata(ident=ident)
223        self.stds_register = RasterSTDSRegister(ident=ident)
224
225    def has_grass_timestamp(self):
226        """Check if a grass file based time stamp exists for this map.
227
228           :return: True if success, False on error
229        """
230        return self.ciface.has_raster_timestamp(self.get_name(),
231                                                self.get_mapset())
232
233    def read_timestamp_from_grass(self):
234        """Read the timestamp of this map from the map metadata
235           in the grass file system based spatial database and
236           set the internal time stamp that should be insert/updated
237           in the temporal database.
238
239           :return: True if success, False on error
240        """
241
242        if not self.has_grass_timestamp():
243            return False
244
245        check, dates = self.ciface.read_raster_timestamp(self.get_name(),
246                                                         self.get_mapset(),)
247
248        if check < 1:
249            self.msgr.error(_("Unable to read timestamp file "
250                              "for raster map <%s>" % (self.get_map_id())))
251            return False
252
253        if len(dates) == 2:
254            self.set_absolute_time(dates[0], dates[1])
255        else:
256            self.set_relative_time(dates[0], dates[1], dates[2])
257
258        return True
259
260    def write_timestamp_to_grass(self):
261        """Write the timestamp of this map into the map metadata in
262           the grass file system based spatial database.
263
264           Internally the libgis API functions are used for writing
265
266           :return: True if success, False on error
267        """
268        check = self.ciface.write_raster_timestamp(self.get_name(),
269                                                   self.get_mapset(),
270                                                   self._convert_timestamp())
271
272        if check == -1:
273            self.msgr.error(_("Unable to create timestamp file "
274                              "for raster map <%s>" % (self.get_map_id())))
275            return False
276
277        if check == -2:
278            self.msgr.error(_("Invalid datetime in timestamp for raster map "
279                              "<%s>" % (self.get_map_id())))
280            return False
281
282        if check == -3:
283            self.msgr.error(_("Internal error"))
284            return False
285
286        return True
287
288    def remove_timestamp_from_grass(self):
289        """Remove the timestamp from the grass file system based
290           spatial database
291
292           Internally the libgis API functions are used for removal
293
294           :return: True if success, False on error
295        """
296        check = self.ciface.remove_raster_timestamp(self.get_name(),
297                                                    self.get_mapset())
298
299        if check == -1:
300            self.msgr.error(_("Unable to remove timestamp for raster map <%s>"
301                            % (self.get_name())))
302            return False
303
304        return True
305
306    def map_exists(self):
307        """Return True in case the map exists in the grass spatial database
308
309           :return: True if map exists, False otherwise
310        """
311        return self.ciface.raster_map_exists(self.get_name(),
312                                             self.get_mapset())
313
314    def load(self):
315        """Load all info from an existing raster map into the internal structure
316
317           This method checks first if the map exists, in case it exists
318           the metadata of the map is put into this object and True is returned
319
320           :return: True is the map exists and the metadata was filled
321                    successfully and getting the data was successful,
322                    False otherwise
323        """
324
325        if self.map_exists() is not True:
326            return False
327
328        # Fill base information
329        self.base.set_creator(str(getpass.getuser()))
330
331        kvp = self.ciface.read_raster_info(self.get_name(),
332                                           self.get_mapset())
333
334        if kvp:
335            # Fill spatial extent
336            self.set_spatial_extent_from_values(north=kvp["north"],
337                                                south=kvp["south"],
338                                                east=kvp["east"],
339                                                west=kvp["west"])
340
341            # Fill metadata
342            self.metadata.set_nsres(kvp["nsres"])
343            self.metadata.set_ewres(kvp["ewres"])
344            self.metadata.set_datatype(kvp["datatype"])
345            self.metadata.set_min(kvp["min"])
346            self.metadata.set_max(kvp["max"])
347
348            rows = int(kvp["rows"])
349            cols = int(kvp["cols"])
350
351            ncells = cols * rows
352
353            self.metadata.set_cols(cols)
354            self.metadata.set_rows(rows)
355            self.metadata.set_number_of_cells(ncells)
356
357            return True
358
359        return False
360
361###############################################################################
362
363
364class Raster3DDataset(AbstractMapDataset):
365    """Raster3d dataset class
366
367        This class provides functions to select, update, insert or delete raster3d
368        map information and valid time stamps into the SQL temporal database.
369
370        Usage:
371
372        .. code-block:: python
373
374            >>> import grass.script as grass
375            >>> init()
376            >>> grass.use_temp_region()
377            >>> grass.run_command("g.region", n=80.0, s=0.0, e=120.0, w=0.0,
378            ... t=100.0, b=0.0, res=10.0, res3=10.0)
379            0
380            >>> grass.run_command("r3.mapcalc", overwrite=True, quiet=True,
381            ...                   expression="str3ds_map_test_case = 1")
382            0
383            >>> grass.run_command("r3.timestamp", map="str3ds_map_test_case",
384            ...                   date="15 jan 1999", quiet=True)
385            0
386            >>> mapset = get_current_mapset()
387            >>> name = "str3ds_map_test_case"
388            >>> identifier = "%s@%s" % (name, mapset)
389            >>> r3map = Raster3DDataset(identifier)
390            >>> r3map.map_exists()
391            True
392            >>> r3map.read_timestamp_from_grass()
393            True
394            >>> r3map.get_temporal_extent_as_tuple()
395            (datetime.datetime(1999, 1, 15, 0, 0), None)
396            >>> r3map.load()
397            True
398            >>> r3map.spatial_extent.print_info()
399             +-------------------- Spatial extent ----------------------------------------+
400             | North:...................... 80.0
401             | South:...................... 0.0
402             | East:.. .................... 120.0
403             | West:....................... 0.0
404             | Top:........................ 100.0
405             | Bottom:..................... 0.0
406            >>> r3map.absolute_time.print_info()
407             +-------------------- Absolute time -----------------------------------------+
408             | Start time:................. 1999-01-15 00:00:00
409             | End time:................... None
410            >>> r3map.metadata.print_info()
411             +-------------------- Metadata information ----------------------------------+
412             | Datatype:................... DCELL
413             | Number of columns:.......... 8
414             | Number of rows:............. 12
415             | Number of cells:............ 960
416             | North-South resolution:..... 10.0
417             | East-west resolution:....... 10.0
418             | Minimum value:.............. 1.0
419             | Maximum value:.............. 1.0
420             | Number of depths:........... 10
421             | Top-Bottom resolution:...... 10.0
422
423            >>> grass.run_command("r3.timestamp", map="str3ds_map_test_case",
424            ...                   date="2 years", quiet=True)
425            0
426            >>> r3map.read_timestamp_from_grass()
427            True
428            >>> r3map.get_temporal_extent_as_tuple()
429            (2, None)
430            >>> r3map.get_relative_time_unit()
431            'years'
432            >>> r3map.is_in_db()
433            False
434            >>> r3map.is_stds()
435            False
436
437            >>> newmap = r3map.get_new_instance("new@PERMANENT")
438            >>> isinstance(newmap, Raster3DDataset)
439            True
440            >>> newstr3ds = r3map.get_new_stds_instance("new@PERMANENT")
441            >>> isinstance(newstr3ds, SpaceTimeRaster3DDataset)
442            True
443            >>> r3map.get_type()
444            'raster3d'
445            >>> r3map.set_absolute_time(start_time=datetime(2001,1,1),
446            ...                        end_time=datetime(2012,1,1))
447            True
448            >>> r3map.get_absolute_time()
449            (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
450            >>> r3map.get_temporal_extent_as_tuple()
451            (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
452            >>> r3map.get_name()
453            'str3ds_map_test_case'
454            >>> r3map.get_mapset() == mapset
455            True
456            >>> r3map.get_temporal_type()
457            'absolute'
458            >>> r3map.get_spatial_extent_as_tuple()
459            (80.0, 0.0, 120.0, 0.0, 100.0, 0.0)
460            >>> r3map.is_time_absolute()
461            True
462            >>> r3map.is_time_relative()
463            False
464            >>> grass.run_command("g.remove", flags="f", type="raster_3d", name=name, quiet=True)
465            0
466            >>> grass.del_temp_region()
467
468    """
469    def __init__(self, ident):
470        AbstractMapDataset.__init__(self)
471        self.reset(ident)
472
473    def is_stds(self):
474        """Return True if this class is a space time dataset
475
476           :return: True if this class is a space time dataset, False otherwise
477        """
478        return False
479
480    def get_type(self):
481        return "raster3d"
482
483    def get_new_instance(self, ident):
484        """Return a new instance with the type of this class"""
485        return Raster3DDataset(ident)
486
487    def get_new_stds_instance(self, ident):
488        """Return a new space time dataset instance in which maps
489        are stored with the type of this class"""
490        return SpaceTimeRaster3DDataset(ident)
491
492    def spatial_overlapping(self, dataset):
493        """Return True if the spatial extents overlap"""
494        if self.get_type() == dataset.get_type() or dataset.get_type() == "str3ds":
495            return self.spatial_extent.overlapping(dataset.spatial_extent)
496        else:
497            return self.spatial_extent.overlapping_2d(dataset.spatial_extent)
498
499    def spatial_relation(self, dataset):
500        """Return the two or three dimensional spatial relation"""
501        if self.get_type() == dataset.get_type() or dataset.get_type() == "str3ds":
502            return self.spatial_extent.spatial_relation(dataset.spatial_extent)
503        else:
504            return self.spatial_extent.spatial_relation_2d(dataset.spatial_extent)
505
506    def spatial_intersection(self, dataset):
507        """Return the three or two dimensional intersection as spatial_extent
508           object or None in case no intersection was found.
509
510           :param dataset: The abstract dataset to intersect with
511           :return: The intersection spatial extent or None
512        """
513        if self.get_type() == dataset.get_type() or dataset.get_type() == "str3ds":
514            return self.spatial_extent.intersect(dataset.spatial_extent)
515        else:
516            return self.spatial_extent.intersect_2d(dataset.spatial_extent)
517
518    def spatial_union(self, dataset):
519        """Return the three or two dimensional union as spatial_extent
520           object or None in case the extents does not overlap or meet.
521
522           :param dataset: The abstract dataset to create a union with
523           :return: The union spatial extent or None
524        """
525        if self.get_type() == dataset.get_type() or dataset.get_type() == "str3ds":
526            return self.spatial_extent.union(dataset.spatial_extent)
527        else:
528            return self.spatial_extent.union_2d(dataset.spatial_extent)
529
530    def spatial_disjoint_union(self, dataset):
531        """Return the three or two dimensional union as spatial_extent object.
532
533           :param dataset: The abstract dataset to create a union with
534           :return: The union spatial extent
535        """
536        if self.get_type() == dataset.get_type() or dataset.get_type() == "str3ds":
537            return self.spatial_extent.disjoint_union(dataset.spatial_extent)
538        else:
539            return self.spatial_extent.disjoint_union_2d(dataset.spatial_extent)
540
541    def get_np_array(self):
542        """Return this 3D raster map as memmap numpy style array to access the
543           3D raster values in numpy style without loading the whole map in
544           the RAM.
545
546           In case this 3D raster map does exists in the grass spatial database,
547           the map will be exported using r3.out.bin to a temporary location
548           and assigned to the memmap object that is returned by this function.
549
550           In case the 3D raster map does not exist, an empty temporary
551           binary file will be created and assigned to the memap object.
552
553           You need to call the write function to write the memmap
554           array back into grass.
555        """
556
557        a = garray.array3d()
558
559        if self.map_exists():
560            a.read(self.get_map_id())
561
562        return a
563
564    def reset(self, ident):
565        """Reset the internal structure and set the identifier"""
566        self.base = Raster3DBase(ident=ident)
567        self.absolute_time = Raster3DAbsoluteTime(ident=ident)
568        self.relative_time = Raster3DRelativeTime(ident=ident)
569        self.spatial_extent = Raster3DSpatialExtent(ident=ident)
570        self.metadata = Raster3DMetadata(ident=ident)
571        self.stds_register = Raster3DSTDSRegister(ident=ident)
572
573    def has_grass_timestamp(self):
574        """Check if a grass file bsased time stamp exists for this map.
575
576           :return: True if success, False on error
577        """
578        return self.ciface.has_raster3d_timestamp(self.get_name(),
579                                                  self.get_mapset())
580
581    def read_timestamp_from_grass(self):
582        """Read the timestamp of this map from the map metadata
583           in the grass file system based spatial database and
584           set the internal time stamp that should be insert/updated
585           in the temporal database.
586
587           :return: True if success, False on error
588        """
589
590        if not self.has_grass_timestamp():
591            return False
592
593        check, dates = self.ciface.read_raster3d_timestamp(self.get_name(),
594                                                           self.get_mapset(),)
595
596        if check < 1:
597            self.msgr.error(_("Unable to read timestamp file "
598                              "for 3D raster map <%s>" % (self.get_map_id())))
599            return False
600
601        if len(dates) == 2:
602            self.set_absolute_time(dates[0], dates[1])
603        else:
604            self.set_relative_time(dates[0], dates[1], dates[2])
605
606        return True
607
608    def write_timestamp_to_grass(self):
609        """Write the timestamp of this map into the map metadata
610        in the grass file system based spatial database.
611
612           Internally the libgis API functions are used for writing
613
614           :return: True if success, False on error
615        """
616        check = self.ciface.write_raster3d_timestamp(self.get_name(),
617                                                     self.get_mapset(),
618                                                     self._convert_timestamp())
619
620        if check == -1:
621            self.msgr.error(_("Unable to create timestamp file "
622                              "for 3D raster map <%s>" % (self.get_map_id())))
623            return False
624
625        if check == -2:
626            self.msgr.error(_("Invalid datetime in timestamp for 3D raster "
627                              "map <%s>" % (self.get_map_id())))
628            return False
629
630        if check == -3:
631            self.msgr.error(_("Internal error"))
632            return False
633
634        return True
635
636    def remove_timestamp_from_grass(self):
637        """Remove the timestamp from the grass file system based spatial database
638
639           :return: True if success, False on error
640        """
641        check = self.ciface.remove_raster3d_timestamp(self.get_name(),
642                                                      self.get_mapset())
643
644        if check == -1:
645            self.msgr.error(_("Unable to remove timestamp for raster map "
646                              "<%s>" % (self.get_name())))
647            return False
648
649        return True
650
651    def map_exists(self):
652        """Return True in case the map exists in the grass spatial database
653
654           :return: True if map exists, False otherwise
655        """
656        return self.ciface.raster3d_map_exists(self.get_name(),
657                                               self.get_mapset())
658
659    def load(self):
660        """Load all info from an existing 3d raster map into the internal structure
661
662           This method checks first if the map exists, in case it exists
663           the metadata of the map is put into this object and True is returned
664
665           :return: True is the map exists and the metadata was filled
666                    successfully and getting the data was successful,
667                    False otherwise
668        """
669
670        if self.map_exists() is not True:
671            return False
672
673        # Fill base information
674        self.base.set_creator(str(getpass.getuser()))
675
676        # Fill spatial extent
677        kvp = self.ciface.read_raster3d_info(self.get_name(),
678                                             self.get_mapset())
679
680        if kvp:
681            self.set_spatial_extent_from_values(north=kvp["north"],
682                                                south=kvp["south"],
683                                                east=kvp["east"],
684                                                west=kvp["west"],
685                                                top=kvp["top"],
686                                                bottom=kvp["bottom"])
687
688            # Fill metadata
689            self.metadata.set_nsres(kvp["nsres"])
690            self.metadata.set_ewres(kvp["ewres"])
691            self.metadata.set_tbres(kvp["tbres"])
692            self.metadata.set_datatype(kvp["datatype"])
693            self.metadata.set_min(kvp["min"])
694            self.metadata.set_max(kvp["max"])
695
696            rows = int(kvp["rows"])
697            cols = int(kvp["cols"])
698            depths = int(kvp["depths"])
699
700            ncells = cols * rows * depths
701
702            self.metadata.set_cols(cols)
703            self.metadata.set_rows(rows)
704            self.metadata.set_depths(depths)
705            self.metadata.set_number_of_cells(ncells)
706
707            return True
708
709        return False
710
711###############################################################################
712
713
714class VectorDataset(AbstractMapDataset):
715    """Vector dataset class
716
717        This class provides functions to select, update, insert or delete vector
718        map information and valid time stamps into the SQL temporal database.
719
720        Usage:
721
722        .. code-block:: python
723
724            >>> import grass.script as grass
725            >>> init()
726            >>> grass.use_temp_region()
727            >>> grass.run_command("g.region", n=80.0, s=0.0, e=120.0, w=0.0,
728            ... t=1.0, b=0.0, res=10.0)
729            0
730            >>> grass.run_command("v.random", overwrite=True, output="stvds_map_test_case",
731            ... n=100, zmin=0, zmax=100, flags="z", column="elevation", quiet=True)
732            0
733            >>> grass.run_command("v.timestamp", map="stvds_map_test_case",
734            ...                   date="15 jan 1999", quiet=True)
735            0
736            >>> mapset = get_current_mapset()
737            >>> name = "stvds_map_test_case"
738            >>> identifier = "%s@%s" % (name, mapset)
739            >>> vmap = VectorDataset(identifier)
740            >>> vmap.map_exists()
741            True
742            >>> vmap.read_timestamp_from_grass()
743            True
744            >>> vmap.get_temporal_extent_as_tuple()
745            (datetime.datetime(1999, 1, 15, 0, 0), None)
746            >>> vmap.load()
747            True
748            >>> vmap.absolute_time.print_info()
749             +-------------------- Absolute time -----------------------------------------+
750             | Start time:................. 1999-01-15 00:00:00
751             | End time:................... None
752            >>> vmap.metadata.print_info()
753             +-------------------- Metadata information ----------------------------------+
754             | Is map 3d .................. True
755             | Number of points ........... 100
756             | Number of lines ............ 0
757             | Number of boundaries ....... 0
758             | Number of centroids ........ 0
759             | Number of faces ............ 0
760             | Number of kernels .......... 0
761             | Number of primitives ....... 100
762             | Number of nodes ............ 0
763             | Number of areas ............ 0
764             | Number of islands .......... 0
765             | Number of holes ............ 0
766             | Number of volumes .......... 0
767
768            >>> grass.run_command("v.timestamp", map="stvds_map_test_case",
769            ...                   date="2 years", quiet=True)
770            0
771            >>> vmap.read_timestamp_from_grass()
772            True
773            >>> vmap.get_temporal_extent_as_tuple()
774            (2, None)
775            >>> vmap.get_relative_time_unit()
776            'years'
777            >>> vmap.is_in_db()
778            False
779            >>> vmap.is_stds()
780            False
781
782            >>> newmap = vmap.get_new_instance("new@PERMANENT")
783            >>> isinstance(newmap, VectorDataset)
784            True
785            >>> newstvds = vmap.get_new_stds_instance("new@PERMANENT")
786            >>> isinstance(newstvds, SpaceTimeVectorDataset)
787            True
788            >>> vmap.get_type()
789            'vector'
790            >>> vmap.set_absolute_time(start_time=datetime(2001,1,1),
791            ...                        end_time=datetime(2012,1,1))
792            True
793            >>> vmap.get_absolute_time()
794            (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
795            >>> vmap.get_temporal_extent_as_tuple()
796            (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
797            >>> vmap.get_name()
798            'stvds_map_test_case'
799            >>> vmap.get_mapset() == mapset
800            True
801            >>> vmap.get_temporal_type()
802            'absolute'
803            >>> vmap.is_time_absolute()
804            True
805            >>> vmap.is_time_relative()
806            False
807            >>> grass.run_command("g.remove", flags="f", type="vector", name=name, quiet=True)
808            0
809            >>> grass.del_temp_region()
810
811    """
812    def __init__(self, ident):
813        AbstractMapDataset.__init__(self)
814        self.reset(ident)
815
816    def is_stds(self):
817        """Return True if this class is a space time dataset
818
819           :return: True if this class is a space time dataset, False otherwise
820        """
821        return False
822
823    def get_type(self):
824        return "vector"
825
826    def get_new_instance(self, ident):
827        """Return a new instance with the type of this class"""
828        return VectorDataset(ident)
829
830    def get_new_stds_instance(self, ident):
831        """Return a new space time dataset instance in which maps
832        are stored with the type of this class"""
833        return SpaceTimeVectorDataset(ident)
834
835    def get_layer(self):
836        """Return the layer"""
837        return self.base.get_layer()
838
839    def spatial_overlapping(self, dataset):
840        """Return True if the spatial extents 2d overlap"""
841
842        return self.spatial_extent.overlapping_2d(dataset.spatial_extent)
843
844    def spatial_relation(self, dataset):
845        """Return the two dimensional spatial relation"""
846
847        return self.spatial_extent.spatial_relation_2d(dataset.spatial_extent)
848
849    def spatial_intersection(self, dataset):
850        """Return the two dimensional intersection as spatial_extent
851           object or None in case no intersection was found.
852
853           :param dataset: The abstract dataset to intersect with
854           :return: The intersection spatial extent or None
855        """
856        return self.spatial_extent.intersect_2d(dataset.spatial_extent)
857
858    def spatial_union(self, dataset):
859        """Return the two dimensional union as spatial_extent
860           object or None in case the extents does not overlap or meet.
861
862           :param dataset: The abstract dataset to create a union with
863           :return: The union spatial extent or None
864        """
865        return self.spatial_extent.union_2d(dataset.spatial_extent)
866
867    def spatial_disjoint_union(self, dataset):
868        """Return the two dimensional union as spatial_extent object.
869
870           :param dataset: The abstract dataset to create a union with
871           :return: The union spatial extent
872        """
873        return self.spatial_extent.disjoint_union_2d(dataset.spatial_extent)
874
875    def reset(self, ident):
876        """Reset the internal structure and set the identifier"""
877        self.base = VectorBase(ident=ident)
878        self.absolute_time = VectorAbsoluteTime(ident=ident)
879        self.relative_time = VectorRelativeTime(ident=ident)
880        self.spatial_extent = VectorSpatialExtent(ident=ident)
881        self.metadata = VectorMetadata(ident=ident)
882        self.stds_register = VectorSTDSRegister(ident=ident)
883
884    def has_grass_timestamp(self):
885        """Check if a grass file bsased time stamp exists for this map.
886        """
887        return self.ciface.has_vector_timestamp(self.get_name(),
888                                                self.get_mapset(),
889                                                self.get_layer())
890
891    def read_timestamp_from_grass(self):
892        """Read the timestamp of this map from the map metadata
893           in the grass file system based spatial database and
894           set the internal time stamp that should be insert/updated
895           in the temporal database.
896        """
897
898        if not self.has_grass_timestamp():
899            return False
900
901        check, dates = self.ciface.read_vector_timestamp(self.get_name(),
902                                                         self.get_mapset(),)
903
904        if check < 1:
905            self.msgr.error(_("Unable to read timestamp file "
906                              "for vector map <%s>" % (self.get_map_id())))
907            return False
908
909        if len(dates) == 2:
910            self.set_absolute_time(dates[0], dates[1])
911        else:
912            self.set_relative_time(dates[0], dates[1], dates[2])
913
914        return True
915
916    def write_timestamp_to_grass(self):
917        """Write the timestamp of this map into the map metadata in
918           the grass file system based spatial database.
919
920           Internally the libgis API functions are used for writing
921        """
922        check = self.ciface.write_vector_timestamp(self.get_name(),
923                                                   self.get_mapset(),
924                                                   self._convert_timestamp(),
925                                                   self.get_layer())
926
927        if check == -1:
928            self.msgr.error(_("Unable to create timestamp file "
929                              "for vector map <%s>" % (self.get_map_id())))
930            return False
931
932        if check == -2:
933            self.msgr.error(_("Invalid datetime in timestamp for vector "
934                              "map <%s>" % (self.get_map_id())))
935            return False
936
937        return True
938
939    def remove_timestamp_from_grass(self):
940        """Remove the timestamp from the grass file system based spatial
941           database
942
943           Internally the libgis API functions are used for removal
944        """
945        check = self.ciface.remove_vector_timestamp(self.get_name(),
946                                                    self.get_mapset())
947
948        if check == -1:
949            self.msgr.error(_("Unable to remove timestamp for vector "
950                              "map <%s>" % (self.get_name())))
951            return False
952
953        return True
954
955    def map_exists(self):
956        """Return True in case the map exists in the grass spatial database
957
958           :return: True if map exists, False otherwise
959        """
960        return self.ciface.vector_map_exists(self.get_name(),
961                                             self.get_mapset())
962
963    def load(self):
964
965        """Load all info from an existing vector map into the internal structure
966
967           This method checks first if the map exists, in case it exists
968           the metadata of the map is put into this object and True is returned
969
970           :return: True is the map exists and the metadata was filled
971                    successfully and getting the data was successful,
972                    False otherwise
973        """
974
975        if self.map_exists() is not True:
976            return False
977
978        # Fill base information
979        self.base.set_creator(str(getpass.getuser()))
980
981        # Get the data from an existing vector map
982
983        kvp = self.ciface.read_vector_info(self.get_name(),
984                                           self.get_mapset())
985
986        if kvp:
987            # Fill spatial extent
988            self.set_spatial_extent_from_values(north=kvp["north"],
989                                                south=kvp["south"],
990                                                east=kvp["east"],
991                                                west=kvp["west"],
992                                                top=kvp["top"],
993                                                bottom=kvp["bottom"])
994
995            # Fill metadata
996            self.metadata.set_3d_info(kvp["map3d"])
997            self.metadata.set_number_of_points(kvp["points"])
998            self.metadata.set_number_of_lines(kvp["lines"])
999            self.metadata.set_number_of_boundaries(kvp["boundaries"])
1000            self.metadata.set_number_of_centroids(kvp["centroids"])
1001            self.metadata.set_number_of_faces(kvp["faces"])
1002            self.metadata.set_number_of_kernels(kvp["kernels"])
1003            self.metadata.set_number_of_primitives(kvp["primitives"])
1004            self.metadata.set_number_of_nodes(kvp["nodes"])
1005            self.metadata.set_number_of_areas(kvp["areas"])
1006            self.metadata.set_number_of_islands(kvp["islands"])
1007            self.metadata.set_number_of_holes(kvp["holes"])
1008            self.metadata.set_number_of_volumes(kvp["volumes"])
1009
1010            return True
1011
1012        return False
1013
1014###############################################################################
1015
1016
1017class SpaceTimeRasterDataset(AbstractSpaceTimeDataset):
1018    """Space time raster dataset class
1019
1020        .. code-block:: python
1021
1022            >>> import grass.temporal as tgis
1023            >>> tgis.init()
1024            >>> mapset = tgis.get_current_mapset()
1025            >>> strds = tgis.SpaceTimeRasterDataset("old@%s"%mapset)
1026            >>> strds.is_in_db()
1027            False
1028            >>> strds.is_stds()
1029            True
1030            >>> strds.get_type()
1031            'strds'
1032            >>> newstrds = strds.get_new_instance("newstrds@%s"%mapset)
1033            >>> isinstance(newstrds, SpaceTimeRasterDataset)
1034            True
1035            >>> newmap = strds.get_new_map_instance("newmap@%s"%mapset)
1036            >>> isinstance(newmap, RasterDataset)
1037            True
1038            >>> strds.reset("new@%s"%mapset)
1039            >>> strds.is_in_db()
1040            False
1041            >>> strds.reset(None)
1042            >>> strds.is_in_db()
1043            False
1044            >>> strds.get_id()
1045
1046        ...
1047    """
1048    def __init__(self, ident):
1049        AbstractSpaceTimeDataset.__init__(self, ident)
1050
1051    def is_stds(self):
1052        """Return True if this class is a space time dataset
1053
1054           :return: True if this class is a space time dataset, False otherwise
1055        """
1056        return True
1057
1058    def get_type(self):
1059        return "strds"
1060
1061    def get_new_instance(self, ident):
1062        """Return a new instance with the type of this class"""
1063        return SpaceTimeRasterDataset(ident)
1064
1065    def get_new_map_instance(self, ident):
1066        """Return a new instance of a map dataset which is associated "
1067        "with the type of this class"""
1068        return RasterDataset(ident)
1069
1070    def get_map_register(self):
1071        """Return the name of the map register table"""
1072        return self.metadata.get_raster_register()
1073
1074    def set_map_register(self, name):
1075        """Set the name of the map register table"""
1076        self.metadata.set_raster_register(name)
1077
1078    def spatial_overlapping(self, dataset):
1079        """Return True if the spatial extents 2d overlap"""
1080        return self.spatial_extent.overlapping_2d(dataset.spatial_extent)
1081
1082    def spatial_relation(self, dataset):
1083        """Return the two dimensional spatial relation"""
1084        return self.spatial_extent.spatial_relation_2d(dataset.spatial_extent)
1085
1086    def spatial_intersection(self, dataset):
1087        """Return the two dimensional intersection as spatial_extent
1088           object or None in case no intersection was found.
1089
1090           :param dataset: The abstract dataset to intersect with
1091           :return: The intersection spatial extent or None
1092        """
1093        return self.spatial_extent.intersect_2d(dataset.spatial_extent)
1094
1095    def spatial_union(self, dataset):
1096        """Return the two dimensional union as spatial_extent
1097           object or None in case the extents does not overlap or meet.
1098
1099           :param dataset: The abstract dataset to create a union with
1100           :return: The union spatial extent or None
1101        """
1102        return self.spatial_extent.union_2d(dataset.spatial_extent)
1103
1104    def spatial_disjoint_union(self, dataset):
1105        """Return the two dimensional union as spatial_extent object.
1106
1107           :param dataset: The abstract dataset to create a union with
1108           :return: The union spatial extent
1109        """
1110        return self.spatial_extent.disjoint_union_2d(dataset.spatial_extent)
1111
1112    def reset(self, ident):
1113
1114        """Reset the internal structure and set the identifier"""
1115        self.base = STRDSBase(ident=ident)
1116        self.base.set_creator(str(getpass.getuser()))
1117        self.absolute_time = STRDSAbsoluteTime(ident=ident)
1118        self.relative_time = STRDSRelativeTime(ident=ident)
1119        self.spatial_extent = STRDSSpatialExtent(ident=ident)
1120        self.metadata = STRDSMetadata(ident=ident)
1121
1122###############################################################################
1123
1124
1125class SpaceTimeRaster3DDataset(AbstractSpaceTimeDataset):
1126    """Space time raster3d dataset class
1127
1128        .. code-block:: python
1129
1130            >>> import grass.temporal as tgis
1131            >>> tgis.init()
1132            >>> mapset = tgis.get_current_mapset()
1133            >>> str3ds = tgis.SpaceTimeRaster3DDataset("old@%s"%mapset)
1134            >>> str3ds.is_in_db()
1135            False
1136            >>> str3ds.is_stds()
1137            True
1138            >>> str3ds.get_type()
1139            'str3ds'
1140            >>> newstrds = str3ds.get_new_instance("newstrds@%s"%mapset)
1141            >>> isinstance(newstrds, SpaceTimeRaster3DDataset)
1142            True
1143            >>> newmap = str3ds.get_new_map_instance("newmap@%s"%mapset)
1144            >>> isinstance(newmap, Raster3DDataset)
1145            True
1146            >>> str3ds.reset("new@%s"%mapset)
1147            >>> str3ds.is_in_db()
1148            False
1149            >>> str3ds.reset(None)
1150            >>> str3ds.is_in_db()
1151            False
1152            >>> str3ds.get_id()
1153
1154        ...
1155    """
1156
1157    def __init__(self, ident):
1158        AbstractSpaceTimeDataset.__init__(self, ident)
1159
1160    def is_stds(self):
1161        """Return True if this class is a space time dataset
1162
1163           :return: True if this class is a space time dataset, False otherwise
1164        """
1165        return True
1166
1167    def get_type(self):
1168        return "str3ds"
1169
1170    def get_new_instance(self, ident):
1171        """Return a new instance with the type of this class"""
1172        return SpaceTimeRaster3DDataset(ident)
1173
1174    def get_new_map_instance(self, ident):
1175        """Return a new instance of a map dataset which is associated
1176        with the type of this class"""
1177        return Raster3DDataset(ident)
1178
1179    def get_map_register(self):
1180        """Return the name of the map register table"""
1181        return self.metadata.get_raster3d_register()
1182
1183    def set_map_register(self, name):
1184        """Set the name of the map register table"""
1185        self.metadata.set_raster3d_register(name)
1186
1187    def spatial_overlapping(self, dataset):
1188        """Return True if the spatial extents overlap"""
1189
1190        if self.get_type() == dataset.get_type() or dataset.get_type() == "str3ds":
1191            return self.spatial_extent.overlapping(dataset.spatial_extent)
1192        else:
1193            return self.spatial_extent.overlapping_2d(dataset.spatial_extent)
1194
1195    def spatial_relation(self, dataset):
1196        """Return the two or three dimensional spatial relation"""
1197
1198        if self.get_type() == dataset.get_type() or \
1199           dataset.get_type() == "str3ds":
1200            return self.spatial_extent.spatial_relation(dataset.spatial_extent)
1201        else:
1202            return self.spatial_extent.spatial_relation_2d(dataset.spatial_extent)
1203
1204    def spatial_intersection(self, dataset):
1205        """Return the three or two dimensional intersection as spatial_extent
1206           object or None in case no intersection was found.
1207
1208           :param dataset: The abstract dataset to intersect with
1209           :return: The intersection spatial extent or None
1210        """
1211        if self.get_type() == dataset.get_type() or dataset.get_type() == "raster3d":
1212            return self.spatial_extent.intersect(dataset.spatial_extent)
1213        else:
1214            return self.spatial_extent.intersect_2d(dataset.spatial_extent)
1215
1216    def spatial_union(self, dataset):
1217        """Return the three or two dimensional union as spatial_extent
1218           object or None in case the extents does not overlap or meet.
1219
1220           :param dataset: The abstract dataset to create a union with
1221           :return: The union spatial extent or None
1222        """
1223        if self.get_type() == dataset.get_type() or dataset.get_type() == "raster3d":
1224            return self.spatial_extent.union(dataset.spatial_extent)
1225        else:
1226            return self.spatial_extent.union_2d(dataset.spatial_extent)
1227
1228    def spatial_disjoint_union(self, dataset):
1229        """Return the three or two dimensional union as spatial_extent object.
1230
1231           :param dataset: The abstract dataset to create a union with
1232           :return: The union spatial extent
1233        """
1234        if self.get_type() == dataset.get_type() or dataset.get_type() == "raster3d":
1235            return self.spatial_extent.disjoint_union(dataset.spatial_extent)
1236        else:
1237            return self.spatial_extent.disjoint_union_2d(dataset.spatial_extent)
1238
1239    def reset(self, ident):
1240
1241        """Reset the internal structure and set the identifier"""
1242        self.base = STR3DSBase(ident=ident)
1243        self.base.set_creator(str(getpass.getuser()))
1244        self.absolute_time = STR3DSAbsoluteTime(ident=ident)
1245        self.relative_time = STR3DSRelativeTime(ident=ident)
1246        self.spatial_extent = STR3DSSpatialExtent(ident=ident)
1247        self.metadata = STR3DSMetadata(ident=ident)
1248
1249###############################################################################
1250
1251
1252class SpaceTimeVectorDataset(AbstractSpaceTimeDataset):
1253    """Space time vector dataset class
1254
1255        .. code-block:: python
1256
1257            >>> import grass.temporal as tgis
1258            >>> tgis.init()
1259            >>> mapset = tgis.get_current_mapset()
1260            >>> stvds = tgis.SpaceTimeVectorDataset("old@%s"%mapset)
1261            >>> stvds.is_in_db()
1262            False
1263            >>> stvds.is_stds()
1264            True
1265            >>> stvds.get_type()
1266            'stvds'
1267            >>> newstvds = stvds.get_new_instance("newstvds@%s"%mapset)
1268            >>> isinstance(newstvds, SpaceTimeVectorDataset)
1269            True
1270            >>> newmap = stvds.get_new_map_instance("newmap@%s"%mapset)
1271            >>> isinstance(newmap, VectorDataset)
1272            True
1273            >>> stvds.reset("new@%s"%mapset)
1274            >>> stvds.is_in_db()
1275            False
1276            >>> stvds.reset(None)
1277            >>> stvds.is_in_db()
1278            False
1279            >>> stvds.get_id()
1280
1281        ...
1282    """
1283
1284    def __init__(self, ident):
1285        AbstractSpaceTimeDataset.__init__(self, ident)
1286
1287    def is_stds(self):
1288        """Return True if this class is a space time dataset
1289
1290           :return: True if this class is a space time dataset, False otherwise
1291        """
1292        return True
1293
1294    def get_type(self):
1295        return "stvds"
1296
1297    def get_new_instance(self, ident):
1298        """Return a new instance with the type of this class"""
1299        return SpaceTimeVectorDataset(ident)
1300
1301    def get_new_map_instance(self, ident):
1302        """Return a new instance of a map dataset which is associated
1303        with the type of this class"""
1304        return VectorDataset(ident)
1305
1306    def get_map_register(self):
1307        """Return the name of the map register table"""
1308        return self.metadata.get_vector_register()
1309
1310    def set_map_register(self, name):
1311        """Set the name of the map register table"""
1312        self.metadata.set_vector_register(name)
1313
1314    def spatial_overlapping(self, dataset):
1315        """Return True if the spatial extents 2d overlap"""
1316        return self.spatial_extent.overlapping_2d(dataset.spatial_extent)
1317
1318    def spatial_relation(self, dataset):
1319        """Return the two dimensional spatial relation"""
1320        return self.spatial_extent.spatial_relation_2d(dataset.spatial_extent)
1321
1322    def spatial_intersection(self, dataset):
1323        """Return the two dimensional intersection as spatial_extent
1324           object or None in case no intersection was found.
1325
1326           :param dataset: The abstract dataset to intersect with
1327           :return: The intersection spatial extent or None
1328        """
1329        return self.spatial_extent.intersect_2d(dataset.spatial_extent)
1330
1331    def spatial_union(self, dataset):
1332        """Return the two dimensional union as spatial_extent
1333           object or None in case the extents does not overlap or meet.
1334
1335           :param dataset: The abstract dataset to create a union with
1336           :return: The union spatial extent or None
1337        """
1338        return self.spatial_extent.union_2d(dataset.spatial_extent)
1339
1340    def spatial_disjoint_union(self, dataset):
1341        """Return the two dimensional union as spatial_extent object.
1342
1343           :param dataset: The abstract dataset to create a union with
1344           :return: The union spatial extent
1345        """
1346        return self.spatial_extent.disjoint_union_2d(dataset.spatial_extent)
1347
1348    def reset(self, ident):
1349
1350        """Reset the internal structure and set the identifier"""
1351        self.base = STVDSBase(ident=ident)
1352        self.base.set_creator(str(getpass.getuser()))
1353        self.absolute_time = STVDSAbsoluteTime(ident=ident)
1354        self.relative_time = STVDSRelativeTime(ident=ident)
1355        self.spatial_extent = STVDSSpatialExtent(ident=ident)
1356        self.metadata = STVDSMetadata(ident=ident)
1357
1358###############################################################################
1359
1360if __name__ == "__main__":
1361    import doctest
1362    doctest.testmod()
1363