diff --git a/.gitignore b/.gitignore index e5451fee..1b94b1d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea Pipfile.lock *-checkpoint.ipynb +docs/_build \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml index e69de29b..2f9055ba 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -0,0 +1,12 @@ +version: 2 +build: + os: "ubuntu-20.04" + tools: + python: "3.10" + +python: + install: + - requirements: docs/requirements.txt + +sphinx: + configuration: docs/conf.py \ No newline at end of file diff --git a/pymeos/docs/Makefile b/docs/Makefile similarity index 100% rename from pymeos/docs/Makefile rename to docs/Makefile diff --git a/pymeos/docs/conf.py b/docs/conf.py similarity index 88% rename from pymeos/docs/conf.py rename to docs/conf.py index cc319cbe..9e0df283 100644 --- a/pymeos/docs/conf.py +++ b/docs/conf.py @@ -9,18 +9,20 @@ project = 'PyMEOS' copyright = '2023, Víctor Diví' author = 'Víctor Diví' -release = '1.1.2' +release = '1.1.3a5' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath('../pymeos_cffi')) +sys.path.insert(0, os.path.abspath('../pymeos')) extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', - 'sphinx.ext.intersphinx' + 'sphinx.ext.intersphinx', ] templates_path = ['_templates'] @@ -29,7 +31,6 @@ # -- Intersphinx config -------- intersphinx_mapping = { - 'spans': ('https://runfalk.github.io/spans/', None), 'asyncpg': ('https://magicstack.github.io/asyncpg/current/', None), 'psycopg': ('https://www.psycopg.org/psycopg3/docs/', None), 'psycopg2': ('https://www.psycopg.org/docs/', None), @@ -37,9 +38,8 @@ 'python': ('https://docs.python.org/3', None) } - # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'sphinx_rtd_theme' +html_theme = 'sphinx_book_theme' html_static_path = ['_static'] diff --git a/doc/images/PyMEOS Layer Architecture.png b/docs/images/PyMEOS Layer Architecture.png similarity index 100% rename from doc/images/PyMEOS Layer Architecture.png rename to docs/images/PyMEOS Layer Architecture.png diff --git a/doc/images/PyMEOS Logo.png b/docs/images/PyMEOS Logo.png similarity index 100% rename from doc/images/PyMEOS Logo.png rename to docs/images/PyMEOS Logo.png diff --git a/doc/images/meos-logo.png b/docs/images/meos-logo.png similarity index 100% rename from doc/images/meos-logo.png rename to docs/images/meos-logo.png diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..2a8c6ec6 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,66 @@ +.. PyMEOS documentation master file, created by + sphinx-quickstart on Thu Oct 5 18:26:38 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +======= +PyMEOS +======= + +`MEOS (Mobility Engine, Open Source) `__ is a +C library which enables the manipulation of temporal and spatio-temporal +data based on `MobilityDB `__\ ’s data types +and functions. + +PyMEOS is a library built on top of MEOS that provides all of its +functionality wrapped in a set of Python classes. + +Requirements +============ + +PyMEOS 1.1 requires + +* Python >=3.7 +* MEOS >=1.1 + +Installing PyMEOS +================== + +We recommend installing PyMEOS using one of the available built +distributions using ``pip``: + +.. code-block:: console + + $ pip install pymeos + +See the `installation documentation `__ +for more details and advanced installation instructions. + +.. toctree:: + :caption: User Guide + :hidden: + + src/installation + src/manual + src/examples + + +.. toctree:: + :caption: API Reference + :hidden: + + src/api/pymeos.meos_init + src/api/pymeos.collections + src/api/pymeos.temporal + src/api/pymeos.main + src/api/pymeos.boxes + src/api/pymeos.aggregators + src/api/pymeos.plotters + src/api/pymeos.db + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/pymeos/docs/make.bat b/docs/make.bat similarity index 100% rename from pymeos/docs/make.bat rename to docs/make.bat diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..1961cdf1 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,10 @@ +python-dateutil +shapely +pymeos_cffi==1.1.0a6 +asyncpg +psycopg +psycopg2 +matplotlib +geopandas +sphinx +sphinx-book-theme \ No newline at end of file diff --git a/pymeos/docs/pymeos.aggregators.rst b/docs/src/api/pymeos.aggregators.rst similarity index 100% rename from pymeos/docs/pymeos.aggregators.rst rename to docs/src/api/pymeos.aggregators.rst diff --git a/pymeos/docs/pymeos.boxes.rst b/docs/src/api/pymeos.boxes.rst similarity index 100% rename from pymeos/docs/pymeos.boxes.rst rename to docs/src/api/pymeos.boxes.rst diff --git a/docs/src/api/pymeos.collections.base.rst b/docs/src/api/pymeos.collections.base.rst new file mode 100644 index 00000000..b5fb3c04 --- /dev/null +++ b/docs/src/api/pymeos.collections.base.rst @@ -0,0 +1,26 @@ +Base Abstract Collections +=================== + +Set +------------------------------- + +.. automodule:: pymeos.collections.base.set + :members: + :undoc-members: + :show-inheritance: + +Span +------------------------- + +.. automodule:: pymeos.collections.base.span + :members: + :undoc-members: + :show-inheritance: + +SpanSet +---------------------------- + +.. automodule:: pymeos.collections.base.spanset + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/api/pymeos.collections.numeric.rst b/docs/src/api/pymeos.collections.numeric.rst new file mode 100644 index 00000000..b4eadd0b --- /dev/null +++ b/docs/src/api/pymeos.collections.numeric.rst @@ -0,0 +1,53 @@ +Numeric Collections +=================== + +IntSet +----------------------- + +.. automodule:: pymeos.collections.number.intset + :members: + :undoc-members: + :show-inheritance: + + +FloatSet +----------------------- + +.. automodule:: pymeos.collections.number.floatset + :members: + :undoc-members: + :show-inheritance: + +IntSpan +----------------------- + +.. automodule:: pymeos.collections.number.intspan + :members: + :undoc-members: + :show-inheritance: + + +FloatSpan +----------------------- + +.. automodule:: pymeos.collections.number.floatspan + :members: + :undoc-members: + :show-inheritance: + +IntSpanSet +----------------------- + +.. automodule:: pymeos.collections.number.intspanset + :members: + :undoc-members: + :show-inheritance: + + +FloatSpanSet +----------------------- + +.. automodule:: pymeos.collections.number.floatspanset + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/api/pymeos.collections.rst b/docs/src/api/pymeos.collections.rst new file mode 100644 index 00000000..9e6ac529 --- /dev/null +++ b/docs/src/api/pymeos.collections.rst @@ -0,0 +1,13 @@ +Collections +=================== + + +.. toctree:: + :maxdepth: 2 + + pymeos.collections.base + pymeos.collections.time + pymeos.collections.spatial + pymeos.collections.numeric + pymeos.collections.text + diff --git a/docs/src/api/pymeos.collections.spatial.rst b/docs/src/api/pymeos.collections.spatial.rst new file mode 100644 index 00000000..f66cf3d0 --- /dev/null +++ b/docs/src/api/pymeos.collections.spatial.rst @@ -0,0 +1,29 @@ +Spatial Collections +=================== + + +GeoSet +----------------------- + +.. autoclass:: pymeos.collections.geo.geoset.GeoSet + :members: + :undoc-members: + :show-inheritance: + + +GeometrySet +----------------------- + +.. autoclass:: pymeos.collections.geo.geoset.GeometrySet + :members: + :undoc-members: + :show-inheritance: + + +GeographySet +----------------------- + +.. autoclass:: pymeos.collections.geo.geoset.GeographySet + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/api/pymeos.collections.text.rst b/docs/src/api/pymeos.collections.text.rst new file mode 100644 index 00000000..c975e7cc --- /dev/null +++ b/docs/src/api/pymeos.collections.text.rst @@ -0,0 +1,10 @@ +Text Collections +=================== + +TextSet +------------------------------- + +.. automodule:: pymeos.collections.text.textset + :members: + :undoc-members: + :show-inheritance: diff --git a/pymeos/docs/pymeos.time.rst b/docs/src/api/pymeos.collections.time.rst similarity index 64% rename from pymeos/docs/pymeos.time.rst rename to docs/src/api/pymeos.collections.time.rst index 3a7b8e58..0bdcf7fa 100644 --- a/pymeos/docs/pymeos.time.rst +++ b/docs/src/api/pymeos.collections.time.rst @@ -1,34 +1,34 @@ -Time +Time Collections =================== Time ----------------------- -.. automodule:: pymeos.time.time +.. automodule:: pymeos.collections.time.time :members: :undoc-members: :show-inheritance: -Period -------------------------- +TimestampSet +------------------------------- -.. automodule:: pymeos.time.period +.. automodule:: pymeos.collections.time.timestampset :members: :undoc-members: :show-inheritance: -PeriodSet ----------------------------- +Period +------------------------- -.. automodule:: pymeos.time.periodset +.. automodule:: pymeos.collections.time.period :members: :undoc-members: :show-inheritance: -TimestampSet -------------------------------- +PeriodSet +---------------------------- -.. automodule:: pymeos.time.timestampset +.. automodule:: pymeos.collections.time.periodset :members: :undoc-members: :show-inheritance: diff --git a/pymeos/docs/pymeos.db.rst b/docs/src/api/pymeos.db.rst similarity index 100% rename from pymeos/docs/pymeos.db.rst rename to docs/src/api/pymeos.db.rst diff --git a/pymeos/docs/pymeos.main.rst b/docs/src/api/pymeos.main.rst similarity index 100% rename from pymeos/docs/pymeos.main.rst rename to docs/src/api/pymeos.main.rst diff --git a/pymeos/docs/pymeos.meos_init.rst b/docs/src/api/pymeos.meos_init.rst similarity index 100% rename from pymeos/docs/pymeos.meos_init.rst rename to docs/src/api/pymeos.meos_init.rst diff --git a/pymeos/docs/pymeos.plotters.rst b/docs/src/api/pymeos.plotters.rst similarity index 100% rename from pymeos/docs/pymeos.plotters.rst rename to docs/src/api/pymeos.plotters.rst diff --git a/pymeos/docs/pymeos.temporal.rst b/docs/src/api/pymeos.temporal.rst similarity index 100% rename from pymeos/docs/pymeos.temporal.rst rename to docs/src/api/pymeos.temporal.rst diff --git a/docs/src/examples.rst b/docs/src/examples.rst new file mode 100644 index 00000000..74ca9e77 --- /dev/null +++ b/docs/src/examples.rst @@ -0,0 +1,6 @@ +Examples +==================== + +Section under construction. However, you can check the existing examples available in the +`PyMEOS repository `__ and the +`PyMEOS-Demo repository `__. \ No newline at end of file diff --git a/docs/src/installation.rst b/docs/src/installation.rst new file mode 100644 index 00000000..f32f6777 --- /dev/null +++ b/docs/src/installation.rst @@ -0,0 +1,80 @@ +Installation +============ + +Built distributions +------------------- + +Built distributions don't require compiling PyMEOS, PyMEOS CFFI or MEOS, +and can be installed using ``pip``. + +Installation from PyPI +^^^^^^^^^^^^^^^^^^^^^^ + +PyMEOS and PyMEOS CFFI are available as binary distributions (wheel) for Linux platforms on +`PyPI `__. The distributions include the most recent version of MEOS available at the +time of the PyMEOS release. Install the binary wheel with pip as follows:: + + $ pip install pymeos + +Installation using conda (incoming, not available yet) +^^^^^^^^^^^^^^^^^^^^^^^^ + +PyMEOS is available on the conda-forge channel. Install as follows:: + + $ conda install pymeos --channel conda-forge + + +Installation from source with custom MEOS library +------------------------------------------------ + +If you want to use a specific MEOS version or a MEOS distribution that is +already present on your system, you can compile the PyMEOS packages from source yourself, +by directing pip to ignore the binary wheels. + +Note that only PyMEOS CFFI will need to be compiled from sources, +since PyMEOS is a pure Python package and doesn't interact with MEOS directly. + +First, make sure that MEOS is installed in your system. You can install it following the instructions +in the `MEOS documentation `__. + +Then, install PyMEOS CFFI from source:: + + $ pip install pymeos-cffi --no-binary pymeos-cffi + $ pip install pymeos + + +Updating the PyMEOS CFFI wrapper for custom MEOS library +^^^^^^^^^^^^^^^^^^^^^^^^ + +If your MEOS library API doesn't match the one used by the PyMEOS CFFI wrapper, it will crash. You can fix this +by updating the header file used by PyMEOS CFFI to match your MEOS version. To do so, you will need to recompile it +using the builder scripts provided in the ``pymeos_cffi`` package. + +First, you will need to get the source code of PyMEOS CFFI. You can do so by downloading the source distribution +from PyPI, or by cloning the repository from GitHub:: + + $ git clone git@github.com:MobilityDB/PyMEOS.git + $ cd PyMEOS/pymeos_cffi + +Then, you will need to run the header builder script, which will generate a new header file based on the MEOS +version installed in your system. The script accepts two parameters, a path to the MEOS header file, and a path to your +MEOS library:: + + $ python3 ./pymeos_cffi/builder/build_header.py + +If no parameters are passed, the script will use the default header file and library path:: + + $ python3 ./pymeos_cffi/builder/build_header.py /usr/local/include/meos.h /usr/local/lib/libmeos.so + +The second parameter is optional and is used to remove any function defined in the header file not exposed by the +library. If omitted, this step will not be performed. + +Then, you have to generate the PyMEOS CFFI wrapper functions using the functions builder script:: + + $ python3 ./pymeos_cffi/builder/build_pymeos_functions.py + +This will update the ``functions.py`` file that contains all the python functions exposed by the library. + +Finally, you can install the updated PyMEOS CFFI package:: + + $ pip install . diff --git a/docs/src/manual.rst b/docs/src/manual.rst new file mode 100644 index 00000000..dbf763c3 --- /dev/null +++ b/docs/src/manual.rst @@ -0,0 +1,4 @@ +User Manual +==================== + +Section under construction \ No newline at end of file diff --git a/pymeos/docs/index.rst b/pymeos/docs/index.rst deleted file mode 100644 index e7afd067..00000000 --- a/pymeos/docs/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. PyMEOS documentation master file, created by - sphinx-quickstart on Fri Feb 10 11:27:17 2023. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to PyMEOS's documentation! -================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - pymeos - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/pymeos/docs/pymeos.rst b/pymeos/docs/pymeos.rst deleted file mode 100644 index 6cc0ea25..00000000 --- a/pymeos/docs/pymeos.rst +++ /dev/null @@ -1,15 +0,0 @@ -PyMEOS package -============== - -.. toctree:: - :maxdepth: 4 - - pymeos.meos_init - pymeos.time - pymeos.temporal - pymeos.main - pymeos.boxes - pymeos.aggregators - pymeos.plotters - pymeos.db - diff --git a/pymeos/pymeos/__init__.py b/pymeos/pymeos/__init__.py index 02043095..d5593ee3 100644 --- a/pymeos/pymeos/__init__.py +++ b/pymeos/pymeos/__init__.py @@ -3,7 +3,30 @@ from .main import * from .meos_init import * from .temporal import * -from .time import * +from .collections import * +from pymeos_cffi import \ + MeosException, \ + MeosInternalError, \ + MeosArgumentError, \ + MeosIoError, \ + MeosInternalTypeError, \ + MeosValueOutOfRangeError, \ + MeosDivisionByZeroError, \ + MeosMemoryAllocError, \ + MeosAggregationError, \ + MeosDirectoryError, \ + MeosFileError, \ + MeosInvalidArgError, \ + MeosInvalidArgTypeError, \ + MeosInvalidArgValueError, \ + MeosMfJsonInputError, \ + MeosMfJsonOutputError, \ + MeosTextInputError, \ + MeosTextOutputError, \ + MeosWkbInputError, \ + MeosWkbOutputError, \ + MeosGeoJsonInputError, \ + MeosGeoJsonOutputError __version__ = '1.1.3a1' __all__ = [ @@ -21,8 +44,12 @@ 'TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', 'TGeogPointSeqSet', # temporal 'Temporal', 'TInstant', 'TSequence', 'TSequenceSet', - # time + # Collections 'Time', 'Period', 'TimestampSet', 'PeriodSet', + 'TextSet', + 'IntSet', 'IntSpan', 'IntSpanSet', + 'FloatSet', 'FloatSpan', 'FloatSpanSet', + 'GeoSet', 'GeometrySet', 'GeographySet', # extras 'TInterpolation', # aggregators @@ -35,4 +62,27 @@ 'TemporalTextMaxAggregator', 'TemporalTextMinAggregator', 'TemporalPointExtentAggregator', 'TimeInstantaneousUnionAggregator', 'TimeContinuousUnionAggregator', + # exceptions + 'MeosException', + 'MeosInternalError', + 'MeosArgumentError', + 'MeosIoError', + 'MeosInternalTypeError', + 'MeosValueOutOfRangeError', + 'MeosDivisionByZeroError', + 'MeosMemoryAllocError', + 'MeosAggregationError', + 'MeosDirectoryError', + 'MeosFileError', + 'MeosInvalidArgError', + 'MeosInvalidArgTypeError', + 'MeosInvalidArgValueError', + 'MeosMfJsonInputError', + 'MeosMfJsonOutputError', + 'MeosTextInputError', + 'MeosTextOutputError', + 'MeosWkbInputError', + 'MeosWkbOutputError', + 'MeosGeoJsonInputError', + 'MeosGeoJsonOutputError', ] diff --git a/pymeos/pymeos/aggregators/aggregator.py b/pymeos/pymeos/aggregators/aggregator.py index 5d9f3d67..9f208018 100644 --- a/pymeos/pymeos/aggregators/aggregator.py +++ b/pymeos/pymeos/aggregators/aggregator.py @@ -9,7 +9,7 @@ from ..boxes import Box from ..factory import _TemporalFactory from ..temporal import Temporal -from ..time import Time +from ..collections import Time ResultType = TypeVar('ResultType', bound=Union[Temporal, Time, Box]) SourceType = TypeVar('SourceType', bound=Union[Temporal, Time, Box]) diff --git a/pymeos/pymeos/aggregators/general_aggregators.py b/pymeos/pymeos/aggregators/general_aggregators.py index 6bede171..80c68c29 100644 --- a/pymeos/pymeos/aggregators/general_aggregators.py +++ b/pymeos/pymeos/aggregators/general_aggregators.py @@ -7,7 +7,7 @@ from ..boxes import Box from ..main import TIntSeq, TIntSeqSet from ..temporal import Temporal, TInterpolation -from ..time import Time, TimestampSet, Period, PeriodSet +from ..collections import Time, TimestampSet, Period, PeriodSet class TemporalInstantCountAggregator(BaseAggregator[ diff --git a/pymeos/pymeos/aggregators/time_aggregators.py b/pymeos/pymeos/aggregators/time_aggregators.py index 27093dd5..6e8336b7 100644 --- a/pymeos/pymeos/aggregators/time_aggregators.py +++ b/pymeos/pymeos/aggregators/time_aggregators.py @@ -4,7 +4,7 @@ from pymeos_cffi import * from .aggregator import BaseAggregator -from ..time import TimestampSet, Period, PeriodSet +from ..collections import TimestampSet, Period, PeriodSet class TimeInstantaneousUnionAggregator(BaseAggregator[Union[datetime, TimestampSet], TimestampSet]): diff --git a/pymeos/pymeos/boxes/stbox.py b/pymeos/pymeos/boxes/stbox.py index ad4bbe48..80c7508e 100644 --- a/pymeos/pymeos/boxes/stbox.py +++ b/pymeos/pymeos/boxes/stbox.py @@ -2,15 +2,12 @@ from typing import Optional, Union, List, TYPE_CHECKING, get_args -import postgis as pg import shapely.geometry.base as shp from pymeos_cffi import * from ..main import TPoint from ..temporal import Temporal -from ..time import * - -Geometry = Union[pg.Geometry, shp.BaseGeometry] +from ..collections import * if TYPE_CHECKING: from .box import Box @@ -18,7 +15,8 @@ class STBox: """ - Class for representing a spatio-temporal box. Temporal bounds may be inclusive or exclusive. + Class for representing a spatio-temporal box. Temporal bounds may be inclusive or + exclusive. ``STBox`` objects can be created with a single argument of type string as in MobilityDB. @@ -39,10 +37,13 @@ class STBox: """ __slots__ = ['_inner'] - def _get_box(self, other: Union[Geometry, STBox, Temporal, Time], allow_space_only: bool = True, + _mobilitydb_name = 'stbox' + + def _get_box(self, other: Union[shp.BaseGeometry, STBox, Temporal, Time], + allow_space_only: bool = True, allow_time_only: bool = False) -> STBox: - if allow_space_only and isinstance(other, get_args(Geometry)): - other_box = geo_to_stbox(geometry_to_gserialized(other, self.geodetic())) + if allow_space_only and isinstance(other, shp.BaseGeometry): + other_box = geo_to_stbox(geo_to_gserialized(other, self.geodetic())) elif isinstance(other, STBox): other_box = other._inner elif isinstance(other, TPoint): @@ -63,23 +64,24 @@ def _get_box(self, other: Union[Geometry, STBox, Temporal, Time], allow_space_on # ------------------------- Constructors ---------------------------------- def __init__(self, string: Optional[str] = None, *, - xmin: Optional[Union[str, float]] = None, + xmin: Optional[Union[str, float]] = None, xmax: Optional[Union[str, float]] = None, - ymin: Optional[Union[str, float]] = None, + ymin: Optional[Union[str, float]] = None, ymax: Optional[Union[str, float]] = None, - zmin: Optional[Union[str, float]] = None, + zmin: Optional[Union[str, float]] = None, zmax: Optional[Union[str, float]] = None, - tmin: Optional[Union[str, datetime]] = None, + tmin: Optional[Union[str, datetime]] = None, tmax: Optional[Union[str, datetime]] = None, tmin_inc: bool = True, tmax_inc: bool = True, geodetic: bool = False, srid: Optional[int] = None, _inner=None): assert (_inner is not None) or (string is not None) != ( - (xmin is not None and xmax is not None and ymin is not None and ymax is not None) or + (xmin is not None and xmax is not None and ymin is not None and + ymax is not None) or (tmin is not None and tmax is not None)), \ - "Either string must be not None or at least a bound pair (xmin/max and ymin/max, or tmin/max)" \ - " must be not None" + "Either string must be not None or at least a bound pair (xmin/max" \ + " and ymin/max, or tmin/max) must be not None" if _inner is not None: self._inner = _inner @@ -88,12 +90,16 @@ def __init__(self, string: Optional[str] = None, *, else: period = None hast = tmin is not None and tmax is not None - hasx = xmin is not None and xmax is not None and ymin is not None and ymax is not None + hasx = xmin is not None and xmax is not None and ymin is not None \ + and ymax is not None hasz = zmin is not None and zmax is not None if hast: - period = Period(lower=tmin, upper=tmax, lower_inc=tmin_inc, upper_inc=tmax_inc)._inner - self._inner = stbox_make(hasx, hasz, geodetic, srid or 0, float(xmin or 0), float(xmax or 0), - float(ymin or 0), float(ymax or 0), float(zmin or 0), float(zmax or 0), period) + period = Period(lower=tmin, upper=tmax, lower_inc=tmin_inc, + upper_inc=tmax_inc)._inner + self._inner = stbox_make(hasx, hasz, geodetic, srid or 0, + float(xmin or 0), float(xmax or 0), + float(ymin or 0), float(ymax or 0), + float(zmin or 0), float(zmax or 0), period) def __copy__(self) -> STBox: """ @@ -143,21 +149,21 @@ def from_hexwkb(hexwkb: str) -> STBox: return STBox(_inner=result) @staticmethod - def from_geometry(geom: Geometry, geodetic: bool = False) -> STBox: + def from_geometry(geom: shp.BaseGeometry, geodetic: bool = False) -> STBox: """ - Returns a `STBox` from a `Geometry`. + Returns a `STBox` from a `shp.BaseGeometry`. Args: - geom: A `Geometry` instance. + geom: A `shp.BaseGeometry` instance. geodetic: Whether to create a geodetic or geometric `STBox`. Returns: A new :class:`STBox` instance. MEOS Functions: - gserialized_in, geo_to_stbox + pgis_geometry_in, geo_to_stbox """ - gs = geometry_to_gserialized(geom, geodetic) + gs = geo_to_gserialized(geom, geodetic) return STBox(_inner=geo_to_stbox(gs)) @staticmethod @@ -172,7 +178,8 @@ def from_time(time: Time) -> STBox: A new :class:`STBox` instance. MEOS Functions: - timestamp_to_stbox, timestampset_to_stbox, period_to_stbox, periodset_to_stbox + timestamp_to_stbox, timestampset_to_stbox, period_to_stbox, + periodset_to_stbox """ if isinstance(time, datetime): result = timestamp_to_stbox(datetime_to_timestamptz(time)) @@ -187,13 +194,13 @@ def from_time(time: Time) -> STBox: return STBox(_inner=result) @staticmethod - def from_geometry_time(geometry: Geometry, time: Union[datetime, Period], + def from_geometry_time(geometry: shp.BaseGeometry, time: Union[datetime, Period], geodetic: bool = False) -> STBox: """ Returns a `STBox` from a space and time dimension. Args: - geometry: A `Geometry` instance representing the space dimension. + geometry: A `shp.BaseGeometry` instance representing the space dimension. time: A `Time` instance representing the time dimension. geodetic: Whether to create a geodetic or geometric `STBox`. @@ -203,13 +210,14 @@ def from_geometry_time(geometry: Geometry, time: Union[datetime, Period], MEOS Functions: geo_timestamp_to_stbox, geo_period_to_stbox """ - gs = geometry_to_gserialized(geometry, geodetic) + gs = geo_to_gserialized(geometry, geodetic) if isinstance(time, datetime): result = geo_timestamp_to_stbox(gs, datetime_to_timestamptz(time)) elif isinstance(time, Period): result = geo_period_to_stbox(gs, time._inner) else: - raise TypeError(f'Operation not supported with types {geometry.__class__} and {time.__class__}') + raise TypeError(f'Operation not supported with types ' \ + '{geometry.__class__} and {time.__class__}') return STBox(_inner=result) @staticmethod @@ -229,16 +237,17 @@ def from_tpoint(temporal: TPoint) -> STBox: return STBox(_inner=tpoint_to_stbox(temporal._inner)) @staticmethod - def from_expanding_bounding_box(value: Union[Geometry, TPoint, STBox], + def from_expanding_bounding_box(value: Union[shp.BaseGeometry, TPoint, STBox], expansion: float, geodetic: Optional[bool] = False) -> STBox: """ - Returns a `STBox` from a `Geometry`, `TPoint` or `STBox` instance, expanding its bounding box by the given amount. + Returns a `STBox` from a `shp.BaseGeometry`, `TPoint` or `STBox` instance, + expanding its bounding box by the given amount. Args: - value: A `Geometry`, `TPoint` or `STBox` instance. + value: A `shp.BaseGeometry`, `TPoint` or `STBox` instance. expansion: The amount to expand the bounding box. - geodetic: Whether to create a geodetic or geometric `STBox`. - Only used when value is a `Geometry` instance. + geodetic: Whether to create a geodetic or geometric `STBox`. + Only used when value is a `shp.BaseGeometry` instance. Returns: A new :class:`STBox` instance. @@ -246,8 +255,8 @@ def from_expanding_bounding_box(value: Union[Geometry, TPoint, STBox], MEOS Functions: geo_expand_space, tpoint_expand_space, stbox_expand_space """ - if isinstance(value, get_args(Geometry)): - gs = geometry_to_gserialized(value, geodetic) + if isinstance(value, shp.BaseGeometry): + gs = geo_to_gserialized(value, geodetic) result = geo_expand_space(gs, expansion) elif isinstance(value, TPoint): result = tpoint_expand_space(value._inner, expansion) @@ -300,7 +309,8 @@ def as_hexwkb(self) -> str: Returns the WKB representation of ``self`` in hex-encoded ASCII. Returns: - A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. + A :class:`str` object with the WKB representation of ``self`` in + hex-encoded ASCII. MEOS Functions: stbox_as_hexwkb @@ -310,7 +320,8 @@ def as_hexwkb(self) -> str: # ------------------------- Conversions ---------------------------------- def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: """ - Returns the spatial dimension of ``self`` as a `shapely` :class:`~shapely.BaseGeometry` instance. + Returns the spatial dimension of ``self`` as a `shapely` + :class:`~shapely.BaseGeometry` instance. Args: precision: The precision of the geometry coordinates. @@ -321,7 +332,8 @@ def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: MEOS Functions: stbox_to_geo """ - return gserialized_to_shapely_geometry(stbox_to_geo(self._inner), precision) + return gserialized_to_shapely_geometry(stbox_to_geo(self._inner), + precision) def to_period(self) -> Period: """ @@ -385,7 +397,7 @@ def geodetic(self) -> bool: """ return stbox_isgeodetic(self._inner) - def xmin(self) -> float: + def xmin(self) -> Optional[float]: """ Returns the minimum X coordinate of ``self``. @@ -397,7 +409,7 @@ def xmin(self) -> float: """ return stbox_xmin(self._inner) - def ymin(self) -> float: + def ymin(self) -> Optional[float]: """ Returns the minimum Y coordinate of ``self``. @@ -409,7 +421,7 @@ def ymin(self) -> float: """ return stbox_ymin(self._inner) - def zmin(self) -> float: + def zmin(self) -> Optional[float]: """ Returns the minimum Z coordinate of ``self``. @@ -421,7 +433,7 @@ def zmin(self) -> float: """ return stbox_zmin(self._inner) - def tmin(self) -> datetime: + def tmin(self) -> Optional[datetime]: """ Returns the starting time of ``self``. @@ -432,23 +444,22 @@ def tmin(self) -> datetime: stbox_tmin """ result = stbox_tmin(self._inner) - if not result: - return None - return timestamptz_to_datetime(result) + return timestamptz_to_datetime(result) if result else None def tmin_inc(self) -> bool: """ Returns whether starting time of ``self`` is inclusive or not Returns: - True if the starting time of ``self`` is inclusive and False otherwise + True if the starting time of ``self`` is inclusive and False + otherwise MEOS Functions: stbox_tmin_inc """ return stbox_tmin_inc(self._inner) - def xmax(self) -> float: + def xmax(self) -> Optional[float]: """ Returns the maximum X coordinate of ``self``. @@ -460,7 +471,7 @@ def xmax(self) -> float: """ return stbox_xmax(self._inner) - def ymax(self) -> float: + def ymax(self) -> Optional[float]: """ Returns the maximum Y coordinate of ``self``. @@ -472,7 +483,7 @@ def ymax(self) -> float: """ return stbox_ymax(self._inner) - def zmax(self) -> float: + def zmax(self) -> Optional[float]: """ Returns the maximum Z coordinate of ``self``. @@ -484,7 +495,7 @@ def zmax(self) -> float: """ return stbox_zmax(self._inner) - def tmax(self) -> datetime: + def tmax(self) -> Optional[datetime]: """ Returns the ending time of ``self``. @@ -495,9 +506,7 @@ def tmax(self) -> datetime: stbox_tmax """ result = stbox_tmax(self._inner) - if not result: - return None - return timestamptz_to_datetime(result) + return timestamptz_to_datetime(result) if result else None def tmax_inc(self) -> bool: """ @@ -540,12 +549,28 @@ def set_srid(self, value: int) -> STBox: return STBox(_inner=stbox_set_srid(self._inner, value)) # ------------------------- Transformations ------------------------------- - def expand(self, other: Union[int, float, timedelta, STBox]) -> STBox: + def get_space(self) -> STBox: + """ + Get the spatial dimension of ``self``, removing the temporal dimension + if any + + Returns: + A new :class:`STBox` instance. + + MEOS Functions: + stbox_get_space + """ + result = stbox_get_space(self._inner) + return STBox(_inner=result) + + def expand(self, other: Union[int, float, timedelta]) -> STBox: """ Expands ``self`` with `other`. - If `other` is a :class:`int` or a :class:`float`, the result is equal to ``self`` but with the spatial dimensions - expanded by `other` in all directions. If `other` is a :class:`timedelta`, the result is equal to ``self`` - but with the temporal dimension expanded by `other` in both directions. + If `other` is a :class:`int` or a :class:`float`, the result is equal + to ``self`` but with the spatial dimensions expanded by `other` in all + directions. If `other` is a :class:`timedelta`, the result is equal to + ``self`` but with the temporal dimension expanded by `other` in both + directions. Args: other: The object to expand ``self`` with. @@ -559,15 +584,13 @@ def expand(self, other: Union[int, float, timedelta, STBox]) -> STBox: if isinstance(other, int) or isinstance(other, float): result = stbox_expand_space(self._inner, float(other)) elif isinstance(other, timedelta): - result = stbox_expand_time(self._inner, timedelta_to_interval(other)) - elif isinstance(other, STBox): - result = stbox_copy(self._inner) - stbox_expand(other._inner, result) + result = stbox_expand_time(self._inner, + timedelta_to_interval(other)) else: raise TypeError(f'Operation not supported with type {other.__class__}') return STBox(_inner=result) - def shift(self, delta: timedelta) -> STBox: + def shift_time(self, delta: timedelta) -> STBox: """ Returns a new `STBox` with the time dimension shifted by `delta`. @@ -578,16 +601,17 @@ def shift(self, delta: timedelta) -> STBox: A new :class:`STBox` instance MEOS Functions: - periodshift_tscale + stbox_shift_scale_time See Also: - :meth:`Period.shift` + :meth:`STBox.shift` """ - return self.shift_tscale(shift=delta) + return self.shift_scale_time(shift=delta) - def tscale(self, duration: timedelta) -> STBox: + def scale_time(self, duration: timedelta) -> STBox: """ - Returns a new `STBox` with the time dimension having duration `duration`. + Returns a new `STBox` with the time dimension having duration + `duration`. Args: duration: :class:`datetime.timedelta` instance with new duration @@ -596,17 +620,18 @@ def tscale(self, duration: timedelta) -> STBox: A new :class:`STBox` instance MEOS Functions: - period_shift_tscale + period_shift_scale See Also: - :meth:`Period.tscale` + :meth:`STBox.scale` """ - return self.shift_tscale(duration=duration) + return self.shift_scale_time(duration=duration) - def shift_tscale(self, shift: Optional[timedelta] = None, + def shift_scale_time(self, shift: Optional[timedelta] = None, duration: Optional[timedelta] = None) -> STBox: """ - Returns a new `STBox` with the time dimension shifted by `shift` and with duration `duration`. + Returns a new `STBox` with the time dimension shifted by `shift` and + with duration `duration`. Args: shift: :class:`datetime.timedelta` instance to shift @@ -616,27 +641,26 @@ def shift_tscale(self, shift: Optional[timedelta] = None, A new :class:`STBox` instance MEOS Functions: - period_shift_tscale + stbox_shift_scale_time See Also: - :meth:`Period.shift_tscale` + :meth:`Period.shift_scale` """ - assert shift is not None or duration is not None, 'shift and scale deltas must not be both None' - new_inner = stbox_copy(self._inner) - new_period = get_address(new_inner.period) - period_shift_tscale( - new_period, + assert shift is not None or duration is not None, \ + 'shift and scale deltas must not be both None' + result = stbox_shift_scale_time( + self._inner, timedelta_to_interval(shift) if shift else None, timedelta_to_interval(duration) if duration else None ) - return STBox(_inner=new_inner) + return STBox(_inner=result) - def round(self, maxdd : int = 0) -> STBox: + def round(self, max_decimals : Optional[int] = 0) -> STBox: """ Returns `self` rounded to the given number of decimal digits. Args: - maxdd: Maximum number of decimal digits. + max_decimals: Maximum number of decimal digits. Returns: A new :class:`STBox` instance @@ -645,16 +669,17 @@ def round(self, maxdd : int = 0) -> STBox: stbox_round """ new_inner = stbox_copy(self._inner) - stbox_round(new_inner, maxdd) + stbox_round(new_inner, max_decimals) return STBox(_inner=new_inner) # ------------------------- Set Operations -------------------------------- - def union(self, other: STBox, strict: Optional[bool] = True) -> STBox: + def union(self, other: STBox, strict: Optional[bool] = False) -> STBox: """ - Returns the union of `self` with `other`. Fails if the union is not contiguous. + Returns the union of `self` with `other`. Args: other: spatiotemporal box to merge with + strict: Whether to fail if the boxes do not intersect. Returns: A :class:`STBox` instance. @@ -666,7 +691,8 @@ def union(self, other: STBox, strict: Optional[bool] = True) -> STBox: def __add__(self, other): """ - Returns the union of `self` with `other`. Fails if the union is not contiguous. + Returns the union of `self` with `other`. Fails if the union is not + contiguous. Args: other: spatiotemporal box to merge with @@ -677,7 +703,7 @@ def __add__(self, other): MEOS Functions: union_stbox_stbox """ - return self.union(other) + return self.union(other, False) # TODO: Check returning None for empty intersection is the desired behaviour def intersection(self, other: STBox) -> Optional[STBox]: @@ -688,7 +714,8 @@ def intersection(self, other: STBox) -> Optional[STBox]: other: temporal object to merge with Returns: - A :class:`STBox` instance if the instersection is not empty, `None` otherwise. + A :class:`STBox` instance if the instersection is not empty, `None` + otherwise. MEOS Functions: intersection_stbox_stbox @@ -704,7 +731,8 @@ def __mul__(self, other): other: temporal object to merge with Returns: - A :class:`STBox` instance if the instersection is not empty, `None` otherwise. + A :class:`STBox` instance if the instersection is not empty, `None` + otherwise. MEOS Functions: intersection_stbox_stbox @@ -712,14 +740,16 @@ def __mul__(self, other): return self.intersection(other) # ------------------------- Topological Operations ------------------------ - def is_adjacent(self, other: Union[Geometry, STBox, Temporal, Time]) -> bool: + def is_adjacent(self, other: Union[shp.BaseGeometry, STBox, Temporal, Time]) -> bool: """ - Returns whether ``self`` and `other` are adjacent. Two spatiotemporal boxes are adjacent if they share n - dimensions and the intersection is of at most n-1 dimensions. Note that for `TPoint` instances, the bounding box - of the temporal point is used. + Returns whether ``self`` and `other` are adjacent. Two spatiotemporal + boxes are adjacent if they share n dimensions and the intersection is + of at most n-1 dimensions. Note that for `TPoint` instances, the + bounding box of the temporal point is used. Args: - other: The other spatiotemporal object to check adjacency with ``self``. + other: The other spatiotemporal object to check adjacency with + ``self``. Returns: ``True`` if ``self`` and `other` are adjacent, ``False`` otherwise. @@ -727,15 +757,18 @@ def is_adjacent(self, other: Union[Geometry, STBox, Temporal, Time]) -> bool: MEOS Functions: adjacent_stbox_stbox """ - return adjacent_stbox_stbox(self._inner, self._get_box(other, allow_time_only=True)) + return adjacent_stbox_stbox(self._inner, self._get_box(other, + allow_time_only=True)) - def is_contained_in(self, container: Union[Geometry, STBox, Temporal, Time]) -> bool: + def is_contained_in(self, + container: Union[shp.BaseGeometry, STBox, Temporal, Time]) -> bool: """ - Returns whether ``self`` is contained in `container`. Note that for `TPoint` instances, the bounding - box of the temporal point is used. + Returns whether ``self`` is contained in `container`. Note that for + `TPoint` instances, the bounding box of the temporal point is used. Args: - container: The spatiotemporal object to check containment with ``self``. + container: The spatiotemporal object to check containment with + ``self``. Returns: ``True`` if ``self`` is contained in `container`, ``False`` otherwise. @@ -743,15 +776,17 @@ def is_contained_in(self, container: Union[Geometry, STBox, Temporal, Time]) -> MEOS Functions: contained_stbox_stbox """ - return contained_stbox_stbox(self._inner, self._get_box(container, allow_time_only=True)) + return contained_stbox_stbox(self._inner, self._get_box(container, + allow_time_only=True)) - def contains(self, content: Union[Geometry, STBox, Temporal, Time]) -> bool: + def contains(self, content: Union[shp.BaseGeometry, STBox, Temporal, Time]) -> bool: """ - Returns whether ``self`` contains `content`. Note that for `TPoint` instances, the bounding box of - the temporal point is used. + Returns whether ``self`` contains `content`. Note that for `TPoint` + instances, the bounding box of the temporal point is used. Args: - content: The spatiotemporal object to check containment with ``self``. + content: The spatiotemporal object to check containment with + ``self``. Returns: ``True`` if ``self`` contains `content`, ``False`` otherwise. @@ -759,14 +794,16 @@ def contains(self, content: Union[Geometry, STBox, Temporal, Time]) -> bool: MEOS Functions: contains_stbox_stbox """ - return contains_stbox_stbox(self._inner, self._get_box(content, allow_time_only=True)) + return contains_stbox_stbox(self._inner, self._get_box(content, + allow_time_only=True)) def __contains__(self, item): """ Returns whether ``self`` contains `item`. Args: - item: The spatiotemporal object to check if it is contained in ``self``. + item: The spatiotemporal object to check if it is contained in + ``self``. Returns: ``True`` if ``self`` contains ``item``, ``False`` otherwise. @@ -779,10 +816,10 @@ def __contains__(self, item): """ return self.contains(item) - def overlaps(self, other: Union[Geometry, STBox, Temporal, Time]) -> bool: + def overlaps(self, other: Union[shp.BaseGeometry, STBox, Temporal, Time]) -> bool: """ - Returns whether ``self`` overlaps `other`. Note that for `TPoint` instances, the bounding box of - the temporal point is used. + Returns whether ``self`` overlaps `other`. Note that for `TPoint` + instances, the bounding box of the temporal point is used. Args: other: The spatiotemporal object to check overlap with ``self``. @@ -793,12 +830,13 @@ def overlaps(self, other: Union[Geometry, STBox, Temporal, Time]) -> bool: MEOS Functions: overlaps_stbox_stbox """ - return overlaps_stbox_stbox(self._inner, self._get_box(other, allow_time_only=True)) + return overlaps_stbox_stbox(self._inner, self._get_box(other, + allow_time_only=True)) - def is_same(self, other: Union[Geometry, STBox, Temporal, Time]) -> bool: + def is_same(self, other: Union[shp.BaseGeometry, STBox, Temporal, Time]) -> bool: """ - Returns whether ``self`` is the same as `other`. Note that for `TPoint` instances, the bounding box of - the temporal point is used. + Returns whether ``self`` is the same as `other`. Note that for `TPoint` + instances, the bounding box of the temporal point is used. Args: other: The spatiotemporal object to check equality with ``self``. @@ -809,189 +847,213 @@ def is_same(self, other: Union[Geometry, STBox, Temporal, Time]) -> bool: MEOS Functions: same_stbox_stbox """ - return same_stbox_stbox(self._inner, self._get_box(other, allow_time_only=True)) + return same_stbox_stbox(self._inner, self._get_box(other, + allow_time_only=True)) # ------------------------- Position Operations --------------------------- - def is_left(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_left(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is strictly to the left of `other`. Checks the X dimension. + Returns whether ``self`` is strictly to the left of `other`. Checks + the X dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is strictly to the left of `other`, ``False`` otherwise. + ``True`` if ``self`` is strictly to the left of `other`, + ``False`` otherwise. MEOS Functions: left_stbox_stbox """ return left_stbox_stbox(self._inner, self._get_box(other)) - def is_over_or_left(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_over_or_left(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is to the left `other` allowing for overlap. That is, ``self`` does not extend - to the right of `other`. Checks the X dimension. + Returns whether ``self`` is to the left `other` allowing for overlap. + That is, ``self`` does not extend to the right of `other`. Checks the + X dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is to the left of `other` allowing for overlap, ``False`` otherwise. + ``True`` if ``self`` is to the left of `other` allowing for + overlap, ``False`` otherwise. MEOS Functions: overleft_stbox_stbox, tpoint_to_stbox """ return overleft_stbox_stbox(self._inner, self._get_box(other)) - def is_right(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_right(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is strictly to the right of `other`. Checks the X dimension. + Returns whether ``self`` is strictly to the right of `other`. + Checks the X dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is strictly to the right of `other`, ``False`` otherwise. + ``True`` if ``self`` is strictly to the right of `other`, + ``False`` otherwise. MEOS Functions: right_stbox_stbox """ return right_stbox_stbox(self._inner, self._get_box(other)) - def is_over_or_right(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_over_or_right(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is to the right of `other` allowing for overlap. That is, ``self`` does not - extend to the left of `other`. Checks the X dimension. + Returns whether ``self`` is to the right of `other` allowing for + overlap. That is, ``self`` does not extend to the left of `other`. + Checks the X dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is to the right of `other` allowing for overlap, ``False`` otherwise. + ``True`` if ``self`` is to the right of `other` allowing for + overlap, ``False`` otherwise. MEOS Functions: overright_stbox_stbox """ return overright_stbox_stbox(self._inner, self._get_box(other)) - def is_below(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_below(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is strictly below `other`. Checks the Y dimension. + Returns whether ``self`` is strictly below `other`. + Checks the Y dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is strictly below `other`, ``False`` otherwise. + ``True`` if ``self`` is strictly below `other`, ``False`` + otherwise. MEOS Functions: below_stbox_stbox """ return below_stbox_stbox(self._inner, self._get_box(other)) - def is_over_or_below(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_over_or_below(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is below `other` allowing for overlap. That is, ``self`` does not extend - above `other`. Checks the Y dimension. + Returns whether ``self`` is below `other` allowing for overlap. + That is, ``self`` does not extend above `other`. Checks the Y dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is below `other` allowing for overlap, ``False`` otherwise. + ``True`` if ``self`` is below `other` allowing for overlap, + ``False`` otherwise. MEOS Functions: overbelow_stbox_stbox """ return overbelow_stbox_stbox(self._inner, self._get_box(other)) - def is_above(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_above(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is strictly above `other`. Checks the Y dimension. + Returns whether ``self`` is strictly above `other`. + Checks the Y dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is strictly above `other`, ``False`` otherwise. + ``True`` if ``self`` is strictly above `other`, ``False`` + otherwise. MEOS Functions: above_stbox_stbox """ return above_stbox_stbox(self._inner, self._get_box(other)) - def is_over_or_above(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_over_or_above(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is above `other` allowing for overlap. That is, ``self`` does not extend - below `other`. Checks the Y dimension. + Returns whether ``self`` is above `other` allowing for overlap. + That is, ``self`` does not extend below `other`. + Checks the Y dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is above `other` allowing for overlap, ``False`` otherwise. + ``True`` if ``self`` is above `other` allowing for overlap, + ``False`` otherwise. MEOS Functions: overabove_stbox_stbox """ return overabove_stbox_stbox(self._inner, self._get_box(other)) - def is_front(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_front(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is strictly in front of `other`. Checks the Z dimension. + Returns whether ``self`` is strictly in front of `other`. + Checks the Z dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is strictly in front of `other`, ``False`` otherwise. + ``True`` if ``self`` is strictly in front of `other`, ``False`` + otherwise. MEOS Functions: front_stbox_stbox """ return front_stbox_stbox(self._inner, self._get_box(other)) - def is_over_or_front(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_over_or_front(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is in front of `other` allowing for overlap. That is, ``self`` does not extend - behind `other`. Checks the Z dimension. + Returns whether ``self`` is in front of `other` allowing for overlap. + That is, ``self`` does not extend behind `other`. + Checks the Z dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is in front of `other` allowing for overlap, ``False`` otherwise. + ``True`` if ``self`` is in front of `other` allowing for overlap, + ``False`` otherwise. MEOS Functions: overfront_stbox_stbox """ return overfront_stbox_stbox(self._inner, self._get_box(other)) - def is_behind(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_behind(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is strictly behind `other`. Checks the Z dimension. + Returns whether ``self`` is strictly behind `other`. + Checks the Z dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is strictly behind `other`, ``False`` otherwise. + ``True`` if ``self`` is strictly behind `other`, ``False`` + otherwise. MEOS Functions: back_stbox_stbox """ return back_stbox_stbox(self._inner, self._get_box(other)) - def is_over_or_behind(self, other: Union[Geometry, STBox, TPoint]) -> bool: + def is_over_or_behind(self, other: Union[shp.BaseGeometry, STBox, TPoint]) -> bool: """ - Returns whether ``self`` is behind `other` allowing for overlap. That is, ``self`` does not extend - in front of `other`. Checks the Z dimension. + Returns whether ``self`` is behind `other` allowing for overlap. + That is, ``self`` does not extend in front of `other`. + Checks the Z dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is behind `other` allowing for overlap, ``False`` otherwise. + ``True`` if ``self`` is behind `other` allowing for overlap, + ``False`` otherwise. MEOS Functions: overback_stbox_stbox @@ -1000,56 +1062,65 @@ def is_over_or_behind(self, other: Union[Geometry, STBox, TPoint]) -> bool: def is_before(self, other: Union[Box, Temporal, Time]) -> bool: """ - Returns whether ``self`` is strictly before `other`. Checks the time dimension. + Returns whether ``self`` is strictly before `other`. + Checks the time dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is strictly before `other`, ``False`` otherwise. + ``True`` if ``self`` is strictly before `other`, ``False`` + otherwise. """ return self.to_period().is_before(other) def is_over_or_before(self, other: Union[Box, Temporal, Time]) -> bool: """ - Returns whether ``self`` is before `other` allowing for overlap. That is, ``self`` does not extend - after `other`. Checks the time dimension. + Returns whether ``self`` is before `other` allowing for overlap. + That is, ``self`` does not extend after `other`. + Checks the time dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is before `other` allowing for overlap, ``False`` otherwise. + ``True`` if ``self`` is before `other` allowing for overlap, + ``False`` otherwise. """ return self.to_period().is_over_or_before(other) def is_after(self, other: Union[Box, Temporal, Time]) -> bool: """ - Returns whether ``self`` is strictly after `other`. Checks the time dimension. + Returns whether ``self`` is strictly after `other`. + Checks the time dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is strictly after `other`, ``False`` otherwise. + ``True`` if ``self`` is strictly after `other`, ``False`` + otherwise. """ return self.to_period().is_after(other) def is_over_or_after(self, other: Union[Box, Temporal, Time]) -> bool: """ - Returns whether ``self`` is after `other` allowing for overlap. That is, ``self`` does not extend - before `other`. Checks the time dimension. + Returns whether ``self`` is after `other` allowing for overlap. + That is, ``self`` does not extend before `other`. + Checks the time dimension. Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is after `other` allowing for overlap, ``False`` otherwise. + ``True`` if ``self`` is after `other` allowing for overlap, + ``False`` otherwise. """ return self.to_period().is_over_or_after(other) # ------------------------- Distance Operations --------------------------- - def nearest_approach_distance(self, other: Union[Geometry, STBox, TPoint]) -> float: + def nearest_approach_distance(self, other: Union[shp.BaseGeometry, STBox, TPoint]) \ + -> float: """ Returns the distance between the nearest points of ``self`` and `other`. @@ -1057,13 +1128,14 @@ def nearest_approach_distance(self, other: Union[Geometry, STBox, TPoint]) -> fl other: The spatiotemporal object to compare with ``self``. Returns: - A :class:`float` with the distance between the nearest points of ``self`` and ``other``. + A :class:`float` with the distance between the nearest points of + ``self`` and ``other``. MEOS Functions: nad_stbox_geo, nad_stbox_stbox """ - if isinstance(other, get_args(Geometry)): - gs = geometry_to_gserialized(other, self.geodetic()) + if isinstance(other, shp.BaseGeometry): + gs = geo_to_gserialized(other, self.geodetic()) return nad_stbox_geo(self._inner, gs) elif isinstance(other, STBox): return nad_stbox_stbox(self._inner, other._inner) @@ -1075,10 +1147,11 @@ def nearest_approach_distance(self, other: Union[Geometry, STBox, TPoint]) -> fl # ------------------------- Splitting -------------------------------------- def quad_split_flat(self) -> List[STBox]: """ - Returns a list of 4 (or 8 if `self`has Z dimension) :class:`STBox` instances resulting from the quad - split of ``self``. + Returns a list of 4 (or 8 if `self`has Z dimension) :class:`STBox` + instances resulting from the quad split of ``self``. - Indices of returned array are as follows (back only present if Z dimension is present): + Indices of returned array are as follows (back only present if Z + dimension is present): >>> # (front) (back) >>> # ------------- ------------- @@ -1098,7 +1171,8 @@ def quad_split_flat(self) -> List[STBox]: def quad_split(self) -> Union[List[List[STBox]], List[List[List[STBox]]]]: """ - Returns a 2D (YxX) or 3D (ZxYxX) list of :class:`STBox` instances resulting from the quad split of ``self``. + Returns a 2D (YxX) or 3D (ZxYxX) list of :class:`STBox` instances + resulting from the quad split of ``self``. Indices of returned array are as follows: @@ -1129,28 +1203,37 @@ def quad_split(self) -> Union[List[List[STBox]], List[List[List[STBox]]]]: boxes, count = stbox_quad_split(self._inner) if self.has_z(): return [ - [[STBox(_inner=boxes + i) for i in range(2)], [STBox(_inner=boxes + i) for i in range(2, 4)]], - [[STBox(_inner=boxes + i) for i in range(4, 6)], [STBox(_inner=boxes + i) for i in range(6, 8)]] + [[STBox(_inner=boxes + i) for i in range(2)], + [STBox(_inner=boxes + i) for i in range(2, 4)]], + [[STBox(_inner=boxes + i) for i in range(4, 6)], + [STBox(_inner=boxes + i) for i in range(6, 8)]] ] else: - return [[STBox(_inner=boxes + i) for i in range(2)], [STBox(_inner=boxes + i) for i in range(2, 4)]] + return [[STBox(_inner=boxes + i) for i in range(2)], + [STBox(_inner=boxes + i) for i in range(2, 4)]] - def tile(self, size: Optional[float] = None, duration: Optional[Union[timedelta, str]] = None, - origin: Optional[Geometry] = None, - start: Union[datetime, str, None] = None) -> List[List[List[List[STBox]]]]: + def tile(self, size: Optional[float] = None, + duration: Optional[Union[timedelta, str]] = None, + origin: Optional[shp.BaseGeometry] = None, + start: Union[datetime, str, None] = None) -> \ + List[List[List[List[STBox]]]]: """ - Returns a 4D matrix (XxYxZxT) of `STBox` instances representing the tiles of ``self``. - The resulting matrix has 4 dimensions regardless of the dimensionality of ``self``. If the ``self`` - is missing a dimension, the resulting matrix will have a size of 1 for that dimension. + Returns a 4D matrix (XxYxZxT) of `STBox` instances representing the + tiles of ``self``. The resulting matrix has 4 dimensions regardless + of the dimensionality of ``self``. If the ``self`` is missing a + dimension, the resulting matrix will have a size of 1 for that dimension. Args: - size: The size of the spatial tiles. If the `STBox` instance has a spatial dimension and this - argument is not provided, the tiling will be only temporal. - duration: The duration of the temporal tiles. If the `STBox` instance has a time dimension and this - argument is not provided, the tiling will be only spatial. - origin: The origin of the spatial tiling. If not provided, the origin will be (0, 0, 0). - start: The start time of the temporal tiling. If not provided, the start time will be the starting time of - the `STBox` time dimension. + size: The size of the spatial tiles. If the `STBox` instance has a + spatial dimension and this argument is not provided, the tiling + will be only temporal. + duration: The duration of the temporal tiles. If the `STBox` + instance has a time dimension and this argument is not + provided, the tiling will be only spatial. + origin: The origin of the spatial tiling. If not provided, the + origin will be (0, 0, 0). + start: The start time of the temporal tiling. If not provided, + the start time used by default is Monday, January 3, 2000. Returns: A 4D matrix (XxYxZxT) of `STBox` instances. @@ -1165,10 +1248,11 @@ def tile(self, size: Optional[float] = None, duration: Optional[Union[timedelta, else None st = datetime_to_timestamptz(start) if isinstance(start, datetime) \ else pg_timestamptz_in(start, -1) if isinstance(start, str) \ - else datetime_to_timestamptz(self.tmin()) if self.has_t() \ + else pg_timestamptz_in('2000-01-03', -1) if self.has_t() \ else 0 - gs = geometry_to_gserialized(origin) if origin is not None \ - else gserialized_in('Point(0 0 0)', -1) + gs = geo_to_gserialized(origin, self.geodetic()) if origin is not None \ + else pgis_geography_in('Point(0 0 0)', -1) if self.geodetic() \ + else pgis_geometry_in('Point(0 0 0)', -1) tiles, dimensions = stbox_tile_list(self._inner, sz, sz, sz, dt, gs, st) x_size = dimensions[0] or 1 y_size = dimensions[1] or 1 @@ -1177,23 +1261,29 @@ def tile(self, size: Optional[float] = None, duration: Optional[Union[timedelta, x_factor = y_size * z_size * t_size y_factor = z_size * t_size z_factor = t_size - return [[[[STBox(_inner=tiles + x * x_factor + y * y_factor + z * z_factor + t) for t in range(t_size)] - for z in range(z_size)] for y in range(y_size)] for x in range(x_size)] + return [[[[STBox(_inner=tiles + x * x_factor + y * y_factor + z * z_factor + t) + for t in range(t_size)] + for z in range(z_size)] for y in range(y_size)] for x in range(x_size)] - def tile_flat(self, size: float, duration: Optional[Union[timedelta, str]] = None, - origin: Optional[Geometry] = None, + def tile_flat(self, size: float, + duration: Optional[Union[timedelta, str]] = None, + origin: Optional[shp.BaseGeometry] = None, start: Union[datetime, str, None] = None) -> List[STBox]: """ - Returns a flat list of `STBox` instances representing the tiles of ``self``. + Returns a flat list of `STBox` instances representing the tiles of + ``self``. Args: - size: The size of the spatial tiles. If the `STBox` instance has a spatial dimension and this - argument is not provided, the tiling will be only temporal. - duration: The duration of the temporal tiles. If the `STBox` instance has a time dimension and this - argument is not provided, the tiling will be only spatial. - origin: The origin of the spatial tiling. If not provided, the origin will be (0, 0, 0). - start: The start time of the temporal tiling. If not provided, the start time will be the starting time of - the `STBox` time dimension. + size: The size of the spatial tiles. If the `STBox` instance has a + spatial dimension and this argument is not provided, the tiling + will be only temporal. + duration: The duration of the temporal tiles. If the `STBox` + instance has a time dimension and this argument is not + provided, the tiling will be only spatial. + origin: The origin of the spatial tiling. If not provided, the + origin will be (0, 0, 0). + start: The start time of the temporal tiling. If not provided, the + start time used by default is Monday, January 3, 2000. Returns: A flat list of `STBox` instances. @@ -1246,8 +1336,9 @@ def __ne__(self, other): def __lt__(self, other): """ - Returns whether ``self`` is less than `other`. Compares first the SRID, then the time dimension, - and finally the spatial dimension (X, then Y then Z lower bounds and then the upper bounds). + Returns whether ``self`` is less than `other`. Compares first the SRID, + then the time dimension, and finally the spatial dimension (X, then Y + then Z lower bounds and then the upper bounds). Args: other: The spatiotemporal object to compare with ``self``. @@ -1264,14 +1355,16 @@ def __lt__(self, other): def __le__(self, other): """ - Returns whether ``self`` is less than or equal to `other`. Compares first the SRID, then the time dimension, - and finally the spatial dimension (X, then Y then Z lower bounds and then the upper bounds). + Returns whether ``self`` is less than or equal to `other`. Compares + first the SRID, then the time dimension, and finally the spatial + dimension (X, then Y then Z lower bounds and then the upper bounds). Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is less than or equal to ``other``, ``False`` otherwise. + ``True`` if ``self`` is less than or equal to ``other``, ``False`` + otherwise. MEOS Functions: stbox_le @@ -1282,8 +1375,9 @@ def __le__(self, other): def __gt__(self, other): """ - Returns whether ``self`` is greater than `other`. Compares first the SRID, then the time dimension, - and finally the spatial dimension (X, then Y then Z lower bounds and then the upper bounds). + Returns whether ``self`` is greater than `other`. Compares first the + SRID, then the time dimension, and finally the spatial dimension (X, + then Y then Z lower bounds and then the upper bounds). Args: other: The spatiotemporal object to compare with ``self``. @@ -1300,14 +1394,16 @@ def __gt__(self, other): def __ge__(self, other): """ - Returns whether ``self`` is greater than or equal to `other`. Compares first the SRID, then the time dimension, - and finally the spatial dimension (X, then Y then Z lower bounds and then the upper bounds). + Returns whether ``self`` is greater than or equal to `other`. Compares + first the SRID, then the time dimension, and finally the spatial + dimension (X, then Y then Z lower bounds and then the upper bounds). Args: other: The spatiotemporal object to compare with ``self``. Returns: - ``True`` if ``self`` is greater than or equal to ``other``, ``False`` otherwise. + ``True`` if ``self`` is greater than or equal to ``other``, + ``False`` otherwise. MEOS Functions: stbox_ge @@ -1329,7 +1425,8 @@ def plot_xy(self, *args, **kwargs): def plot_xt(self, *args, **kwargs): """ - Plots the first spatial dimension and the temporal dimension (XT) of ``self``. + Plots the first spatial dimension and the temporal dimension (XT) of + ``self``. See Also: :func:`~pymeos.plotters.box_plotter.BoxPlotter.plot_stbox_xt` @@ -1339,7 +1436,8 @@ def plot_xt(self, *args, **kwargs): def plot_yt(self, *args, **kwargs): """ - Plots the second spatial dimension and the temporal dimension (YT) of ``self``. + Plots the second spatial dimension and the temporal dimension (YT) of + ``self``. See Also: :func:`~pymeos.plotters.box_plotter.BoxPlotter.plot_stbox_yt` @@ -1351,7 +1449,8 @@ def plot_yt(self, *args, **kwargs): @staticmethod def read_from_cursor(value, _=None): """ - Reads a :class:`STBox` from a database cursor. Used when automatically loading objects from the database. + Reads a :class:`STBox` from a database cursor. Used when automatically + loading objects from the database. Users should use the class constructor instead. """ if not value: diff --git a/pymeos/pymeos/boxes/tbox.py b/pymeos/pymeos/boxes/tbox.py index 49f9eb74..b9e8d7fa 100644 --- a/pymeos/pymeos/boxes/tbox.py +++ b/pymeos/pymeos/boxes/tbox.py @@ -3,17 +3,18 @@ from typing import Optional, Union, List from pymeos_cffi import * -from spans import intrange, floatrange from ..main import TNumber -from ..time import * +from ..collections import * class TBox: """ - Class for representing numeric temporal boxes. Both numeric and temporal bounds may be inclusive or not. + Class for representing numeric temporal boxes. Both numeric and temporal + bounds may be inclusive or not. - ``TBox`` objects can be created with a single argument of type string as in MobilityDB. + ``TBox`` objects can be created with a single argument of type string as in + MobilityDB. >>> TBox('TBOXINT XT([0, 10),[2020-06-01, 2020-06-05])') >>> TBox('TBOXFLOAT XT([0, 10),[2020-06-01, 2020-06-05])') @@ -28,12 +29,15 @@ class TBox: >>> TBox(xmin=0, xmax=10, tmin='2020-06-01', tmax='2020-06-0', xmax_inc=True, tmax_inc=True) >>> TBox(xmin='0', xmax='10', tmin=parse('2020-06-01'), tmax=parse('2020-06-0')) - Note that you can create a TBox with only the numerical or the temporal dimension. In these cases, it will be - equivalent to a :class:`~pymeos.time.period.Period` (if it only has temporal dimension) or to a - :class:`~spans.floatrange` (if it only has the numeric dimension). + Note that you can create a TBox with only the numerical or the temporal + dimension. In these cases, it will be equivalent to a + :class:`~pymeos.time.period.Period` (if it only has temporal dimension) or + to a :class:`FloatSpan` (if it only has the numeric dimension). """ __slots__ = ['_inner'] + _mobilitydb_name = 'tbox' + def _inner_period(self): from pymeos_cffi.functions import _ffi return _ffi.addressof(self._inner.period) @@ -42,19 +46,23 @@ def _inner_span(self): from pymeos_cffi.functions import _ffi return _ffi.addressof(self._inner.span) + def _is_float(self) -> bool: + return self._inner.span.basetype == 5 + # ------------------------- Constructors ---------------------------------- def __init__(self, string: Optional[str] = None, *, xmin: Optional[Union[str, int, float]] = None, xmax: Optional[Union[str, int, float]] = None, tmin: Optional[Union[str, datetime]] = None, tmax: Optional[Union[str, datetime]] = None, - xmin_inc: Optional[bool] = True, - xmax_inc: Optional[bool] = False, - tmin_inc: Optional[bool] = True, - tmax_inc: Optional[bool] = False, + xmin_inc: bool = True, + xmax_inc: bool = False, + tmin_inc: bool = True, + tmax_inc: bool = False, _inner=None): assert (_inner is not None) or (string is not None) != ( - (xmin is not None and xmax is not None) or (tmin is not None and tmax is not None)), \ + (xmin is not None and xmax is not None) or + (tmin is not None and tmax is not None)), \ "Either string must be not None or at least a bound pair (xmin/max or tmin/max) must be not None" if _inner is not None: self._inner = _inner @@ -67,9 +75,11 @@ def __init__(self, string: Optional[str] = None, *, if isinstance(xmin, int) and isinstance(xmax, int): span = intspan_make(xmin, xmax, xmin_inc, xmax_inc) else: - span = floatspan_make(float(xmin), float(xmax), xmin_inc, xmax_inc) + span = floatspan_make(float(xmin), float(xmax), xmin_inc, + xmax_inc) if tmin is not None and tmax is not None: - period = Period(lower=tmin, upper=tmax, lower_inc=tmin_inc, upper_inc=tmax_inc)._inner + period = Period(lower=tmin, upper=tmax, lower_inc=tmin_inc, + upper_inc=tmax_inc)._inner self._inner = tbox_make(span, period) def __copy__(self) -> TBox: @@ -120,10 +130,10 @@ def from_hexwkb(hexwkb: str) -> TBox: return TBox(_inner=result) @staticmethod - def from_value(value: Union[int, float, intrange, floatrange]) -> TBox: + def from_value(value: Union[int, float, IntSpan, FloatSpan]) -> TBox: """ - Returns a `TBox` from a numeric value or range. The created `TBox` will only have a numerical - dimension. + Returns a `TBox` from a numeric value or span. The created `TBox` will + only have a numerical dimension. Args: value: value to be canverted into a TBox @@ -138,10 +148,10 @@ def from_value(value: Union[int, float, intrange, floatrange]) -> TBox: result = int_to_tbox(value) elif isinstance(value, float): result = float_to_tbox(value) - elif isinstance(value, intrange): - result = numspan_to_tbox(intrange_to_intspan(value)) - elif isinstance(value, floatrange): - result = numspan_to_tbox(floatrange_to_floatspan(value)) + elif isinstance(value, IntSpan): + result = numspan_to_tbox(value._inner) + elif isinstance(value, FloatSpan): + result = numspan_to_tbox(value._inner) else: raise TypeError(f'Operation not supported with type {value.__class__}') return TBox(_inner=result) @@ -149,8 +159,8 @@ def from_value(value: Union[int, float, intrange, floatrange]) -> TBox: @staticmethod def from_time(time: Time) -> TBox: """ - Returns a `TBox` from a :class:`~pymeos.time.time.Time` object. The created `TBox` - will only have a temporal dimension. + Returns a `TBox` from a :class:`~pymeos.time.time.Time` object. The + created `TBox` will only have a temporal dimension. Args: time: value to be canverted into a TBox @@ -159,23 +169,23 @@ def from_time(time: Time) -> TBox: A new :class:`TBox` instance MEOS Functions: - timestamp_to_tbox, timestampset_to_tbox, period_to_tbox, periodset_to_tbox + timestamp_to_tbox, timestampset_to_tbox, period_to_tbox, + periodset_to_tbox """ if isinstance(time, datetime): - result = (datetime, datetime) + result = timestamp_to_tbox(datetime_to_timestamptz(time)) elif isinstance(time, TimestampSet): - result = (time.start_timestamp(), time.end_timestamp()) + result = timestampset_to_tbox(time._inner) elif isinstance(time, Period): - result = (time.lower, time.upper, time.lower_inc, time.upper_inc) + result = period_to_tbox(time._inner) elif isinstance(time, PeriodSet): - result = (time.start_period().lower, time.end_period().upper, - time.start_period().lower_inc, time.end_period().upper_inc) + result = periodset_to_tbox(time._inner) else: raise TypeError(f'Operation not supported with type {time.__class__}') return TBox(_inner=result) @staticmethod - def from_value_time(value: Union[int, float, intrange, floatrange], + def from_value_time(value: Union[int, float, IntSpan, FloatSpan], time: Union[datetime, Period]) -> TBox: """ Returns a `TBox` from a numerical and a temporal object. @@ -188,25 +198,30 @@ def from_value_time(value: Union[int, float, intrange, floatrange], A new :class:`TBox` instance MEOS Functions: - int_timestamp_to_tbox, int_period_to_tbox, float_timestamp_to_tbox, float_period_to_tbox, - span_timestamp_to_tbox, span_period_to_tbox + int_timestamp_to_tbox, int_period_to_tbox, + float_timestamp_to_tbox, float_period_to_tbox, + span_timestamp_to_tbox, span_period_to_tbox """ if isinstance(value, int) and isinstance(time, datetime): - result = int_timestamp_to_tbox(value, datetime_to_timestamptz(time)) + result = int_timestamp_to_tbox(value, + datetime_to_timestamptz(time)) elif isinstance(value, int) and isinstance(time, Period): - result = int_period_to_tbox(value, time) + result = int_period_to_tbox(value, time._inner) elif isinstance(value, float) and isinstance(time, datetime): - result = float_timestamp_to_tbox(value, datetime_to_timestamptz(time)) + result = float_timestamp_to_tbox(value, + datetime_to_timestamptz(time)) elif isinstance(value, float) and isinstance(time, Period): - result = float_period_to_tbox(value, time) - elif isinstance(value, intrange) and isinstance(time, datetime): - result = span_timestamp_to_tbox(intrange_to_intspan(value), datetime_to_timestamptz(time)) - elif isinstance(value, intrange) and isinstance(time, Period): - result = span_period_to_tbox(intrange_to_intspan(value), time) - elif isinstance(value, floatrange) and isinstance(time, datetime): - result = span_timestamp_to_tbox(floatrange_to_floatspan(value), datetime_to_timestamptz(time)) - elif isinstance(value, floatrange) and isinstance(time, Period): - result = span_period_to_tbox(floatrange_to_floatspan(value), time) + result = float_period_to_tbox(value, time._inner) + elif isinstance(value, IntSpan) and isinstance(time, datetime): + result = span_timestamp_to_tbox(value._inner, + datetime_to_timestamptz(time)) + elif isinstance(value, IntSpan) and isinstance(time, Period): + result = span_period_to_tbox(value._inner, time._inner) + elif isinstance(value, FloatSpan) and isinstance(time, datetime): + result = span_timestamp_to_tbox(value._inner, + datetime_to_timestamptz(time)) + elif isinstance(value, FloatSpan) and isinstance(time, Period): + result = span_period_to_tbox(value._inner, time._inner) else: raise TypeError(f'Operation not supported with types {value.__class__} and {time.__class__}') return TBox(_inner=result) @@ -240,7 +255,7 @@ def __str__(self, max_decimals: int = 15): """ return tbox_out(self._inner, max_decimals) - def __repr__(self, max_decimals=15): + def __repr__(self): """ Returns a string representation of ``self``. @@ -253,7 +268,7 @@ def __repr__(self, max_decimals=15): return (f'{self.__class__.__name__}' f'({self})') - def as_wkb(self) -> str: + def as_wkb(self) -> bytes: """ Returns the WKB representation of ``self``. @@ -270,7 +285,8 @@ def as_hexwkb(self) -> str: Returns the WKB representation of ``self`` in hex-encoded ASCII. Returns: - A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. + A :class:`str` object with the WKB representation of ``self`` in + hex-encoded ASCII. MEOS Functions: tbox_as_hexwkb @@ -278,17 +294,18 @@ def as_hexwkb(self) -> str: return tbox_as_hexwkb(self._inner, -1)[0] # ------------------------- Conversions ---------------------------------- - def to_floatrange(self) -> floatrange: + def to_floatspan(self) -> FloatSpan: """ Returns the numeric span of ``self``. Returns: - A new :class:`~spans.floatrange` instance + A new :class:`FloatSpan` instance MEOS Functions: tbox_to_floatspan """ - return floatspan_to_floatrange(tbox_to_floatspan(self._inner)) + from ..collections import FloatSpan + return FloatSpan(_inner=tbox_to_floatspan(self._inner)) def to_period(self) -> Period: """ @@ -380,7 +397,8 @@ def tmin(self): Returns the temporal lower bound of ``self``. Returns: - The temporal lower bound of the `TBox` as a :class:`~datetime.datetime` + The temporal lower bound of the `TBox` as a + :class:`~datetime.datetime` MEOS Functions: tbox_tmin @@ -407,7 +425,8 @@ def tmax(self): Returns the temporal upper bound of ``self``. Returns: - The temporal upper bound of the `TBox` as a :class:`~datetime.datetime` + The temporal upper bound of the `TBox` as a + :class:`~datetime.datetime` MEOS Functions: tbox_tmax @@ -430,11 +449,11 @@ def tmax_inc(self) -> bool: return tbox_tmax_inc(self._inner) # ------------------------- Transformation -------------------------------- - def expand(self, other: Union[TBox, int, float, timedelta]) -> TBox: + def expand(self, other: Union[int, float, timedelta]) -> TBox: """ - Returns the result of expanding ``self`` with the ``other``. Depending on the type of ``other``, the expansion - will be of the numeric dimension (:class:`float`), temporal (:class:`~datetime.timedelta`) or both - (:class:`TBox`). + Returns the result of expanding ``self`` with the ``other``. Depending + on the type of ``other``, the expansion will be of the numeric + dimension (:class:`float`) or temporal (:class:`~datetime.timedelta`). Args: other: object used to expand ``self`` @@ -443,12 +462,9 @@ def expand(self, other: Union[TBox, int, float, timedelta]) -> TBox: A new :class:`TBox` instance. MEOS Functions: - tbox_copy, tbox_expand, tbox_expand_value, tbox_expand_time + tbox_expand_value, tbox_expand_time """ - if isinstance(other, TBox): - result = tbox_copy(self._inner) - tbox_expand(other._inner, result) - elif isinstance(other, int) or isinstance(other, float): + if isinstance(other, int) or isinstance(other, float): result = tbox_expand_value(self._inner, float(other)) elif isinstance(other, timedelta): result = tbox_expand_time(self._inner, timedelta_to_interval(other)) @@ -456,7 +472,25 @@ def expand(self, other: Union[TBox, int, float, timedelta]) -> TBox: raise TypeError(f'Operation not supported with type {other.__class__}') return TBox(_inner=result) - def shift(self, delta: timedelta) -> TBox: + def shift_value(self, delta: Union[int, float]) -> TBox: + """ + Returns a new `TBox` with the value dimension shifted by `delta`. + + Args: + delta: value to shift + + Returns: + A new :class:`TBox` instance + + MEOS Functions: + span_shift_scale + + See Also: + :meth:`Span.shift` + """ + return self.shift_scale_value(shift=delta) + + def shift_time(self, delta: timedelta) -> TBox: """ Returns a new `TBox` with the time dimension shifted by `delta`. @@ -467,14 +501,32 @@ def shift(self, delta: timedelta) -> TBox: A new :class:`TBox` instance MEOS Functions: - period_shift_tscale + period_shift_scale See Also: :meth:`Period.shift` """ - return self.shift_tscale(shift=delta) + return self.shift_scale_time(shift=delta) - def tscale(self, duration: timedelta) -> TBox: + def scale_value(self, width: Union[int, float]) -> TBox: + """ + Returns a new `TBox` with the value dimension having width `width`. + + Args: + width: value of the new width + + Returns: + A new :class:`TBox` instance + + MEOS Functions: + span_shift_scale + + See Also: + :meth:`Span.scale` + """ + return self.shift_scale_value(width=width) + + def scale_time(self, duration: timedelta) -> TBox: """ Returns a new `TBox` with the time dimension having duration `duration`. @@ -485,52 +537,96 @@ def tscale(self, duration: timedelta) -> TBox: A new :class:`TBox` instance MEOS Functions: - period_shift_tscale + period_shift_scale See Also: - :meth:`Period.tscale` + :meth:`Period.scale` """ - return self.shift_tscale(duration=duration) + return self.shift_scale_time(duration=duration) + + def shift_scale_value(self, shift: Optional[Union[int, float]] = None, + width: Optional[Union[int, float]] = None) -> TBox: + """ + Returns a new TBox with the value span shifted by `shift` and + width `width`. + + Examples: + >>> tbox = TBox('TBoxInt XT([0, 5),[2020-06-01, 2020-06-02])') + >>> tbox.shift_scale_value(shift=2, width=4) + >>> 'TBOXINT XT([2, 7),[2020-06-01 00:00:00+02, 2020-06-02 00:00:00+02])' + + Args: + shift: :value to shift the start of the value span + width: value representing the width of the value span + + Returns: + A new :class:`TBox` instance + + MEOS Functions: + span_shift_scale - def shift_tscale(self, shift: Optional[timedelta] = None, - duration: Optional[timedelta] = None) -> TBox: + See Also: + :meth:`Span.shift_scale` + """ + assert shift is not None or width is not None, \ + 'shift and width deltas must not be both None' + hasshift = shift is not None + haswidth = width is not None + if (shift is None or isinstance(shift, int)) and \ + (width is None or isinstance(width, int)): + result = tbox_shift_scale_int(self._inner, + shift if shift else 0, width if width else 0, + hasshift, haswidth) + elif (shift is None or isinstance(shift, float)) and \ + (width is None or isinstance(width, float)): + result = tbox_shift_scale_float(self._inner, + shift if shift else 0.0, width if width else 0.0, + hasshift, haswidth) + else: + raise TypeError(f'Operation not supported with type {self.__class__}') + return TBox(_inner=result) + + def shift_scale_time(self, shift: Optional[timedelta] = None, + duration: Optional[timedelta] = None) -> TBox: """ - Returns a new TBox with the temporal span shifted by `shift` and duration `duration`. + Returns a new TBox with the temporal span shifted by `shift` and + duration `duration`. Examples: >>> tbox = TBox('TBoxInt XT([0, 10),[2020-06-01, 2020-06-05])') - >>> tbox.shift_tscale(shift=timedelta(days=2), duration=timedelta(days=4)) + >>> tbox.shift_scale_time(shift=timedelta(days=2), duration=timedelta(days=4)) >>> 'TBOXINT XT([0, 10),[2020-06-03 00:00:00+02, 2020-06-07 00:00:00+02])' Args: - shift: :class:`datetime.timedelta` instance to shift the start of the temporal span - duration: :class:`datetime.timedelta` instance representing the duration of the temporal span + shift: :class:`datetime.timedelta` instance to shift the start of + the temporal span + duration: :class:`datetime.timedelta` instance representing the + duration of the temporal span Returns: A new :class:`TBox` instance MEOS Functions: - period_shift_tscale + period_shift_scale See Also: - :meth:`Period.shift_tscale` + :meth:`Period.shift_scale` """ - assert shift is not None or duration is not None, 'shift and duration deltas must not be both None' - new_inner = tbox_copy(self._inner) - new_period = get_address(new_inner.period) - period_shift_tscale( - new_period, + assert shift is not None or duration is not None, \ + 'shift and duration deltas must not be both None' + result = tbox_shift_scale_time( + self._inner, timedelta_to_interval(shift) if shift else None, - timedelta_to_interval(duration) if duration else None, + timedelta_to_interval(duration) if duration else None ) - return TBox(_inner=new_inner) + return TBox(_inner=result) - def round(self, maxdd : int = 0) -> STBox: + def round(self, max_decimals: int = 0) -> TBox: """ Returns `self` rounded to the given number of decimal digits. Args: - maxdd: Maximum number of decimal digits. + max_decimals: Maximum number of decimal digits. Returns: A new :class:`TBox` instance @@ -539,16 +635,17 @@ def round(self, maxdd : int = 0) -> STBox: tbox_round """ new_inner = tbox_copy(self._inner) - tbox_round(new_inner, maxdd) + tbox_round(new_inner, max_decimals) return TBox(_inner=new_inner) # ------------------------- Set Operations -------------------------------- def union(self, other: TBox, strict: Optional[bool] = True) -> TBox: """ - Returns the union of `self` with `other`. Fails if the union is not contiguous. + Returns the union of `self` with `other`. Args: other: temporal object to merge with + strict: Whether to fail if the boxes do not intersect. Returns: A :class:`TBox` instance. @@ -560,7 +657,8 @@ def union(self, other: TBox, strict: Optional[bool] = True) -> TBox: def __add__(self, other): """ - Returns the union of `self` with `other`. Fails if the union is not contiguous. + Returns the union of `self` with `other`. Fails if the union is not + contiguous. Args: other: temporal object to merge with @@ -571,7 +669,7 @@ def __add__(self, other): MEOS Functions: union_tbox_tbox """ - return self.union(other) + return self.union(other, False) # TODO: Check returning None for empty intersection is the desired behaviour def intersection(self, other: TBox) -> Optional[TBox]: @@ -582,7 +680,8 @@ def intersection(self, other: TBox) -> Optional[TBox]: other: temporal object to merge with Returns: - A :class:`TBox` instance if the instersection is not empty, `None` otherwise. + A :class:`TBox` instance if the instersection is not empty, `None` + otherwise. MEOS Functions: intersection_tbox_tbox @@ -598,7 +697,8 @@ def __mul__(self, other): other: temporal object to merge with Returns: - A :class:`TBox` instance if the instersection is not empty, `None` otherwise. + A :class:`TBox` instance if the instersection is not empty, `None` + otherwise. MEOS Functions: intersection_tbox_tbox @@ -606,10 +706,11 @@ def __mul__(self, other): return self.intersection(other) # ------------------------- Topological Operations ------------------------ - def is_adjacent(self, other: Union[int, float, intrange, floatrange, TBox, TNumber]) -> bool: + def is_adjacent(self, + other: Union[int, float, IntSpan, FloatSpan, TBox, TNumber]) -> bool: """ - Returns whether ``self`` is adjacent to ``other``. That is, they share only the temporal or numerical bound - and only one of them contains it. + Returns whether ``self`` is adjacent to ``other``. That is, they share + only the temporal or numerical bound and only one of them contains it. Examples: >>> TBox('TBoxInt XT([0, 1], [2012-01-01, 2012-01-02))').is_adjacent(TBox('TBoxInt XT([0, 1], [2012-01-02, 2012-01-03])')) @@ -629,14 +730,17 @@ def is_adjacent(self, other: Union[int, float, intrange, floatrange, TBox, TNumb adjacent_tbox_tbox, tnumber_to_tbox """ if isinstance(other, int): - return adjacent_span_span(self._inner_span(), float_to_floaspan(float(other))) + return adjacent_span_span(self._inner_span(), + float_to_floatspan(float(other))) elif isinstance(other, float): - return adjacent_span_span(self._inner_span(), float_to_floaspan(other)) - elif isinstance(other, intrange): + return adjacent_span_span(self._inner_span(), + float_to_floatspan(other)) + elif isinstance(other, IntSpan): from pymeos_cffi.functions import _ffi - return adjacent_span_span(_ffi.addressof(self._inner, 'span'), intrange_to_intspan(other)) - elif isinstance(other, floatrange): - return adjacent_span_span(self._inner.span, floatrange_to_floatspan(other)) + return adjacent_span_span(_ffi.addressof(self._inner, 'span'), + other._inner) + elif isinstance(other, FloatSpan): + return adjacent_span_span(self._inner.span, other._inner) elif isinstance(other, TBox): return adjacent_tbox_tbox(self._inner, other._inner) elif isinstance(other, TNumber): @@ -668,7 +772,8 @@ def is_contained_in(self, container: Union[TBox, TNumber]) -> bool: if isinstance(container, TBox): return contained_tbox_tbox(self._inner, container._inner) elif isinstance(container, TNumber): - return contained_tbox_tbox(self._inner, tnumber_to_tbox(container._inner)) + return contained_tbox_tbox(self._inner, + tnumber_to_tbox(container._inner)) else: raise TypeError(f'Operation not supported with type {container.__class__}') @@ -696,7 +801,8 @@ def contains(self, content: Union[TBox, TNumber]) -> bool: if isinstance(content, TBox): return contains_tbox_tbox(self._inner, content._inner) elif isinstance(content, TNumber): - return contains_tbox_tbox(self._inner, tnumber_to_tbox(content._inner)) + return contains_tbox_tbox(self._inner, + tnumber_to_tbox(content._inner)) else: raise TypeError(f'Operation not supported with type {content.__class__}') @@ -725,7 +831,8 @@ def __contains__(self, item): def overlaps(self, other: Union[TBox, TNumber]) -> bool: """ - Returns whether ``self`` overlaps ``other``. That is, both share at least an instant or a value. + Returns whether ``self`` overlaps ``other``. That is, both share at + least an instant or a value. Args: other: temporal object to compare with @@ -739,7 +846,8 @@ def overlaps(self, other: Union[TBox, TNumber]) -> bool: if isinstance(other, TBox): return overlaps_tbox_tbox(self._inner, other._inner) elif isinstance(other, TNumber): - return overlaps_tbox_tbox(self._inner, tnumber_to_tbox(other._inner)) + return overlaps_tbox_tbox(self._inner, + tnumber_to_tbox(other._inner)) else: raise TypeError(f'Operation not supported with type {other.__class__}') @@ -787,8 +895,8 @@ def is_left(self, other: Union[TBox, TNumber]) -> bool: def is_over_or_left(self, other: Union[TBox, TNumber]) -> bool: """ - Returns whether ``self`` is to the left of ``other`` allowing overlap. That is, ``self`` does not extend to the - right of ``other``. + Returns whether ``self`` is to the left of ``other`` allowing overlap. + That is, ``self`` does not extend to the right of ``other``. Args: other: temporal object to compare with @@ -828,8 +936,8 @@ def is_right(self, other: Union[TBox, TNumber]) -> bool: def is_over_or_right(self, other: Union[TBox, TNumber]) -> bool: """ - Returns whether ``self`` is to the right of ``other`` allowing overlap. That is, ``self`` does not extend to the - left of ``other``. + Returns whether ``self`` is to the right of ``other`` allowing overlap. + That is, ``self`` does not extend to the left of ``other``. Args: other: temporal object to compare with @@ -843,7 +951,8 @@ def is_over_or_right(self, other: Union[TBox, TNumber]) -> bool: if isinstance(other, TBox): return overright_tbox_tbox(self._inner, other._inner) elif isinstance(other, TNumber): - return overright_tbox_tbox(self._inner, tnumber_to_tbox(other._inner)) + return overright_tbox_tbox(self._inner, + tnumber_to_tbox(other._inner)) else: raise TypeError(f'Operation not supported with type {other.__class__}') @@ -869,8 +978,8 @@ def is_before(self, other: Union[TBox, TNumber]) -> bool: def is_over_or_before(self, other: Union[TBox, TNumber]) -> bool: """ - Returns whether ``self`` is before ``other`` allowing overlap. That is, ``self`` does not extend after - ``other``. + Returns whether ``self`` is before ``other`` allowing overlap. That is, + ``self`` does not extend after ``other``. Args: other: temporal object to compare with @@ -884,7 +993,8 @@ def is_over_or_before(self, other: Union[TBox, TNumber]) -> bool: if isinstance(other, TBox): return overbefore_tbox_tbox(self._inner, other._inner) elif isinstance(other, TNumber): - return overbefore_tbox_tbox(self._inner, tnumber_to_tbox(other._inner)) + return overbefore_tbox_tbox(self._inner, + tnumber_to_tbox(other._inner)) else: raise TypeError(f'Operation not supported with type {other.__class__}') @@ -910,8 +1020,8 @@ def is_after(self, other: Union[TBox, TNumber]) -> bool: def is_over_or_after(self, other: Union[TBox, TNumber]) -> bool: """ - Returns whether ``self`` is after ``other`` allowing overlap. That is, ``self`` does not extend before - ``other``. + Returns whether ``self`` is after ``other`` allowing overlap. That is, + ``self`` does not extend before``other``. Args: other: temporal object to compare with @@ -925,7 +1035,8 @@ def is_over_or_after(self, other: Union[TBox, TNumber]) -> bool: if isinstance(other, TBox): return overafter_tbox_tbox(self._inner, other._inner) elif isinstance(other, TNumber): - return overafter_tbox_tbox(self._inner, tnumber_to_tbox(other._inner)) + return overafter_tbox_tbox(self._inner, + tnumber_to_tbox(other._inner)) else: raise TypeError(f'Operation not supported with type {other.__class__}') @@ -938,7 +1049,8 @@ def nearest_approach_distance(self, other: Union[TBox, TNumber]) -> float: other: temporal object to compare with Returns: - A :class:`float` with the distance between the nearest points of ``self`` and ``other``. + A :class:`float` with the distance between the nearest points of + ``self`` and ``other``. MEOS Functions: nad_tbox_tbox @@ -952,7 +1064,8 @@ def nearest_approach_distance(self, other: Union[TBox, TNumber]) -> float: # ------------------------- Splitting -------------------------------------- def tile(self, size: float, duration: Union[timedelta, str], - origin: float = 0.0, start: Union[datetime, str, None] = None) -> List[List[TBox]]: + origin: float = 0.0, start: Union[datetime, str, None] = None) -> \ + List[TBox]: """ Returns 2d matrix of TBoxes resulting of tiling ``self``. @@ -960,40 +1073,47 @@ def tile(self, size: float, duration: Union[timedelta, str], size: size of the numeric dimension of the tiles duration: size of the temporal dimenstion of the tiles origin: origin of the numeric dimension of the tiles - start: origin of the temporal dimension of the tiles + start: origin of the temporal dimension of the tiles. If None, the + start time used by default is Monday, January 3, 2000. Returns: - A 2d array of :class:`TBox` instances. + An array of :class:`TBox` instances. MEOS Functions: - tbox_tile_list + tintbox_tile_list, tfloabox_tile_list """ - dt = timedelta_to_interval(duration) if isinstance(duration, timedelta) else pg_interval_in(duration, -1) + dt = timedelta_to_interval(duration) if isinstance(duration, timedelta) \ + else pg_interval_in(duration, -1) st = datetime_to_timestamptz(start) if isinstance(start, datetime) \ else pg_timestamptz_in(start, -1) if isinstance(start, str) \ - else tbox_tmin(self._inner) - tiles, rows, columns = tbox_tile_list(self._inner, size, dt, origin, st) - return [[TBox(_inner=tiles + (c * rows + r)) for c in range(columns)] for r in range(rows)] - - def tile_flat(self, size: float, duration: Union[timedelta, str], - origin: float = 0.0, start: Union[datetime, str, None] = None) -> List[TBox]: - """ - Returns an array of TBoxes resulting of tiling ``self``. - - Args: - size: size of the numeric dimension of the tiles - duration: size of the temporal dimenstion of the tiles - origin: origin of the numeric dimension of the tiles - start: origin of the temporal dimension of the tiles - - Returns: - An array of :class:`TBox` instances. - - MEOS Functions: - tbox_tile_list - """ - tiles = self.tile(size, duration, origin, start) - return [box for row in tiles for box in row] + else pg_timestamptz_in('2000-01-03', -1) + if self._is_float(): + tiles, count = tfloatbox_tile_list(self._inner, size, dt, origin, st) + else: + tiles, count = tintbox_tile_list(self._inner, int(size), dt, int(origin), st) + return [TBox(_inner=tiles + c) for c in range(count)] + + # def tile_flat(self, size: float, duration: Union[timedelta, str], + # origin: float = 0.0, + # start: Union[datetime, str, None] = None) -> List[TBox]: + # """ + # Returns an array of TBoxes resulting of tiling ``self``. + # + # Args: + # size: size of the numeric dimension of the tiles + # duration: size of the temporal dimenstion of the tiles + # origin: origin of the numeric dimension of the tiles + # start: origin of the temporal dimension of the tiles. If None, the + # start time used by default is Monday, January 3, 2000. + # + # Returns: + # An array of :class:`TBox` instances. + # + # MEOS Functions: + # tbox_tile_list + # """ + # tiles = self.tile(size, duration, origin, start) + # return [box for row in tiles for box in row] # ------------------------- Comparisons ----------------------------------- def __eq__(self, other): @@ -1032,7 +1152,8 @@ def __ne__(self, other): def __lt__(self, other): """ - Returns whether ``self`` is less than ``other``. The time dimension is compared first, then the space dimension. + Returns whether ``self`` is less than ``other``. The time + dimension is compared first, then the space dimension. Args: other: temporal object to compare with @@ -1049,8 +1170,8 @@ def __lt__(self, other): def __le__(self, other): """ - Returns whether ``self`` is less than or equal to ``other``. The time dimension is compared first, then the - space dimension. + Returns whether ``self`` is less than or equal to ``other``. The time + dimension is compared first, then the space dimension. Args: other: temporal object to compare with @@ -1067,8 +1188,8 @@ def __le__(self, other): def __gt__(self, other): """ - Returns whether ``self`` is greater than ``other``. The time dimension is compared first, then the space - dimension. + Returns whether ``self`` is greater than ``other``. The time dimension + is compared first, then the space dimension. Args: other: temporal object to compare with @@ -1085,8 +1206,8 @@ def __gt__(self, other): def __ge__(self, other): """ - Returns whether ``self`` is greater than or equal to ``other``. The time dimension is compared first, then the - space dimension. + Returns whether ``self`` is greater than or equal to ``other``. The + time dimension is compared first, then the space dimension. Args: other: temporal object to compare with @@ -1116,7 +1237,8 @@ def plot(self, *args, **kwargs): @staticmethod def read_from_cursor(value, _=None): """ - Reads a :class:`TBox` from a database cursor. Used when automatically loading objects from the database. + Reads a :class:`TBox` from a database cursor. Used when automatically + loading objects from the database. Users should use the class constructor instead. """ if not value: diff --git a/pymeos/pymeos/collections/__init__.py b/pymeos/pymeos/collections/__init__.py new file mode 100644 index 00000000..de7c1c7c --- /dev/null +++ b/pymeos/pymeos/collections/__init__.py @@ -0,0 +1,15 @@ +from .base import * +from .number import * +from .text import * +from .time import * +from .text import * +from .geo import * + +__all__ = [ + 'Set', 'Span', 'SpanSet', + 'Time', 'TimestampSet', 'Period', 'PeriodSet', 'datetime', 'timedelta', + 'TextSet', + 'GeoSet', 'GeometrySet', 'GeographySet', + 'IntSet', 'IntSpan', 'IntSpanSet', + 'FloatSet', 'FloatSpan', 'FloatSpanSet' +] diff --git a/pymeos/pymeos/collections/base/__init__.py b/pymeos/pymeos/collections/base/__init__.py new file mode 100644 index 00000000..07785cc5 --- /dev/null +++ b/pymeos/pymeos/collections/base/__init__.py @@ -0,0 +1,5 @@ +from .set import Set +from .span import Span +from .spanset import SpanSet + +__all__ = ['Set', 'Span', 'SpanSet'] diff --git a/pymeos/pymeos/collections/base/collection.py b/pymeos/pymeos/collections/base/collection.py new file mode 100644 index 00000000..dc77ce6a --- /dev/null +++ b/pymeos/pymeos/collections/base/collection.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Generic, TypeVar + +T = TypeVar('T') +Self = TypeVar('Self', bound='Set[Any]') + + +class Collection(Generic[T], ABC): + # ------------------------- Topological Operations ------------------------ + # @abstractmethod + # def is_adjacent(self, other) -> bool: + # raise NotImplementedError() + + @abstractmethod + def is_contained_in(self, container) -> bool: + raise NotImplementedError() + + @abstractmethod + def contains(self, content) -> bool: + raise NotImplementedError() + + @abstractmethod + def __contains__(self, item): + raise NotImplementedError() + + @abstractmethod + def overlaps(self, other) -> bool: + raise NotImplementedError() + + # @abstractmethod + # def is_same(self, other) -> bool: + # raise NotImplementedError() + + # ------------------------- Position Operations --------------------------- + @abstractmethod + def is_left(self, other) -> bool: + raise NotImplementedError() + + @abstractmethod + def is_over_or_left(self, other) -> bool: + raise NotImplementedError() + + @abstractmethod + def is_over_or_right(self, other) -> bool: + raise NotImplementedError() + + @abstractmethod + def is_right(self, other) -> bool: + raise NotImplementedError() + + # ------------------------- Database Operations --------------------------- + + # ------------------------- Database Operations --------------------------- + @classmethod + def read_from_cursor(cls, value, _=None): + """ + Reads a :class:`STBox` from a database cursor. Used when automatically + loading objects from the database. + Users should use the class constructor instead. + """ + if not value: + return None + return cls(string=value) diff --git a/pymeos/pymeos/collections/base/set.py b/pymeos/pymeos/collections/base/set.py new file mode 100644 index 00000000..7ed3eaf7 --- /dev/null +++ b/pymeos/pymeos/collections/base/set.py @@ -0,0 +1,618 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Optional, Union, List +from typing import TypeVar, Type, Callable, Any, TYPE_CHECKING, Iterable + +from pymeos_cffi import * + +from .collection import Collection + +if TYPE_CHECKING: + from .spanset import SpanSet + from .span import Span + +T = TypeVar('T') +Self = TypeVar('Self', bound='Set[Any]') + + +class Set(Collection[T], ABC): + """ + Base class for all set classes. + """ + + __slots__ = ['_inner'] + + _parse_function: Callable[[str], 'CData'] = None + _parse_value_function: Callable[[Union[str, T]], Any] = None + _make_function: Callable[[Iterable[Any]], 'CData'] = None + + # ------------------------- Constructors ---------------------------------- + def __init__(self, string: Optional[str] = None, *, + elements: Optional[List[Union[str, T]]] = None, + _inner=None): + super().__init__() + assert (_inner is not None) or ((string is not None) != (elements is not None)), \ + "Either string must be not None or elements must be not" + if _inner is not None: + self._inner = _inner + elif string is not None: + self._inner = self.__class__._parse_function(string) + else: + parsed_elements = [self.__class__._parse_value_function(ts) for ts in elements] + self._inner = self.__class__._make_function(parsed_elements) + + def __copy__(self: Self) -> Self: + """ + Return a copy of ``self``. + + Returns: + A new :class:`Span` instance + + MEOS Functions: + set_copy + """ + inner_copy = set_copy(self._inner) + return self.__class__(_inner=inner_copy) + + @classmethod + def from_wkb(cls: Type[Self], wkb: bytes) -> Self: + """ + Returns a `Set` from its WKB representation. + Args: + wkb: WKB representation + + Returns: + A new :class:`Set` instance + + MEOS Functions: + set_from_wkb + """ + return cls(_inner=set_from_wkb(wkb)) + + @classmethod + def from_hexwkb(cls: Type[Self], hexwkb: str) -> Self: + """ + Returns a `Set` from its WKB representation in hex-encoded ASCII. + Args: + hexwkb: WKB representation in hex-encoded ASCII + + Returns: + A new :class:`Set` instance + + MEOS Functions: + set_from_hexwkb + """ + return cls(_inner=(set_from_hexwkb(hexwkb))) + + # ------------------------- Output ---------------------------------------- + @abstractmethod + def __str__(self): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + """ + raise NotImplementedError() + + def __repr__(self): + """ + Return the string representation of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + set_out + """ + return (f'{self.__class__.__name__}' + f'({self})') + + def as_wkb(self) -> bytes: + """ + Returns the WKB representation of ``self``. + Returns: + A :class:`str` object with the WKB representation of ``self``. + + MEOS Functions: + set_as_wkb + """ + return set_as_wkb(self._inner, 4) + + def as_hexwkb(self) -> str: + """ + Returns the WKB representation of ``self`` in hex-encoded ASCII. + Returns: + A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. + + MEOS Functions: + set_as_hexwkb + """ + return set_as_hexwkb(self._inner, -1)[0] + + # ------------------------- Conversions ----------------------------------- + @abstractmethod + def to_spanset(self) -> SpanSet: + """ + Returns a SpanSet that contains a Span for each element in ``self``. + + Returns: + A new :class:`SpanSet` instance + + MEOS Functions: + set_to_spanset + """ + return set_to_spanset(self._inner) + + @abstractmethod + def to_span(self) -> Span: + """ + Returns a span that encompasses ``self``. + + Returns: + A new :class:`Span` instance + + MEOS Functions: + set_span + """ + return set_span(self._inner) + + # ------------------------- Accessors ------------------------------------- + + def num_elements(self) -> int: + """ + Returns the number of elements in ``self``. + Returns: + An :class:`int` + + MEOS Functions: + set_num_values + """ + return set_num_values(self._inner) + + def __len__(self): + """ + Returns the number of elements in ``self``. + Returns: + An :class:`int` + + MEOS Functions: + set_num_values + """ + return self.num_elements() + + @abstractmethod + def start_element(self) -> T: + """ + Returns the first element in ``self``. + Returns: + A :class:`T` instance + """ + raise NotImplementedError() + + @abstractmethod + def end_element(self) -> T: + """ + Returns the last element in ``self``. + Returns: + A :class:`T` instance + """ + raise NotImplementedError() + + @abstractmethod + def element_n(self, n: int) -> T: + """ + Returns the n-th element in ``self``. + Returns: + A :class:`T` instance + """ + if n < 0 or n >= self.num_elements(): + raise IndexError(f'Index {n} out of bounds') + + @abstractmethod + def elements(self) -> List[T]: + """ + Returns the list of distinct elements in ``self``. + Returns: + A :class:`list[T]` instance + """ + raise NotImplementedError() + + def __hash__(self) -> int: + """ + Return the hash representation of ``self``. + + Returns: + A new :class:`int` instance + + MEOS Functions: + set_hash + """ + return set_hash(self._inner) + + # ------------------------- Topological Operations ------------------------ + + def is_contained_in(self, container) -> bool: + """ + Returns whether ``self`` is contained in ``container``. + + Args: + container: object to compare with + + Returns: + True if contained, False otherwise + + MEOS Functions: + contained_span_span, contained_span_spanset, contained_set_set, + contained_spanset_spanset + """ + if isinstance(container, Set): + return contained_set_set(self._inner, container._inner) + else: + raise TypeError(f'Operation not supported with type {container.__class__}') + + @abstractmethod + def contains(self, content) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_set_set + """ + if isinstance(content, Set): + return contains_set_set(self._inner, content._inner) + else: + raise TypeError(f'Operation not supported with type {content.__class__}') + + def __contains__(self, item): + """ + Returns whether ``self`` contains ``content``. + + Args: + item: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_set_set + """ + return self.contains(item) + + def overlaps(self, other) -> bool: + """ + Returns whether ``self`` overlaps ``other``. That is, both share at least an instant + + Args: + other: object to compare with + + Returns: + True if overlaps, False otherwise + + MEOS Functions: + overlaps_set_set, overlaps_span_span, overlaps_spanset_spanset + """ + if isinstance(other, Set): + return overlaps_set_set(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + # ------------------------- Position Operations --------------------------- + def is_left(self, other) -> bool: + """ + Returns whether ``self`` is strictly to the left of ``other``. That is, + ``self`` ends before ``other`` starts. + + Args: + other: object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + left_span_span, left_span_spanset + """ + if isinstance(other, Set): + return left_set_set(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_over_or_left(self, other) -> bool: + """ + Returns whether ``self`` is to the left of ``other`` allowing overlap. + That is, ``self`` ends before ``other`` ends (or at the same value). + + Args: + other: object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + overleft_span_span, overleft_span_spanset + """ + if isinstance(other, Set): + return overleft_set_set(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_over_or_right(self, other) -> bool: + """ + Returns whether ``self`` is to the right of ``other`` allowing overlap. That is, ``self`` starts after ``other`` + starts (or at the same value). + + Args: + other: object to compare with + + Returns: + True if overlapping or to the right, False otherwise + + MEOS Functions: + overright_span_span, overright_span_spanset + """ + if isinstance(other, Set): + return overright_set_set(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_right(self, other) -> bool: + """ + Returns whether ``self`` is strictly to the right of ``other``. That is, the first element in ``self`` + is to the right ``other``. + + Args: + other: object to compare with + + Returns: + True if right, False otherwise + + MEOS Functions: + right_set_set, right_span_span, right_span_spanset + """ + if isinstance(other, Set): + return right_set_set(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + # ------------------------- Distance Operations --------------------------- + def distance(self, other) -> float: + """ + Returns the distance between ``self`` and ``other``. + + Args: + other: object to compare with + + Returns: + A :class:`float` instance + + MEOS Functions: + distance_set_set, distance_span_span, distance_spanset_span + """ + from .span import Span + from .spanset import SpanSet + if isinstance(other, Set): + return distance_set_set(self._inner, other._inner) + elif isinstance(other, Span): + return distance_span_span(set_span(self._inner), other._inner) + elif isinstance(other, SpanSet): + return distance_spanset_span(other._inner, set_span(self._inner)) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + # ------------------------- Set Operations -------------------------------- + @abstractmethod + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + A :class:`Collection` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_set_set, intersection_spanset_span, + intersection_spanset_spanset + """ + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __mul__(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + A :class:`Collection` instance. The actual class depends on ``other``. + """ + return self.intersection(other) + + @abstractmethod + def minus(self, other): + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: object to diff with + + Returns: + A :class:`Collection` instance. The actual class depends on ``other``. + """ + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __sub__(self, other): + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: object to diff with + + Returns: + A :class:`Collection` instance. The actual class depends on ``other``. + """ + return self.minus(other) + + @abstractmethod + def subtract_from(self, other): + """ + Returns the difference of ``other`` and ``self``. + + Args: + other: object to subtract ``self`` from + + Returns: + A :class:`Collection` instance or an element instance. The actual class depends on ``other``. + + See Also: + :meth:`minus` + """ + raise NotImplementedError() + + def __rsub__(self, other): + """ + Returns the difference of ``other`` and ``self``. + + Args: + other: object to subtract ``self`` from + + Returns: + A :class:`Collection` instance or an element instance. The actual class depends on ``other``. + """ + return self.subtract_from(other) + + @abstractmethod + def union(self, other): + """ + Returns the union of ``self`` and ``other``. + + Args: + other: object to merge with + + Returns: + A :class:`Collection` instance. The actual class depends on ``other``. + """ + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __add__(self, other): + """ + Returns the union of ``self`` and ``other``. + + Args: + other: object to merge with + + Returns: + A :class:`Collection` instance. The actual class depends on ``other``. + """ + return self.union(other) + + # ------------------------- Comparisons ----------------------------------- + def __eq__(self, other): + """ + Returns whether ``self`` and ``other`` are equal. + + Args: + other: object to compare with + + Returns: + True if equal, False otherwise + + MEOS Functions: + set_eq + """ + if isinstance(other, self.__class__): + return set_eq(self._inner, other._inner) + return False + + def __ne__(self, other): + """ + Returns whether ``self`` and ``other`` are not equal. + + Args: + other: object to compare with + + Returns: + True if not equal, False otherwise + + MEOS Functions: + set_ne + """ + if isinstance(other, self.__class__): + return set_ne(self._inner, other._inner) + return True + + def __lt__(self, other): + """ + Return whether ``self`` is less than ``other``. + + Args: + other: object to compare with + + Returns: + True if less than, False otherwise + + MEOS Functions: + set_lt + """ + if isinstance(other, self.__class__): + return set_lt(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __le__(self, other): + """ + Return whether ``self`` is less than or equal to ``other``. + + Args: + other: object to compare with + + Returns: + True if less than or equal, False otherwise + + MEOS Functions: + set_le + """ + if isinstance(other, self.__class__): + return set_le(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __gt__(self, other): + """ + Return whether ``self`` is greater than ``other``. + + Args: + other: object to compare with + + Returns: + True if greater than, False otherwise + + MEOS Functions: + set_gt + """ + if isinstance(other, self.__class__): + return set_gt(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __ge__(self, other): + """ + Return whether ``self`` is greater than or equal to ``other``. + + Args: + other: object to compare with + + Returns: + True if greater than or equal, False otherwise + + MEOS Functions: + set_ge + """ + if isinstance(other, self.__class__): + return set_ge(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') diff --git a/pymeos/pymeos/collections/base/span.py b/pymeos/pymeos/collections/base/span.py new file mode 100644 index 00000000..b82e2f51 --- /dev/null +++ b/pymeos/pymeos/collections/base/span.py @@ -0,0 +1,663 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Optional, Union +from typing import TypeVar, Type, Callable, Any, TYPE_CHECKING + +from pymeos_cffi import * + +from .collection import Collection + +if TYPE_CHECKING: + from .spanset import SpanSet + +T = TypeVar('T') +Self = TypeVar('Self', bound='Span[Any]') + + +class Span(Collection[T], ABC): + """ + Base class for all span classes. + """ + + __slots__ = ['_inner'] + + _parse_function: Callable[[str], 'CData'] = None + _parse_value_function: Callable[[Union[str, T]], Any] = None + _make_function: Callable[[Any, Any, bool, bool], 'CData'] = None + + # ------------------------- Constructors ---------------------------------- + def __init__(self, string: Optional[str] = None, *, + lower: Optional[Union[str, T]] = None, + upper: Optional[Union[str, T]] = None, + lower_inc: Optional[bool] = True, + upper_inc: Optional[bool] = False, + _inner=None): + super().__init__() + assert (_inner is not None) or ((string is not None) != \ + (lower is not None and upper is not None)), \ + "Either string must be not None or both lower and upper must be not" + if _inner is not None: + self._inner = _inner + elif string is not None: + self._inner = self.__class__._parse_function(string) + else: + lower_converted = self.__class__._parse_value_function(lower) + upper_converted = self.__class__._parse_value_function(upper) + self._inner = self.__class__._make_function(lower_converted, + upper_converted, lower_inc, upper_inc) + + def __copy__(self: Self) -> Self: + """ + Return a copy of ``self``. + + Returns: + A new :class:`Span` instance + + MEOS Functions: + span_copy + """ + inner_copy = span_copy(self._inner) + return self.__class__(_inner=inner_copy) + + @classmethod + def from_wkb(cls: Type[Self], wkb: bytes) -> Self: + """ + Returns a `Period` from its WKB representation. + + Args: + wkb: The WKB string. + + Returns: + A new :class:`Period` instance + + MEOS Functions: + span_from_wkb + """ + return cls(_inner=(span_from_wkb(wkb))) + + @classmethod + def from_hexwkb(cls: Type[Self], hexwkb: str) -> Self: + """ + Returns a `Period` from its WKB representation in hex-encoded ASCII. + + Args: + hexwkb: WKB representation in hex-encoded ASCII + + Returns: + A new :class:`Period` instance + + MEOS Functions: + span_from_hexwkb + """ + result = span_from_hexwkb(hexwkb) + return cls(_inner=result) + + # ------------------------- Output ---------------------------------------- + @abstractmethod + def __str__(self) -> str: + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + """ + raise NotImplementedError() + + def __repr__(self): + """ + Return the string representation of ``self``. + + Returns: + A new :class:`str` instance + """ + return (f'{self.__class__.__name__}' + f'({self})') + + def as_wkb(self) -> bytes: + """ + Returns the WKB representation of ``self``. + + Returns: + A :class:`str` object with the WKB representation of ``self``. + + MEOS Functions: + span_as_wkb + """ + return span_as_wkb(self._inner, 4) + + def as_hexwkb(self) -> str: + """ + Returns the WKB representation of ``self`` in hex-encoded ASCII. + + Returns: + A :class:`str` object with the WKB representation of ``self`` in + hex-encoded ASCII. + + MEOS Functions: + span_as_hexwkb + """ + return span_as_hexwkb(self._inner, -1)[0] + + # ------------------------- Conversions ----------------------------------- + @abstractmethod + def to_spanset(self) -> SpanSet: + """ + Returns a period set containing ``self``. + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + span_to_spanset + """ + return span_to_spanset(self._inner) + + # ------------------------- Accessors ------------------------------------- + @abstractmethod + def lower(self) -> T: + """ + Returns the lower bound of a period + """ + return NotImplementedError() + + @abstractmethod + def upper(self) -> T: + """ + Returns the upper bound of a period + """ + return NotImplementedError() + + def lower_inc(self) -> bool: + """ + Returns whether the lower bound of the period is inclusive or not + + Returns: + True if the lower bound of the period is inclusive and False + otherwise + + MEOS Functions: + span_lower_inc + """ + return span_lower_inc(self._inner) + + def upper_inc(self) -> bool: + """ + Returns whether the upper bound of the period is inclusive or not + + Returns: + True if the upper bound of the period is inclusive and False + otherwise + + MEOS Functions: + span_upper_inc + """ + return span_upper_inc(self._inner) + + def width(self) -> float: + """ + Returns the duration of the period. + + Returns: + Returns a `float` representing the duration of the period in seconds + + MEOS Functions: + span_width + """ + return span_width(self._inner) + + def __hash__(self) -> int: + """ + Return the hash representation of ``self``. + + Returns: + A new :class:`int` instance + + MEOS Functions: + span_hash + """ + return span_hash(self._inner) + + # ------------------------- Topological Operations ------------------------ + @abstractmethod + def is_adjacent(self: Self, other) -> bool: + """ + Returns whether ``self`` is adjacent to ``other``. That is, they share + a bound but only one of them contains it. + + Args: + other: object to compare with + + Returns: + True if adjacent, False otherwise + + MEOS Functions: + adjacent_span_span, adjacent_span_spanset, + """ + from .spanset import SpanSet + if isinstance(other, Span): + return adjacent_span_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return adjacent_spanset_span(other._inner, self._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_contained_in(self, container) -> bool: + """ + Returns whether ``self`` is contained in ``container``. + + Args: + container: temporal object to compare with + + Returns: + True if contained, False otherwise + + MEOS Functions: + contained_span_span, contained_span_spanset, contained_period_temporal + """ + from .spanset import SpanSet + if isinstance(container, Span): + return contained_span_span(self._inner, container._inner) + elif isinstance(container, SpanSet): + return contained_span_spanset(self._inner, container._inner) + else: + raise TypeError(f'Operation not supported with type {container.__class__}') + + def contains(self, content) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_span_span, contains_span_spanset, contains_period_timestamp, + contains_period_timestampset, contains_period_temporal + """ + from .spanset import SpanSet + if isinstance(content, Span): + return contains_span_span(self._inner, content._inner) + elif isinstance(content, SpanSet): + return contains_span_spanset(self._inner, content._inner) + else: + raise TypeError(f'Operation not supported with type {content.__class__}') + + def __contains__(self, item): + """ + Return whether ``self`` contains ``item``. + + Args: + item: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_span_span, contains_span_spanset, contains_period_timestamp, + contains_period_timestampset, contains_period_temporal + """ + return self.contains(item) + + def overlaps(self, other) -> bool: + """ + Returns whether ``self`` overlaps ``other``. That is, both share at + least an element. + + Args: + other: temporal object to compare with + + Returns: + True if overlaps, False otherwise + + MEOS Functions: + overlaps_span_span, overlaps_span_spanset + """ + from .spanset import SpanSet + if isinstance(other, Span): + return overlaps_span_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return overlaps_spanset_span(other._inner, self._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_same(self, other) -> bool: + """ + Returns whether ``self`` and the bounding period of ``other`` is the + same. + + Args: + other: temporal object to compare with + + Returns: + True if equal, False otherwise + + MEOS Functions: + same_period_temporal + """ + from .spanset import SpanSet + if isinstance(other, Span): + return span_eq(self._inner, other._inner) + elif isinstance(other, SpanSet): + return span_eq(self._inner, spanset_span(other._inner)) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + # ------------------------- Position Operations --------------------------- + def is_left(self, other) -> bool: + """ + Returns whether ``self`` is strictly before ``other``. That is, + ``self`` ends before ``other`` starts. + + Args: + other: temporal object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + left_span_span, left_span_spanset + """ + from .spanset import SpanSet + if isinstance(other, Span): + return left_span_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return left_span_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_over_or_left(self, other) -> bool: + """ + Returns whether ``self`` is before ``other`` allowing overlap. That is, + ``self`` ends before ``other`` ends (or at the same time). + + Args: + other: temporal object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + overleft_span_span, overleft_span_spanset + """ + from .spanset import SpanSet + if isinstance(other, Span): + return overleft_span_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return overleft_span_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_over_or_right(self, other) -> bool: + """ + Returns whether ``self`` is after ``other`` allowing overlap. That is, + ``self`` starts after ``other`` starts (or at the same time). + + Args: + other: temporal object to compare with + + Returns: + True if overlapping or after, False otherwise + + MEOS Functions: + overright_span_span, overright_span_spanset, overafter_period_timestamp, + overafter_period_timestampset, overafter_period_temporal + """ + from .spanset import SpanSet + if isinstance(other, Span): + return overright_span_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return overright_span_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_right(self, other) -> bool: + """ + Returns whether ``self`` is strictly after ``other``. That is, ``self`` + starts after ``other`` ends. + + Args: + other: temporal object to compare with + + Returns: + True if after, False otherwise + + MEOS Functions: + right_span_span, right_span_spanset + """ + from .spanset import SpanSet + if isinstance(other, Span): + return right_span_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return right_span_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + # ------------------------- Distance Operations --------------------------- + def distance(self, other) -> float: + """ + Returns the distance between ``self`` and ``other``. + + Args: + other: object to compare with + + Returns: + A :class:`flat` instance + + MEOS Functions: + distance_span_span, distance_spanset_span + """ + from .spanset import SpanSet + if isinstance(other, Span): + return distance_span_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return distance_spanset_span(other._inner, self._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + # ------------------------- Set Operations -------------------------------- + @abstractmethod + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + A collection instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_span_span, intersection_spanset_span, + intersection_period_timestamp + """ + from .spanset import SpanSet + if isinstance(other, Span): + return intersection_span_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return intersection_spanset_span(other._inner, self._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __mul__(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + A :class:`Span` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_span_span, intersection_spanset_span, + intersection_period_timestamp + """ + return self.intersection(other) + + @abstractmethod + def minus(self, other): + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: object to diff with + + Returns: + A :class:`SpanSet` instance. + """ + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __sub__(self, other): + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: object to diff with + + Returns: + A :class:`SpanSet` instance. + """ + return self.minus(other) + + @abstractmethod + def union(self, other): + """ + Returns the temporal union of ``self`` and ``other``. + + Args: + other: temporal object to merge with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + union_period_timestamp, union_spanset_span, union_span_span + """ + from .spanset import SpanSet + if isinstance(other, Span): + return union_span_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return union_spanset_span(other._inner, self._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __add__(self, other): + """ + Returns the temporal union of ``self`` and ``other``. + + Args: + other: temporal object to merge with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + union_period_timestamp, union_spanset_span, union_span_span + """ + return self.union(other) + + # ------------------------- Comparisons ----------------------------------- + def __eq__(self, other): + """ + Return whether ``self`` and ``other`` are equal. + + Args: + other: temporal object to compare with + + Returns: + True if equal, False otherwise + + MEOS Functions: + span_eq + """ + if isinstance(other, self.__class__): + return span_eq(self._inner, other._inner) + return False + + def __ne__(self, other): + """ + Return whether ``self`` and ``other`` are not equal. + + Args: + other: temporal object to compare with + + Returns: + True if not equal, False otherwise + + MEOS Functions: + span_neq + """ + if isinstance(other, self.__class__): + return span_ne(self._inner, other._inner) + return True + + def __lt__(self, other): + """ + Return whether ``self`` is less than ``other``. + + Args: + other: temporal object to compare with + + Returns: + True if less than, False otherwise + + MEOS Functions: + span_lt + """ + if isinstance(other, self.__class__): + return span_lt(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __le__(self, other): + """ + Return whether ``self`` is less than or equal to ``other``. + + Args: + other: temporal object to compare with + + Returns: + True if less than or equal, False otherwise + + MEOS Functions: + span_le + """ + if isinstance(other, self.__class__): + return span_le(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __gt__(self, other): + """ + Return whether ``self`` is greater than ``other``. + + Args: + other: temporal object to compare with + + Returns: + True if greater than, False otherwise + + MEOS Functions: + span_gt + """ + if isinstance(other, self.__class__): + return span_gt(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __ge__(self, other): + """ + Return whether ``self`` is greater than or equal to ``other``. + + Args: + other: temporal object to compare with + + Returns: + True if greater than or equal, False otherwise + + MEOS Functions: + span_ge + """ + if isinstance(other, self.__class__): + return span_ge(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') diff --git a/pymeos/pymeos/collections/base/spanset.py b/pymeos/pymeos/collections/base/spanset.py new file mode 100644 index 00000000..12a0501d --- /dev/null +++ b/pymeos/pymeos/collections/base/spanset.py @@ -0,0 +1,662 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Optional, Union +from typing import TypeVar, Type, Callable, Any, TYPE_CHECKING, List + +from pymeos_cffi import * + +from .collection import Collection + +if TYPE_CHECKING: + from .span import Span + +T = TypeVar('T') +Self = TypeVar('Self', bound='Span[Any]') + + +class SpanSet(Collection[T], ABC): + """ + Base class for all spanset classes. + """ + + __slots__ = ['_inner'] + + _parse_function: Callable[[str], 'CData'] = None + _parse_value_function: Callable[[Union[str, T]], Any] = None + + # ------------------------- Constructors ---------------------------------- + def __init__(self, string: Optional[str] = None, *, span_list: Optional[List[Union[str, Span]]] = None, + normalize: bool = True, _inner=None): + super().__init__() + assert (_inner is not None) or ((string is not None) != (span_list is not None)), \ + "Either string must be not None or span_list must be not" + if _inner is not None: + self._inner = _inner + elif string is not None: + self._inner = self.__class__._parse_function(string) + else: + spans = [self.__class__._parse_value_function(p) for p in span_list] + self._inner = spanset_make(spans, normalize) + + def __copy__(self: Self) -> Self: + """ + Return a copy of ``self``. + + Returns: + A new :class:`SpanSet` instance + + MEOS Functions: + spanset_copy + """ + inner_copy = spanset_copy(self._inner) + return self.__class__(_inner=inner_copy) + + @classmethod + def from_wkb(cls: Type[Self], wkb: bytes) -> Self: + """ + Returns a `SpanSet` from its WKB representation. + + Args: + wkb: The WKB string. + + Returns: + A new :class:`SpanSet` instance + + MEOS Functions: + spanset_from_wkb + """ + result = spanset_from_wkb(wkb) + return cls(_inner=result) + + @classmethod + def from_hexwkb(cls: Type[Self], hexwkb: str) -> Self: + """ + Returns a `SpanSet` from its WKB representation in hex-encoded ASCII. + Args: + hexwkb: WKB representation in hex-encoded ASCII + + Returns: + A new :class:`SpanSet` instance + + MEOS Functions: + spanset_from_hexwkb + """ + result = spanset_from_hexwkb(hexwkb) + return cls(_inner=result) + + # ------------------------- Output ---------------------------------------- + @abstractmethod + def __str__(self): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + """ + raise NotImplementedError() + + def __repr__(self): + """ + Return the string representation of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + periodset_out + """ + return (f'{self.__class__.__name__}' + f'({self})') + + def as_wkb(self) -> bytes: + """ + Returns the WKB representation of ``self``. + + Returns: + A :class:`str` object with the WKB representation of ``self``. + + MEOS Functions: + spanset_as_wkb + """ + return spanset_as_wkb(self._inner, 4) + + def as_hexwkb(self) -> str: + """ + Returns the WKB representation of ``self`` in hex-encoded ASCII. + Returns: + A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. + + MEOS Functions: + spanset_as_hexwkb + """ + return spanset_as_hexwkb(self._inner, -1)[0] + + # ------------------------- Conversions ----------------------------------- + @abstractmethod + def to_span(self) -> Span: + """ + Returns a span that encompasses ``self``. + + Returns: + A new :class:`Span` instance + + MEOS Functions: + spanset_span + """ + return spanset_span(self._inner) + + # ------------------------- Accessors ------------------------------------- + + def num_spans(self) -> int: + """ + Returns the number of spans in ``self``. + Returns: + An :class:`int` + + MEOS Functions: + spanset_num_spans + """ + return spanset_num_spans(self._inner) + + @abstractmethod + def start_span(self) -> Span: + """ + Returns the first span in ``self``. + Returns: + A :class:`Span` instance + + MEOS Functions: + spanset_start_span + """ + return spanset_start_span(self._inner) + + @abstractmethod + def end_span(self) -> Span: + """ + Returns the last span in ``self``. + Returns: + A :class:`Span` instance + + MEOS Functions: + spanset_end_span + """ + return spanset_end_span(self._inner) + + @abstractmethod + def span_n(self, n: int) -> Span: + """ + Returns the n-th span in ``self``. + Returns: + A :class:`Span` instance + + MEOS Functions: + spanset_span_n + """ + return spanset_span_n(self._inner, n + 1) + + @abstractmethod + def spans(self) -> List[Span]: + """ + Returns the list of periods in ``self``. + Returns: + A :class:`list[Period]` instance + + MEOS Functions: + spanset_spans + """ + return spanset_spans(self._inner) + + def __hash__(self) -> int: + """ + Return the hash representation of ``self``. + + Returns: + A new :class:`int` instance + + MEOS Functions: + spanset_hash + """ + return spanset_hash(self._inner) + + # ------------------------- Transformations ------------------------------- + + # ------------------------- Topological Operations ------------------------ + def is_adjacent(self, other) -> bool: + """ + Returns whether ``self`` is adjacent to ``other``. That is, they share a bound but only one of them + contains it. + + Args: + other: object to compare with + + Returns: + True if adjacent, False otherwise + + MEOS Functions: + adjacent_spanset_span, adjacent_spanset_spanset + """ + from .span import Span + if isinstance(other, Span): + return adjacent_spanset_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return adjacent_spanset_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_contained_in(self, container) -> bool: + """ + Returns whether ``self`` is contained in ``container``. + + Args: + container: temporal object to compare with + + Returns: + True if contained, False otherwise + + MEOS Functions: + contained_spanset_span, contained_spanset_spanset + """ + from .span import Span + if isinstance(container, Span): + return contained_spanset_span(self._inner, container._inner) + elif isinstance(container, SpanSet): + return contained_spanset_spanset(self._inner, container._inner) + else: + raise TypeError(f'Operation not supported with type {container.__class__}') + + def contains(self, content) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_spanset_span, contains_spanset_spanset, + """ + from .span import Span + if isinstance(content, Span): + return contains_spanset_span(self._inner, content._inner) + elif isinstance(content, SpanSet): + return contains_spanset_spanset(self._inner, content._inner) + else: + raise TypeError(f'Operation not supported with type {content.__class__}') + + def __contains__(self, item): + """ + Returns whether ``self`` contains ``content``. + + Args: + item: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_spanset_span, contains_spanset_spanset + """ + return self.contains(item) + + def overlaps(self, other) -> bool: + """ + Returns whether ``self`` overlaps ``other``. That is, both share at least an instant + + Args: + other: temporal object to compare with + + Returns: + True if overlaps, False otherwise + + MEOS Functions: + overlaps_spanset_span, overlaps_spanset_spanset + """ + from .span import Span + if isinstance(other, Span): + return overlaps_spanset_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return overlaps_spanset_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_same(self, other) -> bool: + """ + Returns whether the bounding span of `self` is the same as the bounding span of `other`. + + Args: + other: A time or temporal object to compare to `self`. + + Returns: + True if same, False otherwise. + + See Also: + :meth:`Period.is_same` + """ + return self.to_span().is_same(other) + + # ------------------------- Position Operations --------------------------- + def is_left(self, other) -> bool: + """ + Returns whether ``self`` is strictly to the left of ``other``. That is, ``self`` ends before ``other`` starts. + + Args: + other: temporal object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + before_periodset_timestamp, left_spanset_span, left_spanset_spanset + """ + from .span import Span + if isinstance(other, Span): + return left_spanset_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return left_spanset_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_over_or_left(self, other) -> bool: + """ + Returns whether ``self`` is to the left of ``other`` allowing overlap. That is, ``self`` ends before ``other`` ends (or + at the same time). + + Args: + other: temporal object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + overleft_spanset_span, overleft_spanset_spanset + """ + from .span import Span + if isinstance(other, Span): + return overleft_spanset_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return overleft_spanset_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_over_or_right(self, other) -> bool: + """ + Returns whether ``self`` is to the right of ``other`` allowing overlap. That is, ``self`` starts after ``other`` starts + (or at the same time). + + Args: + other: temporal object to compare with + + Returns: + True if overlapping or after, False otherwise + + MEOS Functions: + overright_spanset_span, overright_spanset_spanset + """ + from .span import Span + if isinstance(other, Span): + return overright_spanset_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return overright_spanset_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def is_right(self, other) -> bool: + """ + Returns whether ``self`` is strictly to the right of ``other``.That is, ``self`` starts after ``other`` ends. + + Args: + other: temporal object to compare with + + Returns: + True if after, False otherwise + + MEOS Functions: + right_spanset_span, right_spanset_spanset + """ + from .span import Span + if isinstance(other, Span): + return right_spanset_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return right_spanset_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + # ------------------------- Distance Operations --------------------------- + def distance(self, other) -> float: + """ + Returns the distance between ``self`` and ``other``. + + Args: + other: object to compare with + + Returns: + A :class:`float` instance + + MEOS Functions: + """ + from .span import Span + if isinstance(other, Span): + return distance_spanset_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return distance_spanset_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + # ------------------------- Set Operations -------------------------------- + @abstractmethod + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + A collection instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_spanset_spanset, intersection_spanset_span + """ + from .span import Span + if isinstance(other, Span): + return intersection_spanset_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return intersection_spanset_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __mul__(self, other): + """ + Returns the temporal intersection of ``self`` and ``other``. + + Args: + other: temporal object to intersect with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_periodset_timestamp, intersection_spanset_spanset, intersection_spanset_span + """ + return self.intersection(other) + + @abstractmethod + def minus(self, other): + """ + Returns the temporal difference of ``self`` and ``other``. + + Args: + other: temporal object to diff with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + minus_spanset_span, minus_spanset_spanset + """ + from .span import Span + if isinstance(other, Span): + return minus_spanset_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return minus_spanset_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __sub__(self, other): + """ + Returns the temporal difference of ``self`` and ``other``. + + Args: + other: temporal object to diff with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + minus_spanset_span, minus_spanset_spanset, minus_periodset_timestamp + """ + return self.minus(other) + + @abstractmethod + def union(self, other): + """ + Returns the temporal union of ``self`` and ``other``. + + Args: + other: temporal object to merge with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + union_periodset_timestamp, union_spanset_spanset, + union_spanset_span + """ + from .span import Span + if isinstance(other, Span): + return union_spanset_span(self._inner, other._inner) + elif isinstance(other, SpanSet): + return union_spanset_spanset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __add__(self, other): + """ + Returns the temporal union of ``self`` and ``other``. + + Args: + other: temporal object to merge with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + union_periodset_timestamp, union_spanset_spanset, union_spanset_span + """ + return self.union(other) + + # ------------------------- Comparisons ----------------------------------- + def __eq__(self, other): + """ + Return whether ``self`` and ``other`` are equal. + + Args: + other: temporal object to compare with + + Returns: + True if equal, False otherwise + + MEOS Functions: + spanset_eq + """ + if isinstance(other, self.__class__): + return spanset_eq(self._inner, other._inner) + return False + + def __ne__(self, other): + """ + Return whether ``self`` and ``other`` are not equal. + + Args: + other: temporal object to compare with + + Returns: + True if not equal, False otherwise + + MEOS Functions: + spanset_ne + """ + if isinstance(other, self.__class__): + return spanset_ne(self._inner, other._inner) + return True + + def __lt__(self, other): + """ + Return whether ``self`` is less than ``other``. + + Args: + other: temporal object to compare with + + Returns: + True if less than, False otherwise + + MEOS Functions: + spanset_lt + """ + if isinstance(other, self.__class__): + return spanset_lt(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __le__(self, other): + """ + Return whether ``self`` is less than or equal to ``other``. + + Args: + other: temporal object to compare with + + Returns: + True if less than or equal, False otherwise + + MEOS Functions: + spanset_le + """ + if isinstance(other, self.__class__): + return spanset_le(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __gt__(self, other): + """ + Return whether ``self`` is greater than ``other``. + + Args: + other: temporal object to compare with + + Returns: + True if greater than, False otherwise + + MEOS Functions: + spanset_gt + """ + if isinstance(other, self.__class__): + return spanset_gt(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') + + def __ge__(self, other): + """ + Return whether ``self`` is greater than or equal to ``other``. + + Args: + other: temporal object to compare with + + Returns: + True if greater than or equal, False otherwise + + MEOS Functions: + spanset_ge + """ + if isinstance(other, self.__class__): + return spanset_ge(self._inner, other._inner) + raise TypeError(f'Operation not supported with type {other.__class__}') diff --git a/pymeos/pymeos/collections/geo/__init__.py b/pymeos/pymeos/collections/geo/__init__.py new file mode 100644 index 00000000..30b84136 --- /dev/null +++ b/pymeos/pymeos/collections/geo/__init__.py @@ -0,0 +1,3 @@ +from .geoset import GeoSet, GeometrySet, GeographySet + +__all__ = ['GeoSet', 'GeometrySet', 'GeographySet'] diff --git a/pymeos/pymeos/collections/geo/geoset.py b/pymeos/pymeos/collections/geo/geoset.py new file mode 100644 index 00000000..8f2c0d13 --- /dev/null +++ b/pymeos/pymeos/collections/geo/geoset.py @@ -0,0 +1,300 @@ +from __future__ import annotations + +from abc import ABC +from typing import List, overload, Optional, Union, TypeVar + +import shapely as shp +from pymeos_cffi import geoset_start_value, gserialized_to_shapely_geometry, geoset_end_value, geoset_value_n, \ + geoset_values, intersection_geoset_geo, minus_geoset_geo, union_geoset_geo, geoset_as_ewkt, geoset_as_text, \ + geoset_out, geoset_make, geoset_srid, geoset_round, minus_geo_geoset, geomset_in, geogset_in, pgis_geometry_in, \ + geometry_to_gserialized, pgis_geography_in, geography_to_gserialized, intersection_set_set, minus_set_set, \ + union_set_set + +from ..base import Set + +Self = TypeVar('Self', bound='GeoSet') + + +class GeoSet(Set[shp.Geometry], ABC): + __slots__ = ['_inner'] + + _make_function = geoset_make + + # ------------------------- Constructors ---------------------------------- + + # ------------------------- Output ---------------------------------------- + + def __str__(self, max_decimals: int = 15): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + geoset_out + """ + return geoset_out(self._inner, max_decimals) + + def as_ewkt(self, max_decimals: int = 15) -> str: + """ + Returns the EWKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use for the coordinates. + + Returns: + A :class:`str` instance. + + MEOS Functions: + geoset_as_ewkt + """ + return geoset_as_ewkt(self._inner, max_decimals) + + def as_wkt(self, max_decimals: int = 15): + """ + Returns the WKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use for the coordinates. + + Returns: + A :class:`str` instance. + + MEOS Functions: + geoset_as_text + """ + return geoset_as_text(self._inner, max_decimals) + + def as_text(self, max_decimals: int = 15): + """ + Returns the WKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use for the coordinates. + + Returns: + A :class:`str` instance. + + MEOS Functions: + geoset_as_text + """ + return geoset_as_text(self._inner, max_decimals) + + # ------------------------- Conversions ----------------------------------- + + def to_spanset(self): + raise NotImplementedError() + + def to_span(self): + raise NotImplementedError() + + # ------------------------- Accessors ------------------------------------- + + def start_element(self) -> shp.Geometry: + """ + Returns the first element in ``self``. + + Returns: + A :class:`Geometry` instance + + MEOS Functions: + geoset_start_value + """ + return gserialized_to_shapely_geometry(geoset_start_value(self._inner)) + + def end_element(self) -> shp.Geometry: + """ + Returns the last element in ``self``. + + Returns: + A :class:`Geometry` instance + + MEOS Functions: + geoset_end_value + """ + return gserialized_to_shapely_geometry(geoset_end_value(self._inner)) + + def element_n(self, n: int) -> shp.Geometry: + """ + Returns the ``n``-th element in ``self``. + + Args: + n: The 0-based index of the element to return. + + Returns: + A :class:`Geometry` instance + + MEOS Functions: + geoset_value_n + """ + super().element_n(n) + return gserialized_to_shapely_geometry(geoset_value_n(self._inner, n + 1)[0]) + + def elements(self) -> List[shp.Geometry]: + """ + Returns a list of all elements in ``self``. + + Returns: + A list of :class:`Geometry` instances + + MEOS Functions: + geoset_values + """ + elems = geoset_values(self._inner) + return [gserialized_to_shapely_geometry(elems[i]) for i in range(self.num_elements())] + + def srid(self) -> int: + """ + Returns the SRID of ``self``. + + Returns: + An integer + + MEOS Functions: + geoset_srid + """ + return geoset_srid(self._inner) + + # ------------------------- Topological Operations -------------------------------- + + def contains(self, content: Union[GeoSet, str]) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + """ + return super().contains(content) + + # ------------------------- Set Operations -------------------------------- + + @overload + def intersection(self, other: shp.Geometry) -> Optional[shp.Geometry]: + ... + + @overload + def intersection(self, other: GeoSet) -> Optional[GeoSet]: + ... + + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: A :class:`GeoSet` or :class:`Geometry` instance + + Returns: + An object of the same type as ``other`` or ``None`` if the intersection is empty. + + MEOS Functions: + intersection_geoset_geo, intersection_set_set + """ + if isinstance(other, shp.Geometry): + return gserialized_to_shapely_geometry( + intersection_geoset_geo(self._inner, geometry_to_gserialized(other))[0]) + elif isinstance(other, GeoSet): + result = intersection_set_set(self._inner, other._inner) + return GeoSet(_inner=result) if result is not None else None + else: + return super().intersection(other) + + def minus(self, other: Union[GeoSet, shp.Geometry]) -> Optional[GeoSet]: + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: A :class:`GeoSet` or :class:`Geometry` instance + + Returns: + A :class:`GeoSet` instance or ``None`` if the difference is empty. + + MEOS Functions: + minus_geoset_geo, minus_set_set + + See Also: + :meth:`subtract_from` + """ + if isinstance(other, shp.Geometry): + result = minus_geoset_geo(self._inner, geometry_to_gserialized(other)) + return GeoSet(_inner=result) if result is not None else None + elif isinstance(other, GeoSet): + result = minus_set_set(self._inner, other._inner) + return GeoSet(_inner=result) if result is not None else None + else: + return super().minus(other) + + def subtract_from(self, other: shp.Geometry) -> Optional[shp.Geometry]: + """ + Returns the difference of ``other`` and ``self``. + + Args: + other: A :class:`Geometry` instance + + Returns: + A :class:`Geometry` instance. + + MEOS Functions: + minus_geo_geoset + + See Also: + :meth:`minus` + """ + result = minus_geo_geoset(geometry_to_gserialized(other), self._inner) + return gserialized_to_shapely_geometry(result[0]) if result is not None else None + + def union(self, other: Union[GeoSet, shp.Geometry]) -> GeoSet: + """ + Returns the union of ``self`` and ``other``. + + Args: + other: A :class:`GeoSet` or :class:`Geometry` instance + + Returns: + A :class:`GeoSet` instance. + + MEOS Functions: + union_geoset_geo, union_set_set + """ + if isinstance(other, shp.Geometry): + result = union_geoset_geo(self._inner, geometry_to_gserialized(other)) + return GeoSet(_inner=result) if result is not None else None + elif isinstance(other, GeoSet): + result = union_set_set(self._inner, other._inner) + return GeoSet(_inner=result) if result is not None else None + else: + return super().union(other) + + # ------------------------- Transformations ------------------------------------ + + def round(self: Self, max_decimals: int) -> Self: + """ + Rounds the coordinate values to a number of decimal places. + + Args: + max_decimals: The number of decimal places to use for the coordinates. + + Returns: + A new :class:`GeoSet` object of the same subtype of ``self``. + + MEOS Functions: + tpoint_roundgeoset_round + """ + return self.__class__(_inner=geoset_round(self._inner, max_decimals)) + + +class GeometrySet(GeoSet): + _mobilitydb_name = 'geomset' + + _parse_function = geomset_in + _parse_value_function = lambda x: pgis_geometry_in(x, -1) if isinstance(x, str) else geometry_to_gserialized(x) + + +class GeographySet(GeoSet): + _mobilitydb_name = 'geogset' + + _parse_function = geogset_in + _parse_value_function = lambda x: pgis_geography_in(x, -1) if isinstance(x, str) else geography_to_gserialized(x) diff --git a/pymeos/pymeos/collections/number/__init__.py b/pymeos/pymeos/collections/number/__init__.py new file mode 100644 index 00000000..445e0ef3 --- /dev/null +++ b/pymeos/pymeos/collections/number/__init__.py @@ -0,0 +1,11 @@ +from .intset import IntSet +from .intspan import IntSpan +from .intspanset import IntSpanSet +from .floatset import FloatSet +from .floatspan import FloatSpan +from .floatspanset import FloatSpanSet + +__all__ = [ + 'IntSet', 'IntSpan', 'IntSpanSet', + 'FloatSet', 'FloatSpan', 'FloatSpanSet' +] diff --git a/pymeos/pymeos/collections/number/floatset.py b/pymeos/pymeos/collections/number/floatset.py new file mode 100644 index 00000000..adfa24c3 --- /dev/null +++ b/pymeos/pymeos/collections/number/floatset.py @@ -0,0 +1,402 @@ +from __future__ import annotations + +from typing import List, Union, overload, Optional, TYPE_CHECKING + +from pymeos_cffi import floatset_in, floatset_make, floatset_out, \ + floatset_start_value, floatset_end_value, floatset_value_n, \ + floatset_values, contains_floatset_float, intersection_floatset_float, \ + intersection_set_set, left_floatset_float, overleft_floatset_float, \ + right_floatset_float, overright_floatset_float, minus_floatset_float, \ + minus_set_set, union_set_set, union_floatset_float, floatset_shift_scale, \ + minus_float_floatset, distance_floatset_float + +from .floatspan import FloatSpan +from .floatspanset import FloatSpanSet +from ..base import Set + +if TYPE_CHECKING: + from .intset import IntSet + + +class FloatSet(Set[float]): + """ + Class for representing a set of text values. + + ``TextSet`` objects can be created with a single argument of type string as + in MobilityDB. + + >>> FloatSet(string='{1, 3, 56}') + + Another possibility is to create a ``TextSet`` object from a list of + strings or floats. + + >>> FloatSet(elements=[1, '2', 3, '56']) + + + """ + + __slots__ = ['_inner'] + + _mobilitydb_name = 'floatset' + + _parse_function = floatset_in + _parse_value_function = float + _make_function = floatset_make + + # ------------------------- Constructors ---------------------------------- + + # ------------------------- Output ---------------------------------------- + + def __str__(self, max_decimals: int = 15): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + floatset_out + """ + return floatset_out(self._inner, max_decimals) + + # ------------------------- Conversions ----------------------------------- + + def to_spanset(self) -> FloatSpanSet: + """ + Returns a SpanSet that contains a Span for each element in ``self``. + + Returns: + A new :class:`FloatSpanSet` instance + + MEOS Functions: + set_to_spanset + """ + + return FloatSpanSet(_inner=super().to_spanset()) + + def to_span(self) -> FloatSpan: + """ + Returns a span that encompasses ``self``. + + Returns: + A new :class:`FloatSpan` instance + + MEOS Functions: + set_span + """ + return FloatSpan(_inner=super().to_span()) + + def to_intset(self) -> IntSet: + """ + Converts ``self`` to an :class:`IntSet` instance. + + Returns: + A new :class:`IntSet` instance + """ + from .intset import IntSet + return IntSet(elements=[int(x) for x in self.elements()]) + + # ------------------------- Accessors ------------------------------------- + + def start_element(self) -> float: + """ + Returns the first element in ``self``. + + Returns: + A :class:`float` instance + + MEOS Functions: + floatset_start_value + """ + return floatset_start_value(self._inner) + + def end_element(self) -> float: + """ + Returns the last element in ``self``. + + Returns: + A :class:`float` instance + + MEOS Functions: + floatset_end_value + """ + return floatset_end_value(self._inner) + + def element_n(self, n: int) -> float: + """ + Returns the ``n``-th element in ``self``. + + Args: + n: The 0-based index of the element to return. + + Returns: + A :class:`float` instance + + MEOS Functions: + floatset_value_n + """ + super().element_n(n) + return floatset_value_n(self._inner, n + 1) + + def elements(self) -> List[float]: + """ + Returns the elements in ``self``. + + Returns: + A list of :class:`float` instances + + MEOS Functions: + floattset_values + """ + elems = floatset_values(self._inner) + return [elems[i] for i in range(self.num_elements())] + + # ------------------------- Transformations ------------------------------------ + + def shift(self, delta: float) -> FloatSet: + """ + Returns a new ``FloatSet`` instance with all elements shifted by ``delta``. + + Args: + delta: The value to shift by. + + Returns: + A new :class:`FloatSet` instance + + MEOS Functions: + floatset_shift_scale + """ + return self.shift_scale(delta, None) + + def scale(self, new_width: float) -> FloatSet: + """ + Returns a new ``FloatSet`` instance with all elements scaled to so that the encompassing + span has width ``new_width``. + + Args: + new_width: The new width. + + Returns: + A new :class:`FloatSet` instance + + MEOS Functions: + floatset_shift_scale + """ + return self.shift_scale(None, new_width) + + def shift_scale(self, delta: Optional[float], new_width: Optional[float]) -> FloatSet: + """ + Returns a new ``FloatSet`` instance with all elements shifted by ``delta`` and scaled to so that the + encompassing span has width ``new_width``. + + Args: + delta: The value to shift by. + new_width: The new width. + + Returns: + A new :class:`FloatSet` instance + + MEOS Functions: + floatset_shift_scale + """ + return FloatSet( + _inner=floatset_shift_scale(self._inner, delta, new_width, delta is not None, new_width is not None)) + + # ------------------------- Topological Operations -------------------------------- + + def contains(self, content: Union[FloatSet, float]) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_set_set, contains_floatset_float + """ + if isinstance(content, float): + return contains_floatset_float(self._inner, content) + else: + return super().contains(content) + + # ------------------------- Position Operations -------------------------------- + + def is_left(self, content: Union[FloatSet, float]) -> bool: + """ + Returns whether ``self`` is strictly to the left of ``other``. That is, + ``self`` ends before ``other`` starts. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + left_set_set, left_floatset_float + """ + if isinstance(content, float): + return left_floatset_float(self._inner, content) + else: + return super().is_left(content) + + def is_over_or_left(self, content: Union[FloatSet, float]) -> bool: + """ + Returns whether ``self`` is to the left of ``other`` allowing overlap. + That is, ``self`` ends before ``other`` ends (or at the same value). + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + overleft_set_set, overleft_floatset_float + """ + if isinstance(content, float): + return overleft_floatset_float(self._inner, content) + else: + return super().is_over_or_left(content) + + def is_right(self, content: Union[FloatSet, float]) -> bool: + """ + Returns whether ``self`` is strictly to the left of ``other``. That is, + ``self`` starts before ``other`` ends. + + Args: + content: object to compare with + + Returns: + True if is rigth, False otherwise + + MEOS Functions: + right_set_set, right_floatset_float + """ + if isinstance(content, float): + return right_floatset_float(self._inner, content) + else: + return super().is_right(content) + + def is_over_or_right(self, content: Union[FloatSet, float]) -> bool: + """ + Returns whether ``self`` is to the right of ``other`` allowing overlap. + That is, ``self`` ends after ``other`` ends (or at the same value). + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + overright_set_set, overright_floatset_float + """ + if isinstance(content, float): + return overright_floatset_float(self._inner, content) + else: + return super().is_over_or_right(content) + + # ------------------------- Set Operations -------------------------------- + + @overload + def intersection(self, other: float) -> Optional[float]: + ... + + @overload + def intersection(self, other: FloatSet) -> Optional[FloatSet]: + ... + + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: A :class:`FloatSet` or :class:`float` instance + + Returns: + An object of the same type as ``other`` or ``None`` if the intersection is empty. + + MEOS Functions: + intersection_set_set, intersection_floatset_float + """ + if isinstance(other, float): + return intersection_floatset_float(self._inner, other) + elif isinstance(other, FloatSet): + result = intersection_set_set(self._inner, other._inner) + return FloatSet(_inner=result) if result is not None else None + else: + return super().intersection(other) + + def minus(self, other: Union[FloatSet, float]) -> Optional[FloatSet]: + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: A :class:`FloatSet` or :class:`float` instance + + Returns: + A :class:`FloatSet` instance or ``None`` if the difference is empty. + + MEOS Functions: + minus_set_set, minus_floatset_float + """ + if isinstance(other, float): + result = minus_floatset_float(self._inner, other) + return FloatSet(_inner=result) if result is not None else None + elif isinstance(other, FloatSet): + result = minus_set_set(self._inner, other._inner) + return FloatSet(_inner=result) if result is not None else None + else: + return super().minus(other) + + def subtract_from(self, other: float) -> Optional[float]: + """ + Returns the difference of ``other`` and ``self``. + + Args: + other: A :class:`float` instance + + Returns: + A :class:`float` instance or ``None`` if the difference is empty. + + MEOS Functions: + minus_float_floatset + + See Also: + :meth:`minus` + """ + return minus_float_floatset(other, self._inner) + + def union(self, other: Union[FloatSet, float]) -> FloatSet: + """ + Returns the union of ``self`` and ``other``. + + Args: + other: A :class:`FloatSet` or :class:`float` instance + + Returns: + A :class:`FloatSet` instance. + + MEOS Functions: + union_set_set, union_floatset_float + """ + if isinstance(other, float): + result = union_floatset_float(self._inner, other) + return FloatSet(_inner=result) if result is not None else None + elif isinstance(other, FloatSet): + result = union_set_set(self._inner, other._inner) + return FloatSet(_inner=result) if result is not None else None + else: + return super().union(other) + + # ------------------------- Distance Operations --------------------------- + + def distance(self, other: Union[float, FloatSet, FloatSpan, FloatSpanSet]) -> float: + if isinstance(other, float): + return distance_floatset_float(self._inner, other) + else: + return super().distance(other) diff --git a/pymeos/pymeos/collections/number/floatspan.py b/pymeos/pymeos/collections/number/floatspan.py new file mode 100644 index 00000000..c7b44584 --- /dev/null +++ b/pymeos/pymeos/collections/number/floatspan.py @@ -0,0 +1,424 @@ +from __future__ import annotations + +from typing import Union, overload, Optional, TYPE_CHECKING + +from pymeos_cffi import intersection_floatspan_float, distance_floatspan_float, \ + floatspan_in, floatspan_lower, floatspan_upper, floatspan_shift_scale, \ + contains_floatspan_float, adjacent_floatspan_float, \ + float_to_floatspan, span_eq, \ + left_floatspan_float, overleft_floatspan_float, \ + right_floatspan_float, overright_floatspan_float, \ + intersection_span_span, intersection_spanset_span, \ + minus_floatspan_float, minus_span_span, minus_spanset_span, \ + union_floatspan_float, union_span_span, union_spanset_span, \ + floatspan_out, floatspan_make, span_width, floatspan_intspan + +from .. import Span + +if TYPE_CHECKING: + from .floatspanset import FloatSpanSet + from .intspan import IntSpan + + +class FloatSpan(Span[float]): + """ + Class for representing sets of contiguous float values between a lower and + an upper bound. The bounds may be inclusive or not. + + ``FloatSpan`` objects can be created with a single argument of type string + as in MobilityDB. + + >>> FloatSpan('(2.5, 5.21]') + + Another possibility is to provide the ``lower`` and ``upper`` named + parameters (of type str or float), and optionally indicate whether the + bounds are inclusive or exclusive (by default, the lower bound is inclusive + and the upper is exclusive): + + >>> FloatSpan(lower=2.0, upper=5.8) + >>> FloatSpan(lower=2.0, upper=5.8, lower_inc=False, upper_inc=True) + >>> FloatSpan(lower='2.0', upper='5.8', upper_inc=True) + """ + + __slots__ = ['_inner'] + + _mobilitydb_name = 'floatspan' + + _parse_function = floatspan_in + _parse_value_function = float + _make_function = floatspan_make + + # ------------------------- Output ---------------------------------------- + def __str__(self, max_decimals: int = 15) -> str: + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + floatspan_out + """ + return floatspan_out(self._inner, max_decimals) + + # ------------------------- Conversions ----------------------------------- + + def to_spanset(self) -> FloatSpanSet: + """ + Returns a spanset containing ``self``. + + Returns: + A new :class:`FloatSpanSet` instance + + MEOS Functions: + span_to_spanset + """ + from .floatspanset import FloatSpanSet + return FloatSpanSet(_inner=super().to_spanset()) + + def to_intspan(self) -> IntSpan: + """ + Converts ``self`` to a :class:`IntSpan` instance. + + Returns: + A new :class:`IntSpan` instance + + MEOS Functions: + floatspan_intspan + """ + from .intspan import IntSpan + return IntSpan(_inner=floatspan_intspan(self._inner)) + + # ------------------------- Accessors ------------------------------------- + def lower(self) -> float: + """ + Returns the lower bound of ``self``. + + Returns: + The lower bound of the span as a :class:`float` + + MEOS Functions: + period_lower + """ + + return floatspan_lower(self._inner) + + def upper(self) -> float: + """ + Returns the upper bound of ``self``. + + Returns: + The upper bound of the span as a :class:`float` + + MEOS Functions: + period_upper + """ + return floatspan_upper(self._inner) + + def width(self) -> float: + """ + Returns the width of ``self``. + + Returns: + Returns a `float` representing the width of the span + + MEOS Functions: + span_width + """ + return span_width(self._inner) + + # ------------------------- Transformations ------------------------------- + def shift(self, delta: float) -> FloatSpan: + """ + Return a new ``FloatSpan`` with the lower and upper bounds shifted by + ``delta``. + + Args: + delta: The value to shift by + + Returns: + A new ``FloatSpan`` instance + + MEOS Functions: + floatspan_shift_scale + """ + return self.shift_scale(delta, None) + + def scale(self, width: float) -> FloatSpan: + """ + Return a new ``FloatSpan`` with the lower and upper bounds scaled so + that the width is ``width``. + + Args: + width: The new width + + Returns: + A new ``FloatSpan`` instance + + MEOS Functions: + floatspan_shift_scale + """ + return self.shift_scale(None, width) + + def shift_scale(self, delta: Optional[float], width: Optional[float]) -> FloatSpan: + """ + Return a new ``FloatSpan`` with the lower and upper bounds shifted by + ``delta`` and scaled so that the width is ``width``. + + Args: + delta: The value to shift by + width: The new width + + Returns: + A new ``FloatSpan`` instance + + MEOS Functions: + floatspan_shift_scale + """ + d = delta if delta is not None else 0 + w = width if width is not None else 0 + modified = floatspan_shift_scale(self._inner, d, w, delta is not None, + width is not None) + return FloatSpan(_inner=modified) + + # ------------------------- Topological Operations -------------------------------- + + def is_adjacent(self, other: Union[int, float, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` is adjacent to ``other``. That is, they share + a bound but only one of them contains it. + + Args: + other: object to compare with + + Returns: + True if adjacent, False otherwise + + MEOS Functions: + adjacent_span_span, adjacent_span_spanset, adjacent_floatspan_float + """ + if isinstance(other, int) or isinstance(other, float): + return adjacent_floatspan_float(self._inner, float(other)) + else: + return super().is_adjacent(other) + + def contains(self, content: Union[int, float, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_set_set, contains_floatspan_float + """ + if isinstance(content, int) or isinstance(content, float): + return contains_floatspan_float(self._inner, float(content)) + else: + return super().contains(content) + + def is_same(self, other: Union[int, float, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` and the bounding period of ``other`` is the + same. + + Args: + other: object to compare with + + Returns: + True if equal, False otherwise + + MEOS Functions: + same_period_temporal + """ + if isinstance(other, int) or isinstance(other, float): + return span_eq(self._inner, float_to_floatspan(float(other))) + else: + return super().is_same(other) + + # ------------------------- Position Operations --------------------------- + def is_left(self, other: Union[int, float, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` is strictly before ``other``. That is, + ``self`` ends before ``other`` starts. + + Args: + other: object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + left_span_span, left_span_spanset, left_floatspan_float + """ + if isinstance(other, int) or isinstance(other, float): + return left_floatspan_float(self._inner, float(other)) + else: + return super().is_left(other) + + def is_over_or_left(self, other: Union[int, float, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` is before ``other`` allowing overlap. That is, + ``self`` ends before ``other`` ends (or at the same value). + + Args: + other: object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + overleft_span_span, overleft_span_spanset, overleft_floatspan_float + """ + if isinstance(other, int) or isinstance(other, float): + return overleft_floatspan_float(self._inner, float(other)) + else: + return super().is_over_or_left(other) + + def is_right(self, other: Union[int, float, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` is strictly after ``other``. That is, ``self`` + starts after ``other`` ends. + + Args: + other: object to compare with + + Returns: + True if after, False otherwise + + MEOS Functions: + right_span_span, right_span_spanset, right_floatspan_float + """ + if isinstance(other, int) or isinstance(other, float): + return right_floatspan_float(self._inner, float(other)) + else: + return super().is_right(other) + + def is_over_or_right(self, other: Union[float, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` is after ``other`` allowing overlap. That is, + ``self`` starts after ``other`` starts (or at the same value). + + Args: + other: object to compare with + + Returns: + True if overlapping or after, False otherwise + + MEOS Functions: + overright_span_span, overright_span_spanset, overright_floatspan_float + """ + if isinstance(other, int) or isinstance(other, float): + return overright_floatspan_float(self._inner, float(other)) + else: + return super().is_over_or_right(other) + + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[int, float, FloatSpan, FloatSpanSet]) -> float: + """ + Returns the distance between ``self`` and ``other``. + + Args: + other: object to compare with + + Returns: + A float value + + MEOS Functions: + distance_span_span, distance_span_spanset, distance_floatspan_float, + """ + if isinstance(other, int) or isinstance(other, float): + return distance_floatspan_float(self._inner, float(other)) + else: + return super().distance(other) + + # ------------------------- Set Operations -------------------------------- + @overload + def intersection(self, other: Union[int, float]) -> Optional[float]: + ... + + @overload + def intersection(self, other: FloatSpan) -> Optional[FloatSpan]: + ... + + @overload + def intersection(self, other: FloatSpanSet) -> Optional[FloatSpanSet]: + ... + + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + A :class:`Collection[float]` instance. The actual class depends on + ``other``. + + MEOS Functions: + intersection_span_span, intersection_spanset_span, + intersection_floatset_float + """ + from .floatspanset import FloatSpanSet + if isinstance(other, int) or isinstance(other, float): + return intersection_floatspan_float(self._inner, float(other)) + elif isinstance(other, FloatSpan): + result = intersection_span_span(self._inner, other._inner) + return FloatSpan(_inner=result) if result is not None else None + elif isinstance(other, FloatSpanSet): + result = intersection_spanset_span(other._inner, self._inner) + return FloatSpanSet(_inner=result) if result is not None else None + else: + super().intersection(other) + + def minus(self, other: Union[int, float, FloatSpan, FloatSpanSet]) -> FloatSpanSet: + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: object to diff with + + Returns: + A :class:`FloatSpanSet` instance. + + MEOS Functions: + minus_span_span, minus_spanset_span, minus_floatspan_float + """ + from .floatspanset import FloatSpanSet + if isinstance(other, int) or isinstance(other, float): + result = minus_floatspan_float(self._inner, float(other)) + elif isinstance(other, FloatSpan): + result = minus_span_span(self._inner, other._inner) + elif isinstance(other, FloatSpanSet): + result = minus_spanset_span(other._inner, self._inner) + else: + result = super().minus(other) + return FloatSpanSet(_inner=result) if result is not None else None + + def union(self, other: Union[int, float, FloatSpan, FloatSpanSet]) -> FloatSpanSet: + """ + Returns the union of ``self`` and ``other``. + + Args: + other: object to merge with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + union_spanset_span, union_span_span, union_floatspan_float + """ + from .floatspanset import FloatSpanSet + if isinstance(other, int) or isinstance(other, float): + result = union_floatspan_float(self._inner, float(other)) + elif isinstance(other, FloatSpan): + result = union_span_span(self._inner, other._inner) + elif isinstance(other, FloatSpanSet): + result = union_spanset_span(other._inner, self._inner) + else: + result = super().union(other) + return FloatSpanSet(_inner=result) if result is not None else None diff --git a/pymeos/pymeos/collections/number/floatspanset.py b/pymeos/pymeos/collections/number/floatspanset.py new file mode 100644 index 00000000..5654960d --- /dev/null +++ b/pymeos/pymeos/collections/number/floatspanset.py @@ -0,0 +1,489 @@ +from __future__ import annotations + +from typing import Union, overload, Optional, TYPE_CHECKING, List + +from pymeos_cffi import floatspanset_in, floatspanset_out, spanset_width, \ + floatspanset_shift_scale, adjacent_floatspanset_float, contains_floatspanset_float, \ + spanset_eq, float_to_floatspanset, left_floatspanset_float, \ + overleft_floatspanset_float, right_floatspanset_float, overright_floatspanset_float, \ + distance_floatspanset_float, intersection_floatspanset_float, \ + union_floatspanset_float, minus_floatspanset_float, floatspanset_intspanset + +from pymeos.collections import SpanSet + +if TYPE_CHECKING: + from .floatspan import FloatSpan + from .intspanset import IntSpanSet + + +class FloatSpanSet(SpanSet[float]): + """ + Class for representing lists of disjoint floatspans. + + ``FloatSpanSet`` objects can be created with a single argument of type string + as in MobilityDB. + + >>> FloatSpanSet(string='{[8, 10], [11, 1]}') + + Another possibility is to give a list specifying the composing + spans, which can be instances of ``str`` or ``FloatSpan``. The composing + spans must be given in increasing order. + + >>> FloatSpanSet(span_list=['[8, 10]', '[11, 12]']) + >>> FloatSpanSet(span_list=[FloatSpan('[8, 10]'), FloatSpan('[11, 12]')]) + + """ + + __slots__ = ['_inner'] + + _mobilitydb_name = 'floatspanset' + + _parse_function = floatspanset_in + _parse_value_function = lambda span: floatspanset_in(span)[0] if isinstance(span, str) else span._inner[0] + + # ------------------------- Output ---------------------------------------- + def __str__(self, max_decimals: int = 15): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + floatspanset_out + """ + return floatspanset_out(self._inner, max_decimals) + + # ------------------------- Conversions ----------------------------------- + + def to_span(self) -> FloatSpan: + """ + Returns a span that encompasses ``self``. + + Returns: + A new :class:`FloatSpan` instance + + MEOS Functions: + spanset_span + """ + from .floatspan import FloatSpan + return FloatSpan(_inner=super().to_span()) + + def to_intspanset(self) -> IntSpanSet: + """ + Returns an intspanset that encompasses ``self``. + + Returns: + A new :class:`IntSpanSet` instance + + MEOS Functions: + floatspanset_intspanset + """ + from .intspanset import IntSpanSet + return IntSpanSet(_inner=floatspanset_intspanset(self._inner)) + + # ------------------------- Accessors ------------------------------------- + + def width(self, ignore_gaps: Optional[bool] = False) -> float: + """ + Returns the width of the spanset. By default, i.e., when the second + argument is False, the function takes into account the gaps within, + i.e., returns the sum of the widths of the spans within. + Otherwise, the function returns the width of the spanset ignoring + any gap, i.e., the width from the lower bound of the first span to + the upper bound of the last span. + + Parameters: + ignore_gaps: Whether to take into account potential gaps in + the spanset. + + Returns: + A `float` representing the duration of the spanset + + MEOS Functions: + spanset_width + """ + return spanset_width(self._inner, ignore_gaps) + + def start_span(self) -> FloatSpan: + """ + Returns the first span in ``self``. + Returns: + A :class:`FloatSpan` instance + + MEOS Functions: + spanset_start_span + """ + from .floatspan import FloatSpan + return FloatSpan(_inner=super().start_span()) + + def end_span(self) -> FloatSpan: + """ + Returns the last span in ``self``. + Returns: + A :class:`FloatSpan` instance + + MEOS Functions: + spanset_end_span + """ + from .floatspan import FloatSpan + return FloatSpan(_inner=super().end_span()) + + def span_n(self, n: int) -> FloatSpan: + """ + Returns the n-th span in ``self``. + Returns: + A :class:`FloatSpan` instance + + MEOS Functions: + spanset_span_n + """ + from .floatspan import FloatSpan + return FloatSpan(_inner=super().span_n(n)) + + def spans(self) -> List[FloatSpan]: + """ + Returns the list of spans in ``self``. + Returns: + A :class:`list[FloatSpan]` instance + + MEOS Functions: + spanset_spans + """ + from .floatspan import FloatSpan + ps = super().spans() + return [FloatSpan(_inner=ps[i]) for i in range(self.num_spans())] + + # ------------------------- Transformations ------------------------------- + + def shift(self, delta: int) -> FloatSpanSet: + """ + Return a new ``FloatSpanSet`` with the lower and upper bounds shifted by + ``delta``. + + Args: + delta: The value to shift by + + Returns: + A new ``FloatSpanSet`` instance + + MEOS Functions: + floatspanset_shift_scale + """ + return self.shift_scale(delta, None) + + def scale(self, width: int) -> FloatSpanSet: + """ + Return a new ``FloatSpanSet`` with the lower and upper bounds scaled so + that the width is ``width``. + + Args: + width: The new width + + Returns: + A new ``FloatSpanSet`` instance + + MEOS Functions: + floatspanset_shift_scale + """ + return self.shift_scale(None, width) + + def shift_scale(self, delta: Optional[int], width: Optional[int]) -> FloatSpanSet: + """ + Return a new ``FloatSpanSet`` with the lower and upper bounds shifted by + ``delta`` and scaled so that the width is ``width``. + + Args: + delta: The value to shift by + width: The new width + + Returns: + A new ``FloatSpanSet`` instance + + MEOS Functions: + floatspanset_shift_scale + """ + d = delta if delta is not None else 0 + w = width if width is not None else 0 + modified = floatspanset_shift_scale(self._inner, d, w, delta is not None, + width is not None) + return FloatSpanSet(_inner=modified) + + # ------------------------- Topological Operations -------------------------------- + + def is_adjacent(self, other: Union[int, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` is adjacent to ``other``. That is, they share + a bound but only one of them contains it. + + Args: + other: object to compare with + + Returns: + True if adjacent, False otherwise + + MEOS Functions: + adjacent_spanset_span, adjacent_spanset_spanset, + adjacent_floatspanset_float + """ + if isinstance(other, int): + return adjacent_floatspanset_float(self._inner, other) + else: + return super().is_adjacent(other) + + def contains(self, content: Union[int, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_spanset_span, contains_spanset_spanset, + contains_floatspanset_float + """ + if isinstance(content, int): + return contains_floatspanset_float(self._inner, content) + else: + return super().contains(content) + + def is_same(self, other: Union[int, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` and the bounding period of ``other`` is the + same. + + Args: + other: object to compare with + + Returns: + True if equal, False otherwise + + MEOS Functions: + same_period_temporal + """ + if isinstance(other, int): + return spanset_eq(self._inner, float_to_floatspanset(other)) + else: + return super().is_same(other) + + # ------------------------- Position Operations --------------------------- + def is_left(self, other: Union[int, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` is strictly left of ``other``. That is, + ``self`` ends before ``other`` starts. + + Args: + other: object to compare with + + Returns: + True if left, False otherwise + + MEOS Functions: + left_span_span, left_span_spanset, left_floatspan_float + """ + if isinstance(other, int): + return left_floatspanset_float(self._inner, other) + else: + return super().is_left(other) + + def is_over_or_left(self, other: Union[int, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` is left ``other`` allowing overlap. That is, + ``self`` ends before ``other`` ends (or at the same value). + + Args: + other: object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + overleft_span_span, overleft_span_spanset, overleft_floatspan_float + """ + if isinstance(other, int): + return overleft_floatspanset_float(self._inner, other) + else: + return super().is_over_or_left(other) + + def is_right(self, other: Union[int, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` is strictly right ``other``. That is, ``self`` + starts after ``other`` ends. + + Args: + other: object to compare with + + Returns: + True if right, False otherwise + + MEOS Functions: + right_span_span, right_span_spanset, right_floatspan_float + """ + if isinstance(other, int): + return right_floatspanset_float(self._inner, other) + else: + return super().is_right(other) + + def is_over_or_right(self, other: Union[int, FloatSpan, FloatSpanSet]) -> bool: + """ + Returns whether ``self`` is right ``other`` allowing overlap. That is, + ``self`` starts after ``other`` starts (or at the same value). + + Args: + other: object to compare with + + Returns: + True if overlapping or after, False otherwise + + MEOS Functions: + overright_spanset_span, overright_spanset_spanset, + overright_floatspanset_float + """ + if isinstance(other, int): + return overright_floatspanset_float(self._inner, other) + else: + return super().is_over_or_right(other) + + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[int, FloatSpan, FloatSpanSet]) -> float: + """ + Returns the distance between ``self`` and ``other``. + + Args: + other: object to compare with + + Returns: + A float value + + MEOS Functions: + distance_spanset_span, distance_spanset_spanset, + distance_floatspanset_float + """ + if isinstance(other, int): + return distance_floatspanset_float(self._inner, other) + else: + return super().distance(other) + + # ------------------------- Set Operations -------------------------------- + @overload + def intersection(self, other: Union[int, float]) -> Optional[float]: + ... + + @overload + def intersection(self, other: FloatSpan) -> Optional[FloatSpanSet]: + ... + + @overload + def intersection(self, other: FloatSpanSet) -> Optional[FloatSpanSet]: + ... + + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + An int or a :class:`FloatSpanSet` instance. The actual class depends + on ``other``. + + MEOS Functions: + intersection_floatspanset_float, intersection_spanset_spanset, + intersection_spanset_span + """ + if isinstance(other, int) or isinstance(other, float): + result = intersection_floatspanset_float(self._inner, float(other)) + else: + result = super().intersection(other) + return FloatSpanSet(_inner=result) if result is not None else None + + def __mul__(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_floatspanset_float, intersection_spanset_spanset, + intersection_spanset_span + """ + return self.intersection(other) + + def minus(self, other: Union[int, FloatSpan, FloatSpanSet]) -> FloatSpanSet: + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: object to diff with + + Returns: + A :class:`FloatSpanSet` instance. + + MEOS Functions: + minus_spanset_span, minus_spanset_spanset, minus_floatspanset_float + """ + if isinstance(other, int) or isinstance(other, float): + result = minus_floatspanset_float(self._inner, float(other)) + else: + result = super().minus(other) + return FloatSpanSet(_inner=result) if result is not None else None + + def __sub__(self, other): + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: object to diff with + + Returns: + A :class:`FloatSpanSet` instance. + + MEOS Functions: + minus_spanset_span, minus_spanset_spanset, + minus_floatspanset_float + """ + return self.minus(other) + + def union(self, other: Union[int, FloatSpan, FloatSpanSet]) -> FloatSpanSet: + """ + Returns the union of ``self`` and ``other``. + + Args: + other: object to merge with + + Returns: + A :class:`FloatSpanSet` instance. + + MEOS Functions: + union_floatspanset_float, union_spanset_spanset, + union_spanset_span + """ + if isinstance(other, int) or isinstance(other, float): + result = union_floatspanset_float(self._inner, float(other)) + else: + result = super().union(other) + return FloatSpanSet(_inner=result) if result is not None else None + + def __add__(self, other): + """ + Returns the union of ``self`` and ``other``. + + Args: + other: object to merge with + + Returns: + A :class:`FloatSpanSet` instance. + + MEOS Functions: + union_floatspanset_float, union_spanset_spanset, + union_spanset_span + """ + return self.union(other) diff --git a/pymeos/pymeos/collections/number/intset.py b/pymeos/pymeos/collections/number/intset.py new file mode 100644 index 00000000..57bf45bc --- /dev/null +++ b/pymeos/pymeos/collections/number/intset.py @@ -0,0 +1,404 @@ +from __future__ import annotations + +from typing import List, Union, overload, Optional, TYPE_CHECKING + +from pymeos_cffi import intset_in, intset_make, intset_out, intset_start_value, \ + intset_end_value, intset_value_n, intset_values, contains_intset_int, \ + intersection_intset_int, intersection_set_set, minus_intset_int, \ + left_intset_int, overleft_intset_int, right_intset_int, overright_intset_int, \ + minus_set_set, union_set_set, union_intset_int, intset_shift_scale, \ + minus_int_intset, distance_intset_int + +from .intspan import IntSpan +from .intspanset import IntSpanSet +from ..base import Set + +if TYPE_CHECKING: + from .floatset import FloatSet + + +class IntSet(Set[int]): + """ + Class for representing a set of text values. + + ``TextSet`` objects can be created with a single argument of type string as + in MobilityDB. + + >>> IntSet(string='{1, 3, 56}') + + Another possibility is to create a ``TextSet`` object from a list of + strings or integers. + + >>> IntSet(elements=[1, '2', 3, '56']) + + + """ + + __slots__ = ['_inner'] + + _mobilitydb_name = 'intset' + + _parse_function = intset_in + _parse_value_function = int + _make_function = intset_make + + # ------------------------- Constructors ---------------------------------- + + # ------------------------- Output ---------------------------------------- + + def __str__(self): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + intset_out + """ + return intset_out(self._inner) + + # ------------------------- Conversions ----------------------------------- + + def to_spanset(self) -> IntSpanSet: + """ + Returns a SpanSet that contains a Span for each element in ``self``. + + Returns: + A new :class:`IntSpanSet` instance + + MEOS Functions: + set_to_spanset + """ + + return IntSpanSet(_inner=super().to_spanset()) + + def to_span(self) -> IntSpan: + """ + Returns a span that encompasses ``self``. + + Returns: + A new :class:`IntSpan` instance + + MEOS Functions: + set_span + """ + return IntSpan(_inner=super().to_span()) + + def to_floatset(self) -> FloatSet: + """ + Converts ``self`` to a :class:`FloatSet` instance. + + Returns: + A new :class:`FloatSet` instance + """ + from .floatset import FloatSet + return FloatSet(elements=[float(x) for x in self.elements()]) + + # ------------------------- Accessors ------------------------------------- + + def start_element(self) -> int: + """ + Returns the first element in ``self``. + + Returns: + A :class:`int` instance + + MEOS Functions: + intset_start_value + """ + return intset_start_value(self._inner) + + def end_element(self) -> int: + """ + Returns the last element in ``self``. + + Returns: + A :class:`int` instance + + MEOS Functions: + intset_end_value + """ + return intset_end_value(self._inner) + + def element_n(self, n: int) -> int: + """ + Returns the ``n``-th element in ``self``. + + Args: + n: The 0-based index of the element to return. + + Returns: + A :class:`int` instance + + MEOS Functions: + intset_value_n + """ + super().element_n(n) + return intset_value_n(self._inner, n + 1) + + def elements(self) -> List[int]: + """ + Returns the elements in ``self``. + + Returns: + A list of :class:`int` instances + + MEOS Functions: + inttset_values + """ + elems = intset_values(self._inner) + return [elems[i] for i in range(self.num_elements())] + + # ------------------------- Transformations ------------------------------------ + + def shift(self, delta: int) -> IntSet: + """ + Returns a new ``IntSet`` instance with all elements shifted by ``delta``. + + Args: + delta: The value to shift by. + + Returns: + A new :class:`IntSet` instance + + MEOS Functions: + intset_shift_scale + """ + return self.shift_scale(delta, None) + + def scale(self, width: int) -> IntSet: + """ + Returns a new ``IntSet`` instance with all elements scaled to so that the encompassing + span has width ``width``. + + Args: + width: The new width. + + Returns: + A new :class:`IntSet` instance + + MEOS Functions: + intset_shift_scale + """ + return self.shift_scale(None, width) + + def shift_scale(self, delta: Optional[int], width: Optional[int]) -> IntSet: + """ + Returns a new ``IntSet`` instance with all elements shifted by + ``delta`` and scaled to so that the encompassing span has width + ``width``. + + Args: + delta: The value to shift by. + width: The new width. + + Returns: + A new :class:`IntSet` instance + + MEOS Functions: + intset_shift_scale + """ + return IntSet( + _inner=intset_shift_scale(self._inner, delta, width, + delta is not None, width is not None)) + + # ------------------------- Topological Operations -------------------------------- + + def contains(self, content: Union[IntSet, int]) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_set_set, contains_intset_int + """ + if isinstance(content, int): + return contains_intset_int(self._inner, content) + else: + return super().contains(content) + + # ------------------------- Position Operations -------------------------------- + + def is_left(self, content: Union[IntSet, int]) -> bool: + """ + Returns whether ``self`` is strictly to the left of ``other``. That is, + ``self`` ends before ``other`` starts. + + Args: + content: object to compare with + + Returns: + True if left, False otherwise + + MEOS Functions: + left_set_set, left_intset_int + """ + if isinstance(content, int): + return left_intset_int(self._inner, content) + else: + return super().is_left(content) + + def is_over_or_left(self, content: Union[IntSet, int]) -> bool: + """ + Returns whether ``self`` is to the left of ``other`` allowing overlap. + That is, ``self`` ends before ``other`` ends (or at the same value). + + Args: + content: object to compare with + + Returns: + True if is over or left, False otherwise + + MEOS Functions: + overleft_set_set, overleft_intset_int + """ + if isinstance(content, int): + return overleft_intset_int(self._inner, content) + else: + return super().is_over_or_left(content) + + def is_right(self, content: Union[IntSet, int]) -> bool: + """ + Returns whether ``self`` is strictly to the right of ``other``. That is, + ``self`` ends after ``other`` starts. + + Args: + content: object to compare with + + Returns: + True if right, False otherwise + + MEOS Functions: + right_set_set, right_intset_int + """ + if isinstance(content, int): + return right_intset_int(self._inner, content) + else: + return super().is_right(content) + + def is_over_or_right(self, content: Union[IntSet, int]) -> bool: + """ + Returns whether ``self`` is to the right of ``other`` allowing overlap. + That is, ``self`` starts before ``other`` ends (or at the same value). + + Args: + content: object to compare with + + Returns: + True if is over or right, False otherwise + + MEOS Functions: + overright_set_set, overright_intset_int + """ + if isinstance(content, int): + return overright_intset_int(self._inner, content) + else: + return super().is_over_or_right(content) + + # ------------------------- Set Operations -------------------------------- + + @overload + def intersection(self, other: int) -> Optional[int]: + ... + + @overload + def intersection(self, other: IntSet) -> Optional[IntSet]: + ... + + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: A :class:`IntSet` or :class:`int` instance + + Returns: + An object of the same type as ``other`` or ``None`` if the + intersection is empty. + + MEOS Functions: + intersection_set_set, intersection_intset_int + """ + if isinstance(other, int): + return intersection_intset_int(self._inner, other) + elif isinstance(other, IntSet): + result = intersection_set_set(self._inner, other._inner) + return IntSet(_inner=result) if result is not None else None + else: + return super().intersection(other) + + def minus(self, other: Union[IntSet, int]) -> Optional[IntSet]: + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: A :class:`IntSet` or :class:`int` instance + + Returns: + A :class:`IntSet` instance or ``None`` if the difference is empty. + + MEOS Functions: + minus_set_set, minus_intset_int + """ + if isinstance(other, int): + result = minus_intset_int(self._inner, other) + return IntSet(_inner=result) if result is not None else None + elif isinstance(other, IntSet): + result = minus_set_set(self._inner, other._inner) + return IntSet(_inner=result) if result is not None else None + else: + return super().minus(other) + + def subtract_from(self, other: int) -> Optional[int]: + """ + Returns the difference of ``other`` and ``self``. + + Args: + other: A :class:`int` instance + + Returns: + A :class:`int` instance or ``None`` if the difference is empty. + + MEOS Functions: + minus_int_intset + + See Also: + :meth:`minus` + """ + return minus_int_intset(other, self._inner) + + def union(self, other: Union[IntSet, int]) -> IntSet: + """ + Returns the union of ``self`` and ``other``. + + Args: + other: A :class:`IntSet` or :class:`int` instance + + Returns: + A :class:`IntSet` instance. + + MEOS Functions: + union_set_set, union_intset_int + """ + if isinstance(other, int): + result = union_intset_int(self._inner, other) + return IntSet(_inner=result) if result is not None else None + elif isinstance(other, IntSet): + result = union_set_set(self._inner, other._inner) + return IntSet(_inner=result) if result is not None else None + else: + return super().union(other) + + # ------------------------- Distance Operations --------------------------- + + def distance(self, other: Union[int, IntSet, IntSpan, IntSpanSet]) -> float: + if isinstance(other, int): + return distance_intset_int(self._inner, other) + else: + return super().distance(other) diff --git a/pymeos/pymeos/collections/number/intspan.py b/pymeos/pymeos/collections/number/intspan.py new file mode 100644 index 00000000..9d7d6226 --- /dev/null +++ b/pymeos/pymeos/collections/number/intspan.py @@ -0,0 +1,420 @@ +from __future__ import annotations + +from typing import Union, overload, Optional, TYPE_CHECKING + +from pymeos_cffi import intspan_in, intspan_lower, intspan_upper, \ + intspan_shift_scale, contains_intspan_int, adjacent_intspan_int, \ + span_width, int_to_intspan, span_eq, left_intspan_int, \ + overleft_intspan_int, right_intspan_int, overright_intspan_int, \ + intersection_intspan_int, intersection_span_span, intersection_spanset_span, \ + minus_intspan_int, minus_span_span, minus_spanset_span, union_intspan_int, \ + union_span_span, union_spanset_span, intspan_out, intspan_make, \ + distance_intspan_int, intspan_floatspan + +from .. import Span + +if TYPE_CHECKING: + from .intspanset import IntSpanSet + from .floatspan import FloatSpan + + +class IntSpan(Span[int]): + """ + Class for representing sets of contiguous integer values between a lower and + an upper bound. The bounds may be inclusive or not. + + ``IntSpan`` objects can be created with a single argument of type string + as in MobilityDB. + + >>> IntSpan('(2, 5]') + + Another possibility is to provide the ``lower`` and ``upper`` named parameters (of type str or int), and + optionally indicate whether the bounds are inclusive or exclusive (by default, the lower bound is inclusive and the + upper is exclusive): + + >>> IntSpan(lower=2, upper=5) + >>> IntSpan(lower=2, upper=5, lower_inc=False, upper_inc=True) + >>> IntSpan(lower='2', upper='5', upper_inc=True) + """ + + __slots__ = ['_inner'] + + _mobilitydb_name = 'intspan' + + _parse_function = intspan_in + _parse_value_function = int + _make_function = intspan_make + + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + intspan_out + """ + return intspan_out(self._inner) + + # ------------------------- Conversions ----------------------------------- + + def to_spanset(self) -> IntSpanSet: + """ + Returns a spanset containing ``self``. + + Returns: + A new :class:`IntSpanSet` instance + + MEOS Functions: + span_to_spanset + """ + from .intspanset import IntSpanSet + return IntSpanSet(_inner=super().to_spanset()) + + def to_floatspan(self) -> FloatSpan: + """ + Converts ``self`` to a :class:`FloatSpan` instance. + + Returns: + A new :class:`FloatSpan` instance + + MEOS Functions: + intspan_floatspan + """ + from .floatspan import FloatSpan + return FloatSpan(_inner=intspan_floatspan(self._inner)) + + # ------------------------- Accessors ------------------------------------- + def lower(self) -> int: + """ + Returns the lower bound of ``self``. + + Returns: + The lower bound of the span as a :class:`int` + + MEOS Functions: + period_lower + """ + + return intspan_lower(self._inner) + + def upper(self) -> int: + """ + Returns the upper bound of ``self``. + + Returns: + The upper bound of the span as a :class:`int` + + MEOS Functions: + period_upper + """ + return intspan_upper(self._inner) + + def width(self) -> float: + """ + Returns the width of ``self``. + + Returns: + Returns a `float` representing the width of the span + + MEOS Functions: + span_width + """ + return span_width(self._inner) + + # ------------------------- Transformations ------------------------------- + def shift(self, delta: int) -> IntSpan: + """ + Return a new ``IntSpan`` with the lower and upper bounds shifted by ``delta``. + + Args: + delta: The value to shift by + + Returns: + A new ``IntSpan`` instance + + MEOS Functions: + intspan_shift_scale + """ + return self.shift_scale(delta, None) + + def scale(self, width: int) -> IntSpan: + """ + Return a new ``IntSpan`` with the lower and upper bounds scaled so that + the width is ``width``. + + Args: + width: The new width + + Returns: + A new ``IntSpan`` instance + + MEOS Functions: + intspan_shift_scale + """ + return self.shift_scale(None, width) + + def shift_scale(self, delta: Optional[int], width: Optional[int]) -> IntSpan: + """ + Return a new ``IntSpan`` with the lower and upper bounds shifted by + ``delta`` and scaled so that the width is ``width``. + + Args: + delta: The value to shift by + width: The new width + + Returns: + A new ``IntSpan`` instance + + MEOS Functions: + intspan_shift_scale + """ + d = delta if delta is not None else 0 + w = width if width is not None else 0 + modified = intspan_shift_scale(self._inner, d, w, delta is not None, + width is not None) + return IntSpan(_inner=modified) + + # ------------------------- Topological Operations -------------------------------- + + def is_adjacent(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` is adjacent to ``other``. That is, they share + a bound but only one of them contains it. + + Args: + other: object to compare with + + Returns: + True if adjacent, False otherwise + + MEOS Functions: + adjacent_span_span, adjacent_span_spanset, adjacent_intspan_int + """ + if isinstance(other, int): + return adjacent_intspan_int(self._inner, other) + else: + return super().is_adjacent(other) + + def contains(self, content: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_span_span, contains_intspan_int + """ + if isinstance(content, int): + return contains_intspan_int(self._inner, content) + else: + return super().contains(content) + + def is_same(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` and the bounding period of ``other`` is the + same. + + Args: + other: object to compare with + + Returns: + True if equal, False otherwise + + MEOS Functions: + same_period_temporal + """ + if isinstance(other, int): + return span_eq(self._inner, int_to_intspan(other)) + else: + return super().is_same(other) + + # ------------------------- Position Operations --------------------------- + def is_left(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` is strictly left ``other``. That is, + ``self`` ends before ``other`` starts. + + Args: + other: object to compare with + + Returns: + True if left, False otherwise + + MEOS Functions: + left_span_span, left_span_spanset, left_intspan_int + """ + if isinstance(other, int): + return left_intspan_int(self._inner, other) + else: + return super().is_left(other) + + def is_over_or_left(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` is left ``other`` allowing overlap. That is, + ``self`` ends before ``other`` ends (or at the same value). + + Args: + other: object to compare with + + Returns: + True if over or left, False otherwise + + MEOS Functions: + overleft_span_span, overleft_span_spanset, overleft_intspan_int + """ + if isinstance(other, int): + return overleft_intspan_int(self._inner, other) + else: + return super().is_over_or_left(other) + + def is_right(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` is strictly right ``other``. That is, ``self`` + starts after ``other`` ends. + + Args: + other: object to compare with + + Returns: + True if right, False otherwise + + MEOS Functions: + right_span_span, right_span_spanset, right_intspan_int + """ + if isinstance(other, int): + return right_intspan_int(self._inner, other) + else: + return super().is_right(other) + + def is_over_or_right(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` is right ``other`` allowing overlap. That is, + ``self`` starts after ``other`` starts (or at the same value). + + Args: + other: object to compare with + + Returns: + True if overlapping or after, False otherwise + + MEOS Functions: + overright_span_span, overright_span_spanset, overright_intspan_int + """ + if isinstance(other, int): + return overright_intspan_int(self._inner, other) + else: + return super().is_over_or_right(other) + + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[int, IntSpan, IntSpanSet]) -> float: + """ + Returns the distance between ``self`` and ``other``. + + Args: + other: object to compare with + + Returns: + A float value + + MEOS Functions: + distance_span_span, distance_span_spanset, distance_intspan_int, + """ + if isinstance(other, int): + return distance_intspan_int(self._inner, other) + else: + return super().distance(other) + + # ------------------------- Set Operations -------------------------------- + @overload + def intersection(self, other: int) -> Optional[int]: + ... + + @overload + def intersection(self, other: IntSpan) -> Optional[IntSpan]: + ... + + @overload + def intersection(self, other: IntSpanSet) -> Optional[IntSpanSet]: + ... + + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + A :class:`Collection[int]` instance. The actual class depends on + ``other``. + + MEOS Functions: + intersection_span_span, intersection_spanset_span, + intersection_intspan_int + """ + from .intspanset import IntSpanSet + if isinstance(other, int): + return intersection_intspan_int(self._inner, other) + elif isinstance(other, IntSpan): + result = intersection_span_span(self._inner, other._inner) + return IntSpan(_inner=result) if result is not None else None + elif isinstance(other, IntSpanSet): + result = intersection_spanset_span(other._inner, self._inner) + return IntSpanSet(_inner=result) if result is not None else None + else: + super().intersection(other) + + def minus(self, other: Union[int, IntSpan, IntSpanSet]) -> IntSpanSet: + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: object to diff with + + Returns: + A :class:`IntSpanSet` instance. + + MEOS Functions: + minus_span_span, minus_spanset_span, minus_intspan_int + """ + from .intspanset import IntSpanSet + if isinstance(other, int): + result = minus_intspan_int(self._inner, other) + elif isinstance(other, IntSpan): + result = minus_span_span(self._inner, other._inner) + elif isinstance(other, IntSpanSet): + result = minus_spanset_span(other._inner, self._inner) + else: + result = super().minus(other) + return IntSpanSet(_inner=result) if result is not None else None + + def union(self, other: Union[int, IntSpan, IntSpanSet]) -> IntSpanSet: + """ + Returns the union of ``self`` and ``other``. + + Args: + other: object to merge with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + union_spanset_span, union_span_span, union_intspan_int + """ + from .intspanset import IntSpanSet + if isinstance(other, int): + result = union_intspan_int(self._inner, other) + elif isinstance(other, IntSpan): + result = union_span_span(self._inner, other._inner) + elif isinstance(other, IntSpanSet): + result = union_spanset_span(other._inner, self._inner) + else: + result = super().union(other) + return IntSpanSet(_inner=result) if result is not None else None diff --git a/pymeos/pymeos/collections/number/intspanset.py b/pymeos/pymeos/collections/number/intspanset.py new file mode 100644 index 00000000..f04f5f04 --- /dev/null +++ b/pymeos/pymeos/collections/number/intspanset.py @@ -0,0 +1,489 @@ +from __future__ import annotations + +from typing import Union, overload, Optional, TYPE_CHECKING, List + +from pymeos_cffi import intspanset_in, intspanset_out, spanset_width, \ + intspanset_shift_scale, adjacent_intspanset_int, contains_intspanset_int, \ + spanset_eq, int_to_intspanset, left_intspanset_int, \ + overleft_intspanset_int, right_intspanset_int, overright_intspanset_int, \ + distance_intspanset_int, intersection_intspanset_int, \ + union_intspanset_int, minus_intspanset_int, intspanset_floatspanset + +from pymeos.collections import SpanSet + +if TYPE_CHECKING: + from .intspan import IntSpan + from .floatspanset import FloatSpanSet + + +class IntSpanSet(SpanSet[int]): + """ + Class for representing lists of disjoint intspans. + + ``IntSpanSet`` objects can be created with a single argument of type string + as in MobilityDB. + + >>> IntSpanSet(string='{[8, 10], [11, 1]}') + + Another possibility is to give a list specifying the composing + spans, which can be instances of ``str`` or ``IntSpan``. The composing + spans must be given in increasing order. + + >>> IntSpanSet(span_list=['[8, 10]', '[11, 12]']) + >>> IntSpanSet(span_list=[IntSpan('[8, 10]'), IntSpan('[11, 12]')]) + + """ + + __slots__ = ['_inner'] + + _mobilitydb_name = 'intspanset' + + _parse_function = intspanset_in + _parse_value_function = lambda span: intspanset_in(span)[0] if isinstance(span, str) else span._inner[0] + + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + intspanset_out + """ + return intspanset_out(self._inner) + + # ------------------------- Conversions ----------------------------------- + + def to_span(self) -> IntSpan: + """ + Returns a span that encompasses ``self``. + + Returns: + A new :class:`IntSpan` instance + + MEOS Functions: + spanset_span + """ + from .intspan import IntSpan + return IntSpan(_inner=super().to_span()) + + def to_floatspanset(self) -> FloatSpanSet: + """ + Converts ``self`` to a :class:`FloatSpanSet` instance. + + Returns: + A new :class:`FloatSpanSet` instance + + MEOS Functions: + intspanset_floatspanset + """ + from .floatspanset import FloatSpanSet + return FloatSpanSet(_inner=intspanset_floatspanset(self._inner)) + + # ------------------------- Accessors ------------------------------------- + + def width(self, ignore_gaps: Optional[bool] = False) -> float: + """ + Returns the width of the spanset. By default, i.e., when the second + argument is False, the function takes into account the gaps within, + i.e., returns the sum of the widths of the spans within. + Otherwise, the function returns the width of the spanset ignoring + any gap, i.e., the width from the lower bound of the first span to + the upper bound of the last span. + + Parameters: + ignore_gaps: Whether to take into account potential gaps in + the spanset. + + Returns: + A `float` representing the duration of the spanset + + MEOS Functions: + spanset_width + """ + return spanset_width(self._inner, ignore_gaps) + + def start_span(self) -> IntSpan: + """ + Returns the first span in ``self``. + Returns: + A :class:`IntSpan` instance + + MEOS Functions: + spanset_start_span + """ + from .intspan import IntSpan + return IntSpan(_inner=super().start_span()) + + def end_span(self) -> IntSpan: + """ + Returns the last span in ``self``. + Returns: + A :class:`IntSpan` instance + + MEOS Functions: + spanset_end_span + """ + from .intspan import IntSpan + return IntSpan(_inner=super().end_span()) + + def span_n(self, n: int) -> IntSpan: + """ + Returns the n-th span in ``self``. + Returns: + A :class:`IntSpan` instance + + MEOS Functions: + spanset_span_n + """ + from .intspan import IntSpan + return IntSpan(_inner=super().span_n(n)) + + def spans(self) -> List[IntSpan]: + """ + Returns the list of spans in ``self``. + Returns: + A :class:`list[IntSpan]` instance + + MEOS Functions: + spanset_spans + """ + from .intspan import IntSpan + ps = super().spans() + return [IntSpan(_inner=ps[i]) for i in range(self.num_spans())] + + # ------------------------- Transformations ------------------------------- + + def shift(self, delta: int) -> IntSpanSet: + """ + Return a new ``IntSpanSet`` with the lower and upper bounds shifted by + ``delta``. + + Args: + delta: The value to shift by + + Returns: + A new ``IntSpanSet`` instance + + MEOS Functions: + intspanset_shift_scale + """ + return self.shift_scale(delta, None) + + def scale(self, width: int) -> IntSpanSet: + """ + Return a new ``IntSpanSet`` with the lower and upper bounds scaled so + that the width is ``width``. + + Args: + width: The new width + + Returns: + A new ``IntSpanSet`` instance + + MEOS Functions: + intspanset_shift_scale + """ + return self.shift_scale(None, width) + + def shift_scale(self, delta: Optional[int], width: Optional[int]) -> IntSpanSet: + """ + Return a new ``IntSpanSet`` with the lower and upper bounds shifted by + ``delta`` and scaled so that the width is ``width``. + + Args: + delta: The value to shift by + width: The new width + + Returns: + A new ``IntSpanSet`` instance + + MEOS Functions: + intspanset_shift_scale + """ + d = delta if delta is not None else 0 + w = width if width is not None else 0 + modified = intspanset_shift_scale(self._inner, d, w, delta is not None, + width is not None) + return IntSpanSet(_inner=modified) + + # ------------------------- Topological Operations -------------------------------- + + def is_adjacent(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` is adjacent to ``other``. That is, they share + a bound but only one of them contains it. + + Args: + other: object to compare with + + Returns: + True if adjacent, False otherwise + + MEOS Functions: + adjacent_spanset_span, adjacent_spanset_spanset, + adjacent_intspanset_int + """ + if isinstance(other, int): + return adjacent_intspanset_int(self._inner, other) + else: + return super().is_adjacent(other) + + def contains(self, content: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_spanset_span, contains_spanset_spanset, + contains_intspanset_int + """ + if isinstance(content, int): + return contains_intspanset_int(self._inner, content) + else: + return super().contains(content) + + def is_same(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` and the bounding period of ``other`` is the + same. + + Args: + other: object to compare with + + Returns: + True if equal, False otherwise + + MEOS Functions: + same_period_temporal + """ + if isinstance(other, int): + return spanset_eq(self._inner, int_to_intspanset(other)) + else: + return super().is_same(other) + + # ------------------------- Position Operations --------------------------- + def is_left(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` is strictly left of ``other``. That is, + ``self`` ends before ``other`` starts. + + Args: + other: object to compare with + + Returns: + True if left, False otherwise + + MEOS Functions: + left_span_span, left_span_spanset, left_intspan_int + """ + if isinstance(other, int): + return left_intspanset_int(self._inner, other) + else: + return super().is_left(other) + + def is_over_or_left(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` is left ``other`` allowing overlap. That is, + ``self`` ends before ``other`` ends (or at the same value). + + Args: + other: object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + overleft_span_span, overleft_span_spanset, overleft_intspan_int + """ + if isinstance(other, int): + return overleft_intspanset_int(self._inner, other) + else: + return super().is_over_or_left(other) + + def is_right(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` is strictly right ``other``. That is, ``self`` + starts after ``other`` ends. + + Args: + other: object to compare with + + Returns: + True if right, False otherwise + + MEOS Functions: + right_span_span, right_span_spanset, right_intspan_int + """ + if isinstance(other, int): + return right_intspanset_int(self._inner, other) + else: + return super().is_right(other) + + def is_over_or_right(self, other: Union[int, IntSpan, IntSpanSet]) -> bool: + """ + Returns whether ``self`` is right ``other`` allowing overlap. That is, + ``self`` starts after ``other`` starts (or at the same value). + + Args: + other: object to compare with + + Returns: + True if overlapping or after, False otherwise + + MEOS Functions: + overright_spanset_span, overright_spanset_spanset, + overright_intspanset_int + """ + if isinstance(other, int): + return overright_intspanset_int(self._inner, other) + else: + return super().is_over_or_right(other) + + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[int, IntSpan, IntSpanSet]) -> float: + """ + Returns the distance between ``self`` and ``other``. + + Args: + other: object to compare with + + Returns: + A float value + + MEOS Functions: + distance_spanset_span, distance_spanset_spanset, + distance_intspanset_int + """ + if isinstance(other, int): + return distance_intspanset_int(self._inner, other) + else: + return super().distance(other) + + # ------------------------- Set Operations -------------------------------- + @overload + def intersection(self, other: int) -> Optional[int]: + ... + + @overload + def intersection(self, other: IntSpan) -> Optional[IntSpanSet]: + ... + + @overload + def intersection(self, other: IntSpanSet) -> Optional[IntSpanSet]: + ... + + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + An int or a :class:`IntSpanSet` instance. The actual class depends + on ``other``. + + MEOS Functions: + intersection_intspanset_int, intersection_spanset_spanset, + intersection_spanset_span + """ + if isinstance(other, int): + result = intersection_intspanset_int(self._inner, other) + else: + result = super().intersection(other) + return IntSpanSet(_inner=result) if result is not None else None + + def __mul__(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: object to intersect with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_intspanset_int, intersection_spanset_spanset, + intersection_spanset_span + """ + return self.intersection(other) + + def minus(self, other: Union[int, IntSpan, IntSpanSet]) -> IntSpanSet: + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: object to diff with + + Returns: + A :class:`IntSpanSet` instance. + + MEOS Functions: + minus_spanset_span, minus_spanset_spanset, minus_intspanset_int + """ + if isinstance(other, int): + result = minus_intspanset_int(self._inner, other) + else: + result = super().minus(other) + return IntSpanSet(_inner=result) if result is not None else None + + def __sub__(self, other): + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: object to diff with + + Returns: + A :class:`IntSpanSet` instance. + + MEOS Functions: + minus_spanset_span, minus_spanset_spanset, + minus_intspanset_int + """ + return self.minus(other) + + def union(self, other: Union[int, IntSpan, IntSpanSet]) -> IntSpanSet: + """ + Returns the union of ``self`` and ``other``. + + Args: + other: object to merge with + + Returns: + A :class:`IntSpanSet` instance. + + MEOS Functions: + union_intspanset_int, union_spanset_spanset, + union_spanset_span + """ + if isinstance(other, int): + result = union_intspanset_int(self._inner, other) + else: + result = super().union(other) + return IntSpanSet(_inner=result) if result is not None else None + + def __add__(self, other): + """ + Returns the union of ``self`` and ``other``. + + Args: + other: object to merge with + + Returns: + A :class:`IntSpanSet` instance. + + MEOS Functions: + union_intspanset_int, union_spanset_spanset, + union_spanset_span + """ + return self.union(other) diff --git a/pymeos/pymeos/collections/text/__init__.py b/pymeos/pymeos/collections/text/__init__.py new file mode 100644 index 00000000..275caed5 --- /dev/null +++ b/pymeos/pymeos/collections/text/__init__.py @@ -0,0 +1,3 @@ +from .textset import TextSet + +__all__ = ['TextSet'] diff --git a/pymeos/pymeos/collections/text/textset.py b/pymeos/pymeos/collections/text/textset.py new file mode 100644 index 00000000..b878054e --- /dev/null +++ b/pymeos/pymeos/collections/text/textset.py @@ -0,0 +1,252 @@ +from __future__ import annotations + +from typing import Optional, overload, Union + +from pymeos_cffi import * + +from ..base import Set + + +class TextSet(Set[str]): + """ + Class for representing a set of text values. + + ``TextSet`` objects can be created with a single argument of type string as + in MobilityDB. + + >>> TextSet(string='{a, b, c, def}') + + Another possibility is to create a ``TextSet`` object from a list of strings. + + >>> TextSet(elements=['a', 'b', 'c', 'def']) + + + """ + + __slots__ = ['_inner'] + + _mobilitydb_name = 'textset' + + _parse_function = textset_in + _parse_value_function = lambda x: x + _make_function = textset_make + + # ------------------------- Constructors ---------------------------------- + + # ------------------------- Output ---------------------------------------- + + def __str__(self): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + textset_out + """ + return textset_out(self._inner) + + # ------------------------- Conversions ----------------------------------- + + def to_spanset(self): + raise NotImplementedError() + + def to_span(self): + raise NotImplementedError() + + # ------------------------- Accessors ------------------------------------- + + def start_element(self): + """ + Returns the first element in ``self``. + + Returns: + A :class:`str` instance + + MEOS Functions: + textset_start_value + """ + return textset_start_value(self._inner) + + def end_element(self): + """ + Returns the last element in ``self``. + + Returns: + A :class:`str` instance + + MEOS Functions: + textset_end_value + """ + return textset_end_value(self._inner) + + def element_n(self, n: int): + """ + Returns the ``n``-th element in ``self``. + + Args: + n: The 0-based index of the element to return. + + Returns: + A :class:`str` instance + + MEOS Functions: + textset_value_n + """ + super().element_n(n) + return text2cstring(textset_value_n(self._inner, n + 1)[0]) + + def elements(self): + """ + Returns the elements in ``self``. + + Returns: + A list of :class:`str` instances + + MEOS Functions: + textset_values + """ + elems = textset_values(self._inner) + return [text2cstring(elems[i]) for i in range(self.num_elements())] + + # ------------------------- Topological Operations -------------------------------- + + def contains(self, content: Union[TextSet, str]) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_set_set, contains_textset_text + """ + if isinstance(content, str): + return contains_textset_text(self._inner, content) + else: + return super().contains(content) + + + # ------------------------- Transformations -------------------------------- + def lowercase(self): + """ + Returns a new textset that is the result of appling uppercase to ``self`` + + Returns: + A :class:`str` instance + + MEOS Functions: + textset_lower + """ + return self.__class__(_inner=textset_lower(self._inner)) + + def uppercase(self): + """ + Returns a new textset that is the result of appling uppercase to ``self`` + + Returns: + A :class:`str` instance + + MEOS Functions: + textset_upper + """ + return self.__class__(_inner=textset_upper(self._inner)) + + # ------------------------- Set Operations -------------------------------- + + @overload + def intersection(self, other: str) -> Optional[str]: + ... + + @overload + def intersection(self, other: TextSet) -> Optional[TextSet]: + ... + + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: A :class:`TextSet` or :class:`str` instance + + Returns: + An object of the same type as ``other`` or ``None`` if the intersection is empty. + + MEOS Functions: + intersection_textset_text, intersection_set_set + """ + if isinstance(other, str): + result = intersection_textset_text(self._inner, other) + return text2cstring(result[0]) if result is not None else None + elif isinstance(other, TextSet): + result = intersection_set_set(self._inner, other._inner) + return TextSet(_inner=result) if result is not None else None + else: + return super().intersection(other) + + def minus(self, other: Union[TextSet, str]) -> Optional[TextSet]: + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: A :class:`TextSet` or :class:`str` instance + + Returns: + A :class:`TextSet` instance or ``None`` if the difference is empty. + + MEOS Functions: + minus_textset_text, minus_set_set + """ + if isinstance(other, str): + result = minus_textset_text(self._inner, other) + return TextSet(_inner=result) if result is not None else None + elif isinstance(other, TextSet): + result = minus_set_set(self._inner, other._inner) + return TextSet(_inner=result) if result is not None else None + else: + return super().minus(other) + + def subtract_from(self, other: str) -> Optional[str]: + """ + Returns the difference of ``other`` and ``self``. + + Args: + other: A :class:`str` instance + + Returns: + A :class:`str` instance. + + MEOS Functions: + minus_geo_geoset + + See Also: + :meth:`minus` + """ + result = minus_text_textset(other, self._inner) + return text2cstring(result[0]) if result is not None else None + + def union(self, other: Union[TextSet, str]) -> TextSet: + """ + Returns the union of ``self`` and ``other``. + + Args: + other: A :class:`TextSet` or :class:`str` instance + + Returns: + A :class:`TextSet` instance. + + MEOS Functions: + union_textset_text, union_set_set + """ + if isinstance(other, str): + result = union_textset_text(self._inner, other) + return TextSet(_inner=result) if result is not None else None + elif isinstance(other, TextSet): + result = union_set_set(self._inner, other._inner) + return TextSet(_inner=result) if result is not None else None + else: + return super().union(other) diff --git a/pymeos/pymeos/time/__init__.py b/pymeos/pymeos/collections/time/__init__.py similarity index 66% rename from pymeos/pymeos/time/__init__.py rename to pymeos/pymeos/collections/time/__init__.py index d2dd1f70..433fc4de 100644 --- a/pymeos/pymeos/time/__init__.py +++ b/pymeos/pymeos/collections/time/__init__.py @@ -1,7 +1,7 @@ +from .timestampset import TimestampSet from .period import Period from .periodset import PeriodSet from .time import Time -from .timestampset import TimestampSet from datetime import datetime, timedelta -__all__ = ['Time', 'datetime', 'TimestampSet', 'Period', 'PeriodSet', 'timedelta'] +__all__ = ['Time', 'TimestampSet', 'Period', 'PeriodSet', 'datetime', 'timedelta'] diff --git a/pymeos/pymeos/collections/time/period.py b/pymeos/pymeos/collections/time/period.py new file mode 100644 index 00000000..ddf07176 --- /dev/null +++ b/pymeos/pymeos/collections/time/period.py @@ -0,0 +1,640 @@ +from __future__ import annotations + +from datetime import datetime, timedelta +from typing import Optional, Union, overload, TYPE_CHECKING, get_args + +from dateutil.parser import parse +from pymeos_cffi import * + +from .time_collection import TimeCollection +from ..base.span import Span + +if TYPE_CHECKING: + from ...temporal import Temporal + from ...boxes import Box + from .periodset import PeriodSet + from .time import Time + from .timestampset import TimestampSet + + +class Period(Span[datetime], TimeCollection): + """ + Class for representing sets of contiguous timestamps between a lower and + an upper bound. The bounds may be inclusive or not. + + ``Period`` objects can be created with a single argument of type string + as in MobilityDB. + + >>> Period('(2019-09-08 00:00:00+01, 2019-09-10 00:00:00+01)') + + Another possibility is to provide the ``lower`` and ``upper`` named + parameters (of type str or datetime), and optionally indicate whether the + bounds are inclusive or exclusive (by default, the lower bound is inclusive + and the upper is exclusive): + + >>> Period(lower='2019-09-08 00:00:00+01', upper='2019-09-10 00:00:00+01') + >>> Period(lower='2019-09-08 00:00:00+01', upper='2019-09-10 00:00:00+01', lower_inc=False, upper_inc=True) + >>> Period(lower=parse('2019-09-08 00:00:00+01'), upper=parse('2019-09-10 00:00:00+01'), upper_inc=True) + """ + + __slots__ = ['_inner'] + + _mobilitydb_name = 'tstzspan' + + _parse_function = period_in + _parse_value_function = lambda x: pg_timestamptz_in(x, -1) if isinstance(x, str) else datetime_to_timestamptz(x) + _make_function = period_make + + # ------------------------- Constructors ---------------------------------- + + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + period_out + """ + return period_out(self._inner) + + # ------------------------- Conversions ----------------------------------- + + def to_spanset(self) -> PeriodSet: + """ + Returns a period set containing ``self``. + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + span_to_spanset + """ + from .periodset import PeriodSet + return PeriodSet(_inner=super().to_spanset()) + + def to_periodset(self) -> PeriodSet: + """ + Returns a period set containing ``self``. + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + span_to_spanset + """ + return self.to_spanset() + + # ------------------------- Accessors ------------------------------------- + def lower(self) -> datetime: + """ + Returns the lower bound of a period + + Returns: + The lower bound of the period as a :class:`datetime.datetime` + + MEOS Functions: + period_lower + """ + + return timestamptz_to_datetime(period_lower(self._inner)) + + def upper(self) -> datetime: + """ + Returns the upper bound of a period + + Returns: + The upper bound of the period as a :class:`datetime.datetime` + + MEOS Functions: + period_upper + """ + return timestamptz_to_datetime(period_upper(self._inner)) + + def duration(self) -> timedelta: + """ + Returns the duration of the period. + + Returns: + A :class:`datetime.timedelta` instance representing the duration of + the period + + MEOS Functions: + period_duration + """ + return interval_to_timedelta(period_duration(self._inner)) + + def duration_in_seconds(self) -> float: + """ + Returns the duration of the period. + + Returns: + Returns a `float` representing the duration of the period in seconds + + MEOS Functions: + span_width + """ + return self.width() + + # ------------------------- Transformations ------------------------------- + def shift(self, delta: timedelta) -> Period: + """ + Returns a new period that is the result of shifting ``self`` by ``delta`` + + Examples: + >>> Period('[2000-01-01, 2000-01-10]').shift(timedelta(days=2)) + >>> 'Period([2000-01-03 00:00:00+01, 2000-01-12 00:00:00+01])' + + Args: + delta: :class:`datetime.timedelta` instance to shift + + Returns: + A new :class:`Period` instance + + MEOS Functions: + period_shift_scale + """ + return self.shift_scale(shift=delta) + + def scale(self, duration: timedelta) -> Period: + """ + Returns a new period that starts as ``self`` but has duration ``duration`` + + Examples: + >>> Period('[2000-01-01, 2000-01-10]').scale(timedelta(days=2)) + >>> 'Period([2000-01-01 00:00:00+01, 2000-01-03 00:00:00+01])' + + Args: + duration: :class:`datetime.timedelta` instance representing the + duration of the new period + + Returns: + A new :class:`Period` instance + + MEOS Functions: + period_shift_scale + """ + return self.shift_scale(duration=duration) + + def shift_scale(self, shift: Optional[timedelta] = None, + duration: Optional[timedelta] = None) -> Period: + """ + Returns a new period that starts at ``self`` shifted by ``shift`` and + has duration ``duration`` + + Examples: + >>> Period('[2000-01-01, 2000-01-10]').shift_scale(shift=timedelta(days=2), duration=timedelta(days=4)) + >>> 'Period([2000-01-03 00:00:00+01, 2000-01-07 00:00:00+01])' + + Args: + shift: :class:`datetime.timedelta` instance to shift + duration: :class:`datetime.timedelta` instance representing the + duration of the new period + + Returns: + A new :class:`Period` instance + + MEOS Functions: + period_shift_scale + """ + assert shift is not None or duration is not None, 'shift and scale deltas must not be both None' + modified = period_shift_scale( + self._inner, + timedelta_to_interval(shift) if shift else None, + timedelta_to_interval(duration) if duration else None, + ) + return Period(_inner=modified) + + # ------------------------- Topological Operations ------------------------ + def is_adjacent(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is temporally adjacent to ``other``. That is, + they share a bound but only one of them contains it. + + Examples: + >>> Period('[2012-01-01, 2012-01-02)').is_adjacent(Period('[2012-01-02, 2012-01-03]')) + >>> True + >>> Period('[2012-01-01, 2012-01-02]').is_adjacent(Period('[2012-01-02, 2012-01-03]')) + >>> False # Both contain bound + >>> Period('[2012-01-01, 2012-01-02)').is_adjacent(Period('(2012-01-02, 2012-01-03]')) + >>> False # Neither contain bound + + Args: + other: temporal object to compare with + + Returns: + True if adjacent, False otherwise + + MEOS Functions: + adjacent_span_span, adjacent_span_spanset, adjacent_period_timestamp, + adjacent_period_timestampset, adjacent_period_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return adjacent_period_timestamp(self._inner, + datetime_to_timestamptz(other)) + elif isinstance(other, Temporal): + return adjacent_span_span(self._inner, temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return adjacent_span_span(self._inner, other.to_period()._inner) + else: + return super().is_adjacent(other) + + def is_contained_in(self, container: Union[Period, PeriodSet, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is temporally contained in ``container``. + + Examples: + >>> Period('[2012-01-02, 2012-01-03]').is_contained_in(Period('[2012-01-01, 2012-01-04]')) + >>> True + >>> Period('(2012-01-01, 2012-01-02)').is_contained_in(Period('[2012-01-01, 2012-01-02]')) + >>> True + >>> Period('[2012-01-01, 2012-01-02]').is_contained_in(Period('(2012-01-01, 2012-01-02)')) + >>> False + + Args: + container: temporal object to compare with + + Returns: + True if contained, False otherwise + + MEOS Functions: + contained_span_span, contained_span_spanset, contained_period_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(container, Temporal): + return self.is_contained_in(container.period()) + elif isinstance(container, Box): + return self.is_contained_in(container.to_period()) + else: + return super().is_contained_in(container) + + def contains(self, content: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` temporally contains ``content``. + + Examples: + >>> Period('[2012-01-01, 2012-01-04]').contains(Period('[2012-01-02, 2012-01-03]')) + >>> True + >>> Period('[2012-01-01, 2012-01-02]').contains(Period('(2012-01-01, 2012-01-02)')) + >>> True + >>> Period('(2012-01-01, 2012-01-02)').contains(Period('[2012-01-01, 2012-01-02]')) + >>> False + + Args: + content: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_span_span, contains_span_spanset, contains_period_timestamp, + contains_period_timestampset, contains_period_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(content, datetime): + return contains_period_timestamp(self._inner, + datetime_to_timestamptz(content)) + elif isinstance(content, Temporal): + return self.contains(content.period()) + elif isinstance(content, get_args(Box)): + return self.contains(content.to_period()) + else: + return super().contains(content) + + def overlaps(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` temporally overlaps ``other``. That is, both + share at least an instant + + Examples: + >>> Period('[2012-01-01, 2012-01-02]').overlaps(Period('[2012-01-02, 2012-01-03]')) + >>> True + >>> Period('[2012-01-01, 2012-01-02)').overlaps(Period('[2012-01-02, 2012-01-03]')) + >>> False + >>> Period('[2012-01-01, 2012-01-02)').overlaps(Period('(2012-01-02, 2012-01-03]')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if overlaps, False otherwise + + MEOS Functions: + overlaps_span_span, overlaps_span_spanset, + overlaps_period_timestampset, overlaps_period_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overlaps_span_span(self._inner, + timestamp_to_period(datetime_to_timestamptz(other))) + elif isinstance(other, Temporal): + return self.overlaps(other.period()) + elif isinstance(other, get_args(Box)): + return self.overlaps(other.to_period()) + else: + return super().overlaps(other) + + def is_same(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` and the bounding period of ``other`` is the same. + + Args: + other: temporal object to compare with + + Returns: + True if equal, False otherwise + + MEOS Functions: + same_period_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, Temporal): + return self.is_same(other.period()) + elif isinstance(other, get_args(Box)): + return self.is_same(other.to_period()) + elif isinstance(other, datetime): + return span_eq(self._inner, + timestamp_to_period(datetime_to_timestamptz(other))) + else: + return super().is_same(other) + + # ------------------------- Position Operations --------------------------- + def is_left(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is strictly before ``other``. That is, + ``self`` ends before ``other`` starts. + + Examples: + >>> Period('[2012-01-01, 2012-01-02)').is_left(Period('[2012-01-02, 2012-01-03]')) + >>> True + >>> Period('[2012-01-01, 2012-01-02)').is_left(Period('(2012-01-02, 2012-01-03]')) + >>> True + >>> Period('[2012-01-01, 2012-01-02]').is_left(Period('[2012-01-02, 2012-01-03]')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + left_span_span, left_span_spanset, before_period_timestamp, + before_period_timestampset, before_period_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overafter_timestamp_period(datetime_to_timestamptz(other), + self._inner) + elif isinstance(other, Temporal): + return self.is_left(other.period()) + elif isinstance(other, get_args(Box)): + return self.is_left(other.to_period()) + else: + return super().is_left(other) + + def is_over_or_left(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is before ``other`` allowing overlap. That is, + ``self`` ends before ``other`` ends (or at the same time). + + Examples: + >>> Period('[2012-01-01, 2012-01-02)').is_over_or_left(Period('[2012-01-02, 2012-01-03]')) + >>> True + >>> Period('[2012-01-01, 2012-01-02]').is_over_or_left(Period('[2012-01-02, 2012-01-03]')) + >>> True + >>> Period('[2012-01-03, 2012-01-05]').is_over_or_left(Period('[2012-01-01, 2012-01-04]')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + overleft_span_span, overleft_span_spanset, overbefore_period_timestamp, + overbefore_period_timestampset, overbefore_period_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overbefore_period_timestamp(self._inner, + datetime_to_timestamptz(other)) + elif isinstance(other, Temporal): + return self.is_over_or_left(other.period()) + elif isinstance(other, get_args(Box)): + return self.is_over_or_left(other.to_period()) + else: + return super().is_over_or_left(other) + + def is_right(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is strictly after ``other``. That is, ``self`` + starts after ``other`` ends. + + Examples: + >>> Period('[2012-01-02, 2012-01-03]').is_right(Period('[2012-01-01, 2012-01-02)')) + >>> True + >>> Period('(2012-01-02, 2012-01-03]').is_right(Period('[2012-01-01, 2012-01-02)')) + >>> True + >>> Period('[2012-01-02, 2012-01-03]').is_right(Period('[2012-01-01, 2012-01-02]')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if after, False otherwise + + MEOS Functions: + right_span_span, right_span_spanset, after_period_timestamp, + after_period_timestampset, after_period_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overbefore_timestamp_period(datetime_to_timestamptz(other), + self._inner) + elif isinstance(other, Temporal): + return self.is_right(other.period()) + elif isinstance(other, get_args(Box)): + return self.is_right(other.to_period()) + else: + return super().is_right(other) + + def is_over_or_right(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is after ``other`` allowing overlap. That is, + ``self`` starts after ``other`` starts (or at the same time). + + Examples: + >>> Period('[2012-01-02, 2012-01-03]').is_over_or_right(Period('[2012-01-01, 2012-01-02)')) + >>> True + >>> Period('[2012-01-02, 2012-01-03]').is_over_or_right(Period('[2012-01-01, 2012-01-02]')) + >>> True + >>> Period('[2012-01-02, 2012-01-03]').is_over_or_right(Period('[2012-01-01, 2012-01-03]')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if overlapping or after, False otherwise + + MEOS Functions: + overright_span_span, overright_span_spanset, overafter_period_timestamp, + overafter_period_timestampset, overafter_period_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overafter_period_timestamp(self._inner, + datetime_to_timestamptz(other)) + elif isinstance(other, Temporal): + return self.is_over_or_right(other.period()) + elif isinstance(other, get_args(Box)): + return self.is_over_or_right(other.to_period()) + else: + return super().is_over_or_right(other) + + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[Time, Box, Temporal]) -> timedelta: + """ + Returns the temporal distance between ``self`` and ``other``. + + Args: + other: temporal object to compare with + + Returns: + A :class:`datetime.timedelta` instance + + MEOS Functions: + distance_span_span, distance_spanset_span, distance_period_timestamp + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return timedelta(seconds=distance_period_timestamp(self._inner, + datetime_to_timestamptz(other))) + elif isinstance(other, Temporal): + return self.distance(other.period()) + elif isinstance(other, get_args(Box)): + return self.distance(other.to_period()) + else: + return timedelta(seconds=super().distance(other)) + + # ------------------------- Set Operations -------------------------------- + @overload + def intersection(self, other: datetime) -> Optional[datetime]: + ... + + @overload + def intersection(self, other: Period) -> Optional[Period]: + ... + + @overload + def intersection(self, other: Union[TimestampSet, PeriodSet]) -> Optional[PeriodSet]: + ... + + def intersection(self, other: Time) -> Optional[Time]: + """ + Returns the temporal intersection of ``self`` and ``other``. + + Args: + other: temporal object to intersect with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_span_span, intersection_spanset_span, + intersection_period_timestamp + """ + from .periodset import PeriodSet + from .timestampset import TimestampSet + if isinstance(other, datetime): + result = intersection_period_timestamp(self._inner, + datetime_to_timestamptz(other)) + return timestamptz_to_datetime(result) if result is not None else None + elif isinstance(other, TimestampSet): + return self.intersection(other.to_periodset()) + elif isinstance(other, Period): + result = intersection_span_span(self._inner, other._inner) + return Period(_inner=result) if result is not None else None + elif isinstance(other, PeriodSet): + result = intersection_spanset_span(other._inner, self._inner) + return PeriodSet(_inner=result) if result is not None else None + else: + super().intersection(other) + + def minus(self, other: Time) -> PeriodSet: + """ + Returns the temporal difference of ``self`` and ``other``. + + Args: + other: temporal object to diff with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + minus_period_timestamp, minus_span_spanset, minus_span_span + """ + from .periodset import PeriodSet + from .timestampset import TimestampSet + if isinstance(other, datetime): + result = minus_period_timestamp(self._inner, + datetime_to_timestamptz(other)) + elif isinstance(other, TimestampSet): + return self.minus(other.to_periodset()) + elif isinstance(other, Period): + result = minus_span_span(self._inner, other._inner) + elif isinstance(other, PeriodSet): + result = minus_span_spanset(self._inner, other._inner) + else: + result = super().minus(other) + return PeriodSet(_inner=result) if result is not None else None + + def union(self, other: Time) -> PeriodSet: + """ + Returns the temporal union of ``self`` and ``other``. + + Args: + other: temporal object to merge with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + union_period_timestamp, union_spanset_span, union_span_span + """ + from .periodset import PeriodSet + from .timestampset import TimestampSet + if isinstance(other, datetime): + result = union_period_timestamp(self._inner, + datetime_to_timestamptz(other)) + elif isinstance(other, TimestampSet): + result = super().union(other) + elif isinstance(other, Period): + result = super().union(other) + elif isinstance(other, PeriodSet): + result = super().union(other) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + return PeriodSet(_inner=result) if result is not None else None + + # ------------------------- Plot Operations ------------------------------- + def plot(self, *args, **kwargs): + from ...plotters import TimePlotter + return TimePlotter.plot_period(self, *args, **kwargs) diff --git a/pymeos/pymeos/collections/time/periodset.py b/pymeos/pymeos/collections/time/periodset.py new file mode 100644 index 00000000..45147c93 --- /dev/null +++ b/pymeos/pymeos/collections/time/periodset.py @@ -0,0 +1,816 @@ +from __future__ import annotations + +from datetime import timedelta, datetime +from typing import Optional, Union, List, overload, get_args +from typing import TYPE_CHECKING + +from pymeos_cffi import * + +from .time_collection import TimeCollection + +if TYPE_CHECKING: + from ...temporal import Temporal + from ...boxes import Box + from .period import Period + from .timestampset import TimestampSet + from .time import Time + +from ..base.spanset import SpanSet + + +class PeriodSet(SpanSet[datetime], TimeCollection): + """ + Class for representing lists of disjoint periods. + + ``PeriodSet`` objects can be created with a single argument of type string + as in MobilityDB. + + >>> PeriodSet(string='{[2019-09-08 00:00:00+01, 2019-09-10 00:00:00+01], [2019-09-11 00:00:00+01, 2019-09-12 00:00:00+01]}') + + Another possibility is to give a list specifying the composing + periods, which can be instances of ``str`` or ``Period``. The composing + periods must be given in increasing order. + + >>> PeriodSet(span_list=['[2019-09-08 00:00:00+01, 2019-09-10 00:00:00+01]', '[2019-09-11 00:00:00+01, 2019-09-12 00:00:00+01]']) + >>> PeriodSet(span_list=[Period('[2019-09-08 00:00:00+01, 2019-09-10 00:00:00+01]'), Period('[2019-09-11 00:00:00+01, 2019-09-12 00:00:00+01]')]) + + """ + + __slots__ = ['_inner'] + + _mobilitydb_name = 'tstzspanset' + + _parse_function = periodset_in + _parse_value_function = lambda period: period_in(period)[0] if isinstance(period, str) else period._inner[0] + + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + periodset_out + """ + return periodset_out(self._inner) + + # ------------------------- Conversions ----------------------------------- + + def to_span(self) -> Period: + """ + Returns a period that encompasses ``self``. + + Returns: + A new :class:`Period` instance + + MEOS Functions: + spanset_span + """ + from .period import Period + return Period(_inner=super().to_span()) + + def to_period(self) -> Period: + """ + Returns a period that encompasses ``self``. + + Returns: + A new :class:`Period` instance + + MEOS Functions: + spanset_span + """ + return self.to_span() + + # ------------------------- Accessors ------------------------------------- + def duration(self, ignore_gaps: Optional[bool] = False) -> timedelta: + """ + Returns the duration of the periodset. By default, i.e., when the + second argument is False, the function takes into account the gaps within, + i.e., returns the sum of the durations of the periods within. + Otherwise, the function returns the duration of the periodset ignoring + any gap, i.e., the duration from the lower bound of the first period to + the upper bound of the last period. + + Parameters: + ignore_gaps: Whether to take into account potential time gaps in + the periodset. + + Returns: + A :class:`datetime.timedelta` instance representing the duration of + the periodset + + MEOS Functions: + periodset_duration + """ + return interval_to_timedelta(periodset_duration(self._inner, ignore_gaps)) + + def num_timestamps(self) -> int: + """ + Returns the number of timestamps in ``self``. + Returns: + An :class:`int` + + MEOS Functions: + periodset_num_timestamps + """ + return periodset_num_timestamps(self._inner) + + def start_timestamp(self) -> datetime: + """ + Returns the first timestamp in ``self``. + Returns: + A :class:`datetime` instance + + MEOS Functions: + periodset_start_timestamp + """ + return timestamptz_to_datetime(periodset_start_timestamp(self._inner)) + + def end_timestamp(self) -> datetime: + """ + Returns the last timestamp in ``self``. + Returns: + A :class:`datetime` instance + + MEOS Functions: + periodset_end_timestamp + """ + return timestamptz_to_datetime(periodset_end_timestamp(self._inner)) + + def timestamp_n(self, n: int) -> datetime: + """ + Returns the n-th timestamp in ``self``. + Returns: + A :class:`datetime` instance + + MEOS Functions: + periodset_timestamp_n + """ + if n < 0 or n >= self.num_timestamps(): + raise IndexError(f'Index {n} out of bounds') + return timestamptz_to_datetime(periodset_timestamp_n(self._inner, n + 1)) + + def timestamps(self) -> List[datetime]: + """ + Returns the list of distinct timestamps in ``self``. + Returns: + A :class:`list[datetime]` instance + + MEOS Functions: + periodset_timestamps + """ + ts, count = periodset_timestamps(self._inner) + return [timestamptz_to_datetime(ts[i]) for i in range(count)] + + def num_periods(self) -> int: + """ + Returns the number of periods in ``self``. + Returns: + An :class:`int` + + MEOS Functions: + spanset_num_spans + """ + return self.num_spans() + + def start_span(self) -> Period: + """ + Returns the first period in ``self``. + Returns: + A :class:`Period` instance + + MEOS Functions: + spanset_start_span + """ + from .period import Period + return Period(_inner=super().start_span()) + + def start_period(self) -> Period: + """ + Returns the first period in ``self``. + Returns: + A :class:`Period` instance + + MEOS Functions: + spanset_start_span + """ + return self.start_span() + + def end_span(self) -> Period: + """ + Returns the last period in ``self``. + Returns: + A :class:`Period` instance + + MEOS Functions: + spanset_end_span + """ + from .period import Period + return Period(_inner=super().end_span()) + + def end_period(self) -> Period: + """ + Returns the last period in ``self``. + Returns: + A :class:`Period` instance + + MEOS Functions: + spanset_end_span + """ + return self.end_span() + + def span_n(self, n: int) -> Period: + """ + Returns the n-th period in ``self``. + Returns: + A :class:`Period` instance + + MEOS Functions: + spanset_span_n + """ + from .period import Period + return Period(_inner=super().span_n(n)) + + def period_n(self, n: int) -> Period: + """ + Returns the n-th period in ``self``. + Returns: + A :class:`Period` instance + + MEOS Functions: + spanset_span_n + """ + return self.span_n(n) + + def spans(self) -> List[Period]: + """ + Returns the list of periods in ``self``. + Returns: + A :class:`list[Period]` instance + + MEOS Functions: + spanset_spans + """ + from .period import Period + ps = super().spans() + return [Period(_inner=ps[i]) for i in range(self.num_spans())] + + def periods(self) -> List[Period]: + """ + Returns the list of periods in ``self``. + Returns: + A :class:`list[Period]` instance + + MEOS Functions: + spanset_spans + """ + return self.spans() + + # ------------------------- Transformations ------------------------------- + def shift(self, delta: timedelta) -> PeriodSet: + """ + Returns a new periodset that is the result of shifting ``self`` by + ``delta`` + + Examples: + >>> Period('[2000-01-01, 2000-01-10]').shift(timedelta(days=2)) + >>> 'Period([2000-01-03 00:00:00+01, 2000-01-12 00:00:00+01])' + + Args: + delta: :class:`datetime.timedelta` instance to shift + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + periodset_shift_scale + """ + return self.shift_scale(shift=delta) + + def scale(self, duration: timedelta) -> PeriodSet: + """ + Returns a new periodset that starts as ``self`` but has duration + ``duration`` + + Examples: + >>> Period('[2000-01-01, 2000-01-10]').scale(timedelta(days=2)) + >>> 'Period([2000-01-01 00:00:00+01, 2000-01-03 00:00:00+01])' + + Args: + duration: :class:`datetime.timedelta` instance representing the + duration of the new period + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + periodset_shift_scale + """ + return self.shift_scale(duration=duration) + + def shift_scale(self, shift: Optional[timedelta] = None, + duration: Optional[timedelta] = None) -> PeriodSet: + """ + Returns a new periodset that starts at ``self`` shifted by ``shift`` + and has duration ``duration`` + + Examples: + >>> Period('[2000-01-01, 2000-01-10]').shift_scale(shift=timedelta(days=2), duration=timedelta(days=4)) + >>> 'Period([2000-01-03 00:00:00+01, 2000-01-07 00:00:00+01])' + + Args: + shift: :class:`datetime.timedelta` instance to shift + duration: :class:`datetime.timedelta` instance representing the + duration of the new period + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + periodset_shift_scale + """ + assert shift is not None or duration is not None, 'shift and scale deltas must not be both None' + ps = periodset_shift_scale( + self._inner, + timedelta_to_interval(shift) if shift else None, + timedelta_to_interval(duration) if duration else None + ) + return PeriodSet(_inner=ps) + + # ------------------------- Topological Operations ------------------------ + def is_adjacent(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is temporally adjacent to ``other``. That is, + they share a bound but only one of them contains it. + + Examples: + >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_adjacent(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> True + >>> PeriodSet('{[2012-01-01, 2012-01-02]}').is_adjacent(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> False # Both contain bound + >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_adjacent(PeriodSet('{[(2012-01-02, 2012-01-03]]}')) + >>> False # Neither contain bound + + Args: + other: temporal object to compare with + + Returns: + True if adjacent, False otherwise + + MEOS Functions: + adjacent_spanset_span, adjacent_spanset_spanset, + adjacent_periodset_timestamp, adjacent_periodset_timestampset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return adjacent_periodset_timestamp(self._inner, + datetime_to_timestamptz(other)) + elif isinstance(other, Temporal): + return self.is_adjacent(other.period()) + elif isinstance(other, get_args(Box)): + return self.is_adjacent(other.to_period()) + else: + return super().is_adjacent(other) + + def is_contained_in(self, container: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is temporally contained in ``container``. + + Examples: + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_contained_in(Period('{[2012-01-01, 2012-01-04]}')) + >>> True + >>> PeriodSet('{(2012-01-01, 2012-01-02)}').is_contained_in(Period('{[2012-01-01, 2012-01-02]}')) + >>> True + >>> PeriodSet('{[2012-01-01, 2012-01-02]}').is_contained_in(Period('{(2012-01-01, 2012-01-02)}')) + >>> False + + Args: + container: temporal object to compare with + + Returns: + True if contained, False otherwise + + MEOS Functions: + contained_spanset_span, contained_spanset_spanset, + contained_periodset_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(container, datetime): + return contained_spanset_span(self._inner, + timestamp_to_period(datetime_to_timestamptz(container))) + elif isinstance(container, Temporal): + return self.is_contained_in(container.period()) + elif isinstance(container, get_args(Box)): + return self.is_contained_in(container.to_period()) + else: + return super().is_contained_in(container) + + def contains(self, content: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` temporally contains ``content``. + + Examples: + >>> PeriodSet('{[2012-01-01, 2012-01-04]}').contains(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> True + >>> PeriodSet('{[2012-01-01, 2012-01-02]}').contains(PeriodSet('{(2012-01-01, 2012-01-02)}')) + >>> True + >>> PeriodSet('{(2012-01-01, 2012-01-02)}').contains(PeriodSet('{[2012-01-01, 2012-01-02]}')) + >>> False + + Args: + content: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_spanset_span, contains_spanset_spanset, + contains_periodset_timestamp + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(content, datetime): + return contains_periodset_timestamp(self._inner, + datetime_to_timestamptz(content)) + elif isinstance(content, Temporal): + return self.contains(content.period()) + elif isinstance(content, get_args(Box)): + return self.contains(content.to_period()) + else: + return super().contains(content) + + def __contains__(self, item): + """ + Returns whether ``self`` temporally contains ``content``. + + Examples: + >>> PeriodSet('{[2012-01-01, 2012-01-04]}').contains(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> True + >>> PeriodSet('{[2012-01-01, 2012-01-02]}').contains(PeriodSet('{(2012-01-01, 2012-01-02)}')) + >>> True + >>> PeriodSet('{(2012-01-01, 2012-01-02)}').contains(PeriodSet('{[2012-01-01, 2012-01-02]}')) + >>> False + + Args: + item: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_spanset_span, contains_spanset_spanset, + contains_periodset_timestamp, contains_periodset_timestampset, + contains_periodset_temporal + """ + return self.contains(item) + + def overlaps(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` temporally overlaps ``other``. That is, both + share at least an instant + + Examples: + >>> PeriodSet('{[2012-01-01, 2012-01-02]}').overlaps(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> True + >>> PeriodSet('{[2012-01-01, 2012-01-02)}').overlaps(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> False + >>> PeriodSet('{[2012-01-01, 2012-01-02)}').overlaps(PeriodSet('{(2012-01-02, 2012-01-03]}')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if overlaps, False otherwise + + MEOS Functions: + overlaps_spanset_span, overlaps_spanset_spanset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overlaps_spanset_span(self._inner, + timestamp_to_period(datetime_to_timestamptz(other))) + elif isinstance(other, Temporal): + return self.overlaps(other.period()) + elif isinstance(other, get_args(Box)): + return self.overlaps(other.to_period()) + else: + return super().overlaps(other) + + def is_same(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether the bounding period of `self` is the same as the + bounding period of `other`. + + Args: + other: A time or temporal object to compare to `self`. + + Returns: + True if same, False otherwise. + + See Also: + :meth:`Period.is_same` + """ + return self.to_period().is_same(other) + + # ------------------------- Position Operations --------------------------- + def is_left(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is strictly before ``other``. That is, + ``self`` ends before ``other`` starts. + + Examples: + >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_left(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> True + >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_left(PeriodSet('{(2012-01-02, 2012-01-03]}')) + >>> True + >>> PeriodSet('{[2012-01-01, 2012-01-02]}').is_left(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + before_periodset_timestamp, left_spanset_span, left_spanset_spanset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return before_periodset_timestamp(self._inner, + datetime_to_timestamptz(other)) + elif isinstance(other, Temporal): + return self.is_left(other.period()) + elif isinstance(other, get_args(Box)): + return self.is_left(other.to_period()) + else: + return super().is_left(other) + + def is_over_or_left(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is before ``other`` allowing overlap. That is, + ``self`` ends before ``other`` ends (or at the same time). + + Examples: + >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_over_or_left(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> True + >>> PeriodSet('{[2012-01-01, 2012-01-02]}').is_over_or_left(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> True + >>> PeriodSet('{[2012-01-03, 2012-01-05]}').is_over_or_left(PeriodSet('{[2012-01-01, 2012-01-04]}')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + overleft_spanset_span, overleft_spanset_spanset, + overbefore_periodset_timestamp, overbefore_periodset_timestampset, + overbefore_periodset_temporal + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overbefore_periodset_timestamp(self._inner, + datetime_to_timestamptz(other)) + elif isinstance(other, Temporal): + return self.is_over_or_left(other.period()) + elif isinstance(other, get_args(Box)): + return self.is_over_or_left(other.to_period()) + else: + return super().is_over_or_left(other) + + def is_over_or_right(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is after ``other`` allowing overlap. That is, + ``self`` starts after ``other`` starts (or at the same time). + + Examples: + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_right(PeriodSet('{[2012-01-01, 2012-01-02)}')) + >>> True + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_right(PeriodSet('{[2012-01-01, 2012-01-02]}')) + >>> True + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_right(PeriodSet('{[2012-01-01, 2012-01-03]}')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if overlapping or after, False otherwise + + MEOS Functions: + overright_spanset_span, overright_spanset_spanset, + overafter_periodset_timestamp, overafter_periodset_timestampset, + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overafter_periodset_timestamp(self._inner, + datetime_to_timestamptz(other)) + elif isinstance(other, Temporal): + return self.is_over_or_right(other.period()) + elif isinstance(other, get_args(Box)): + return self.is_over_or_right(other.to_period()) + else: + return super().is_over_or_right(other) + + def is_right(self, other: Union[Time, Box, Temporal]) -> bool: + """ + Returns whether ``self`` is strictly after ``other``.That is, ``self`` + starts after ``other`` ends. + + Examples: + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_right(PeriodSet('{[2012-01-01, 2012-01-02)}')) + >>> True + >>> PeriodSet('{(2012-01-02, 2012-01-03]}').is_right(PeriodSet('{[2012-01-01, 2012-01-02)}')) + >>> True + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_right(PeriodSet('{[2012-01-01, 2012-01-02]}')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if after, False otherwise + + MEOS Functions: + right_spanset_span, right_spanset_spanset, + overbefore_timestamp_periodset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overbefore_timestamp_periodset(datetime_to_timestamptz(other), + self._inner) + elif isinstance(other, Temporal): + return self.is_right(other.period()) + elif isinstance(other, get_args(Box)): + return self.is_right(other.to_period()) + else: + return super().is_right(other) + + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[Time, Box, Temporal]) -> timedelta: + """ + Returns the temporal distance between ``self`` and ``other``. + + Args: + other: temporal object to compare with + + Returns: + A :class:`datetime.timedelta` instance + + MEOS Functions: + distance_periodset_period, distance_periodset_periodset, + distance_periodset_timestamp, distance_periodset_timestampset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return timedelta(seconds=distance_periodset_timestamp(self._inner, + datetime_to_timestamptz(other))) + if isinstance(other, Temporal): + return self.distance(other.period()) + elif isinstance(other, get_args(Box)): + return self.distance(other.to_period()) + else: + return timedelta(seconds=super().distance(other)) + + # ------------------------- Set Operations -------------------------------- + @overload + def intersection(self, other: Period) -> PeriodSet: + ... + + @overload + def intersection(self, other: PeriodSet) -> PeriodSet: + ... + + @overload + def intersection(self, other: datetime) -> datetime: + ... + + def intersection(self, other: Time) -> Union[PeriodSet, datetime, TimestampSet]: + """ + Returns the temporal intersection of ``self`` and ``other``. + + Args: + other: temporal object to intersect with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_periodset_timestamp, intersection_spanset_spanset, + intersection_spanset_span + """ + if isinstance(other, datetime): + result = intersection_periodset_timestamp(self._inner, + datetime_to_timestamptz(other)) + return timestamptz_to_datetime(result) if result is not None else None + else: + result = super().intersection(other) + return PeriodSet(_inner=result) if result is not None else None + + def __mul__(self, other): + """ + Returns the temporal intersection of ``self`` and ``other``. + + Args: + other: temporal object to intersect with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_periodset_timestamp, intersection_spanset_spanset, + intersection_spanset_span + """ + return self.intersection(other) + + def minus(self, other: Time) -> PeriodSet: + """ + Returns the temporal difference of ``self`` and ``other``. + + Args: + other: temporal object to diff with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + minus_spanset_span, minus_spanset_spanset, minus_periodset_timestamp + """ + if isinstance(other, datetime): + result = minus_periodset_timestamp(self._inner, + datetime_to_timestamptz(other)) + else: + result = super().minus(other) + return PeriodSet(_inner=result) if result is not None else None + + def __sub__(self, other): + """ + Returns the temporal difference of ``self`` and ``other``. + + Args: + other: temporal object to diff with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + minus_spanset_span, minus_spanset_spanset, + minus_periodset_timestamp + """ + return self.minus(other) + + def union(self, other: Time) -> PeriodSet: + """ + Returns the temporal union of ``self`` and ``other``. + + Args: + other: temporal object to merge with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + union_periodset_timestamp, union_spanset_spanset, + union_spanset_span + """ + if isinstance(other, datetime): + result = union_periodset_timestamp(self._inner, + datetime_to_timestamptz(other)) + else: + result = super().union(other) + return PeriodSet(_inner=result) if result is not None else None + + def __add__(self, other): + """ + Returns the temporal union of ``self`` and ``other``. + + Args: + other: temporal object to merge with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + union_periodset_timestamp, union_spanset_spanset, + union_spanset_span + """ + return self.union(other) + + # ------------------------- Plot Operations ------------------------------- + def plot(self, *args, **kwargs): + from ...plotters import TimePlotter + return TimePlotter.plot_periodset(self, *args, **kwargs) diff --git a/pymeos/pymeos/time/time.py b/pymeos/pymeos/collections/time/time.py similarity index 99% rename from pymeos/pymeos/time/time.py rename to pymeos/pymeos/collections/time/time.py index 336a665d..c6ee788b 100644 --- a/pymeos/pymeos/time/time.py +++ b/pymeos/pymeos/collections/time/time.py @@ -1,9 +1,9 @@ +from datetime import datetime from typing import Union from .period import Period from .periodset import PeriodSet from .timestampset import TimestampSet -from datetime import datetime Time = Union[datetime, TimestampSet, Period, PeriodSet] """ @@ -13,4 +13,4 @@ - :class:`~pymeos.time.timestampset.TimestampSet` for sets of timestamps - :class:`~pymeos.time.period.Period` for periods of time - :class:`~pymeos.time.periodset.PeriodSet` for sets of periods of time -""" \ No newline at end of file +""" diff --git a/pymeos/pymeos/collections/time/time_collection.py b/pymeos/pymeos/collections/time/time_collection.py new file mode 100644 index 00000000..ae502887 --- /dev/null +++ b/pymeos/pymeos/collections/time/time_collection.py @@ -0,0 +1,19 @@ +from abc import ABC +from datetime import datetime + +from ..base.collection import Collection + + +class TimeCollection(Collection[datetime], ABC): + + def is_before(self, other) -> bool: + return self.is_left(other) + + def is_over_or_before(self, other) -> bool: + return self.is_over_or_left(other) + + def is_over_or_after(self, other) -> bool: + return self.is_over_or_right(other) + + def is_after(self, other) -> bool: + return self.is_right(other) diff --git a/pymeos/pymeos/collections/time/timestampset.py b/pymeos/pymeos/collections/time/timestampset.py new file mode 100644 index 00000000..7f3b6b8b --- /dev/null +++ b/pymeos/pymeos/collections/time/timestampset.py @@ -0,0 +1,731 @@ +from __future__ import annotations + +from datetime import datetime, timedelta +from typing import Optional, List, Union, TYPE_CHECKING, overload, get_args + +from dateutil.parser import parse +from pymeos_cffi import * + +from .time_collection import TimeCollection +from ..base import Set + +if TYPE_CHECKING: + from ...temporal import Temporal + from .period import Period + from .periodset import PeriodSet + from .time import Time + from ...boxes import Box + + +class TimestampSet(Set[datetime], TimeCollection): + """ + Class for representing lists of distinct timestamp values. + + ``TimestampSet`` objects can be created with a single argument of type + string as in MobilityDB. + + >>> TimestampSet(string='{2019-09-08 00:00:00+01, 2019-09-10 00:00:00+01, 2019-09-11 00:00:00+01}') + + Another possibility is to give a tuple or list of composing timestamps, + which can be instances of ``str`` or ``datetime``. The composing timestamps + must be given in increasing order. + + >>> TimestampSet(elements=['2019-09-08 00:00:00+01', '2019-09-10 00:00:00+01', '2019-09-11 00:00:00+01']) + >>> TimestampSet(elements=[parse('2019-09-08 00:00:00+01'), parse('2019-09-10 00:00:00+01'), parse('2019-09-11 00:00:00+01')]) + + """ + + __slots__ = ['_inner'] + + _mobilitydb_name = 'tstzset' + + _parse_function = timestampset_in + _parse_value_function = lambda x: pg_timestamptz_in(x, -1) if isinstance(x, str) else datetime_to_timestamptz(x) + _make_function = timestampset_make + + # ------------------------- Constructors ---------------------------------- + + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + timestampset_out + """ + return timestampset_out(self._inner) + + # ------------------------- Conversions ----------------------------------- + def to_spanset(self) -> PeriodSet: + """ + Returns a PeriodSet that contains a Period for each Timestamp in + ``self``. + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + set_to_spanset + """ + from .periodset import PeriodSet + return PeriodSet(_inner=super().to_spanset()) + + def to_periodset(self) -> PeriodSet: + """ + Returns a PeriodSet that contains a Period for each Timestamp in + ``self``. + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + set_to_spanset + """ + return self.to_spanset() + + def to_span(self) -> Period: + """ + Returns a period that encompasses ``self``. + + Returns: + A new :class:`Period` instance + + MEOS Functions: + set_span + """ + from .period import Period + return Period(_inner=super().to_span()) + + def to_period(self) -> Period: + """ + Returns a period that encompasses ``self``. + + Returns: + A new :class:`Period` instance + + MEOS Functions: + set_span + """ + return self.to_span() + + # ------------------------- Accessors ------------------------------------- + def duration(self) -> timedelta: + """ + Returns the duration of the time ignoring gaps, i.e. the duration from + the first timestamp to the last one. + + Returns: + A :class:`datetime.timedelta` instance representing the duration of + the period + + MEOS Functions: + period_duration + """ + return interval_to_timedelta(period_duration(set_span(self._inner))) + + def start_element(self) -> datetime: + """ + Returns the first timestamp in ``self``. + Returns: + A :class:`datetime` instance + + MEOS Functions: + timestampset_start_timestamp + """ + return timestamptz_to_datetime(timestampset_start_timestamp(self._inner)) + + def end_element(self) -> datetime: + """ + Returns the last timestamp in ``self``. + Returns: + A :class:`datetime` instance + + MEOS Functions: + timestampset_end_timestamp + """ + return timestamptz_to_datetime(timestampset_end_timestamp(self._inner)) + + def element_n(self, n: int) -> datetime: + """ + Returns the n-th timestamp in ``self``. + Returns: + A :class:`datetime` instance + + MEOS Functions: + timestampset_timestamp_n + """ + super().element_n(n) + return timestamptz_to_datetime(timestampset_timestamp_n(self._inner, n + 1)) + + def elements(self) -> List[datetime]: + """ + Returns the list of distinct timestamps in ``self``. + Returns: + A :class:`list[datetime]` instance + + MEOS Functions: + timestampset_timestamps + """ + tss = timestampset_values(self._inner) + return [timestamptz_to_datetime(tss[i]) for i in range(self.num_elements())] + + # ------------------------- Transformations ------------------------------- + def shift(self, delta: timedelta) -> TimestampSet: + """ + Returns a new TimestampSet that is the result of shifting ``self`` by + ``delta`` + + Examples: + >>> TimestampSet('{2000-01-01, 2000-01-10}').shift(timedelta(days=2)) + >>> 'TimestampSet({2000-01-03 00:00:00+01, 2000-01-12 00:00:00+01})' + + Args: + delta: :class:`datetime.timedelta` instance to shift + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + timestampset_shift_scale + """ + return self.shift_scale(shift=delta) + + def scale(self, duration: timedelta) -> TimestampSet: + """ + Returns a new TimestampSet that with the scaled so that the span of + ``self`` is ``duration``. + + Examples: + >>> TimestampSet('{2000-01-01, 2000-01-10}').scale(timedelta(days=2)) + >>> 'TimestampSet({2000-01-01 00:00:00+01, 2000-01-03 00:00:00+01})' + + Args: + duration: :class:`datetime.timedelta` instance representing the + span of the new set + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + timestampset_shift_scale + """ + return self.shift_scale(duration=duration) + + def shift_scale(self, shift: Optional[timedelta] = None, + duration: Optional[timedelta] = None) -> TimestampSet: + """ + Returns a new TimestampSet that is the result of shifting and scaling + ``self``. + + Examples: + >>> TimestampSet('{2000-01-01, 2000-01-10}').shift_scale(shift=timedelta(days=2), duration=timedelta(days=4)) + >>> 'TimestampSet({2000-01-03 00:00:00+01, 2000-01-07 00:00:00+01})' + + Args: + shift: :class:`datetime.timedelta` instance to shift + duration: :class:`datetime.timedelta` instance representing the + span of the new set + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + timestampset_shift_scale + """ + assert shift is not None or duration is not None, 'shift and scale deltas must not be both None' + tss = timestampset_shift_scale( + self._inner, + timedelta_to_interval(shift) if shift else None, + timedelta_to_interval(duration) if duration else None + ) + return TimestampSet(_inner=tss) + + # ------------------------- Topological Operations ------------------------ + def is_adjacent(self, other: Union[Period, PeriodSet, Temporal, Box]) -> bool: + """ + Returns whether ``self`` is temporally adjacent to ``other``. That is, + they share a bound but only one of them contains it. + + Examples: + >>> TimestampSet('{2012-01-01, 2012-01-02}').is_adjacent(Period('[2012-01-02, 2012-01-03]')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').is_adjacent(Period('[2012-01-02, 2012-01-03]')) + >>> False # Both contain bound + >>> TimestampSet('{2012-01-01, 2012-01-02}').is_adjacent(Period('(2012-01-02, 2012-01-03]')) + >>> False # Neither contain bound + + Args: + other: temporal object to compare with + + Returns: + True if adjacent, False otherwise + + MEOS Functions: + adjacent_span_span, adjacent_spanset_span + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, Temporal): + return self.is_adjacent(other.time()) + elif isinstance(other, get_args(Box)): + return self.is_adjacent(other.to_period()) + else: + super().is_adjacent(other) + + def is_contained_in(self, container: Union[Period, PeriodSet, TimestampSet, + Temporal, Box]) -> bool: + """ + Returns whether ``self`` is temporally contained in ``container``. + + Examples: + >>> TimestampSet('{2012-01-02, 2012-01-03}').is_contained_in(Period('[2012-01-01, 2012-01-04]')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').is_contained_in(Period('[2012-01-01, 2012-01-02]')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').is_contained_in(Period('(2012-01-01, 2012-01-02)')) + >>> False + + Args: + container: temporal object to compare with + + Returns: + True if contained, False otherwise + + MEOS Functions: + contained_span_span, contained_span_spanset, contained_set_set, + contained_spanset_spanset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(container, Temporal): + return self.is_contained_in(container.time()) + elif isinstance(container, Box): + return self.is_contained_in(container.to_period()) + else: + return super().is_contained_in(container) + + def contains(self, content: Union[datetime, TimestampSet, Temporal]) -> bool: + """ + Returns whether ``self`` temporally contains ``content``. + + Examples: + >>> TimestampSet('{2012-01-01, 2012-01-04}').contains(parse('2012-01-01]')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01}')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01, 2012-01-03}')) + >>> False + + Args: + content: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_timestampset_timestamp, contains_set_set, + contains_spanset_spanset + """ + from ...temporal import Temporal + if isinstance(content, datetime): + return contains_timestampset_timestamp(self._inner, + datetime_to_timestamptz(content)) + elif isinstance(content, Temporal): + return self.to_spanset().contains(content) + else: + return super().contains(content) + + def __contains__(self, item): + """ + Returns whether ``self`` temporally contains ``content``. + + Examples: + >>> TimestampSet('{2012-01-01, 2012-01-04}').contains(parse('2012-01-01]')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01}')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01, 2012-01-03}')) + >>> False + + Args: + item: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_timestampset_timestamp, contains_set_set, + contains_spanset_spanset + """ + return self.contains(item) + + def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Temporal, + Box]) -> bool: + """ + Returns whether ``self`` temporally overlaps ``other``. That is, both + share at least an instant + + Examples: + >>> TimestampSet('{2012-01-01, 2012-01-02}').overlaps(TimestampSet('{2012-01-02, 2012-01-03}')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').overlaps(Period('[2012-01-02, 2012-01-03]')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').overlaps(Period('(2012-01-02, 2012-01-03]')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if overlaps, False otherwise + + MEOS Functions: + overlaps_set_set, overlaps_span_span, overlaps_spanset_spanset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return contains_timestampset_timestamp(self._inner, + datetime_to_timestamptz(other)) + elif isinstance(other, Temporal): + return self.to_spanset().overlaps(other) + elif isinstance(other, Box): + return self.to_span().overlaps(other) + else: + return super().overlaps(other) + + def is_same(self, other: Union[Time, Temporal, Box]) -> bool: + """ + Returns whether the bounding period of `self` is the same as the + bounding period of `other`. + + Args: + other: A time or temporal object to compare to `self`. + + Returns: + True if same, False otherwise. + + See Also: + :meth:`Period.is_same` + """ + return self.to_period().is_same(other) + + # ------------------------- Position Operations --------------------------- + def is_left(self, other: Union[Time, Temporal, Box]) -> bool: + """ + Returns whether ``self`` is strictly before ``other``. That is, + ``self`` ends before ``other`` starts. + + Examples: + >>> TimestampSet('{2012-01-01, 2012-01-02}').is_left(TimestampSet('{2012-01-03}')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').is_left(Period('(2012-01-02, 2012-01-03]')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').is_left(Period('[2012-01-02, 2012-01-03]')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + overafter_timestamp_period, left_span_span, left_span_spanset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return after_timestamp_timestampset(datetime_to_timestamptz(other), + self._inner) + elif isinstance(other, Temporal): + return self.to_period().is_left(other) + elif isinstance(other, get_args(Box)): + return self.to_period().is_left(other) + else: + return super().is_left(other) + + def is_over_or_left(self, other: Union[Time, Temporal, Box]) -> bool: + """ + Returns whether ``self`` is before ``other`` allowing overlap. That is, + ``self`` ends before ``other`` ends (or at the same time). + + Examples: + >>> TimestampSet('{2012-01-01, 2012-01-02}').is_over_or_left(Period('[2012-01-02, 2012-01-03]')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').is_over_or_left(Period('[2012-01-02, 2012-01-03]')) + >>> True + >>> TimestampSet('{2012-01-03, 2012-01-05}').is_over_or_left(Period('[2012-01-01, 2012-01-04]')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if before, False otherwise + + MEOS Functions: + overbefore_period_timestamp, overleft_span_span, overleft_span_spanset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overafter_timestamp_timestampset(datetime_to_timestamptz(other), + self._inner) + elif isinstance(other, Temporal): + return self.to_period().is_over_or_left(other) + elif isinstance(other, get_args(Box)): + return self.to_period().is_over_or_left(other.to_period()) + else: + return super().is_over_or_left(other) + + def is_over_or_right(self, other: Union[Time, Temporal, Box]) -> bool: + """ + Returns whether ``self`` is after ``other`` allowing overlap. That is, + ``self`` starts after ``other`` starts (or at the same time). + + Examples: + >>> TimestampSet('{2012-01-02, 2012-01-03}').is_over_or_right(Period('[2012-01-01, 2012-01-02)')) + >>> True + >>> TimestampSet('{2012-01-02, 2012-01-03}').is_over_or_right(Period('[2012-01-01, 2012-01-02]')) + >>> True + >>> TimestampSet('{2012-01-02, 2012-01-03}').is_over_or_right(Period('[2012-01-01, 2012-01-03]')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if overlapping or after, False otherwise + + MEOS Functions: + overafter_period_timestamp, overright_span_span, overright_span_spanset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return overbefore_timestamp_timestampset(datetime_to_timestamptz(other), + self._inner) + elif isinstance(other, Temporal): + return self.to_period().is_over_or_right(other) + elif isinstance(other, get_args(Box)): + return self.to_period().is_over_or_right(other) + else: + return super().is_over_or_right(other) + + def is_right(self, other: Union[Time, Temporal, Box]) -> bool: + """ + Returns whether ``self`` is strictly after ``other``. That is, the + first timestamp in ``self`` is after ``other``. + + Examples: + >>> TimestampSet('{2012-01-02, 2012-01-03}').is_right(Period('[2012-01-01, 2012-01-02)')) + >>> True + >>> TimestampSet('{2012-01-02, 2012-01-03}').is_right(TimestampSet('{2012-01-01}')) + >>> True + >>> TimestampSet('{2012-01-02, 2012-01-03}').is_right(Period('[2012-01-01, 2012-01-02]')) + >>> False + + Args: + other: temporal object to compare with + + Returns: + True if after, False otherwise + + MEOS Functions: + overbefore_timestamp_timestampset, right_set_set, right_span_span, + right_span_spanset + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return before_timestamp_timestampset(datetime_to_timestamptz(other), + self._inner) + elif isinstance(other, Temporal): + return self.to_period().is_right(other) + elif isinstance(other, get_args(Box)): + return self.to_period().is_right(other) + else: + return super().is_right(other) + + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[Time, Temporal, Box]) -> timedelta: + """ + Returns the temporal distance between ``self`` and ``other``. + + Args: + other: temporal object to compare with + + Returns: + A :class:`datetime.timedelta` instance + + MEOS Functions: + distance_timestampset_timestamp, distance_set_set, + distance_span_span, distance_spanset_span + """ + from ...temporal import Temporal + from ...boxes import Box + if isinstance(other, datetime): + return timedelta(seconds=distance_timestampset_timestamp(self._inner, datetime_to_timestamptz(other))) + elif isinstance(other, Temporal): + return self.to_period().distance(other) + elif isinstance(other, get_args(Box)): + return self.to_period().distance(other) + else: + return timedelta(seconds=super().distance(other)) + + # ------------------------- Set Operations -------------------------------- + @overload + def intersection(self, other: datetime) -> Optional[datetime]: + ... + + @overload + def intersection(self, other: TimestampSet) -> Optional[TimestampSet]: + ... + + @overload + def intersection(self, other: Union[Period, PeriodSet, Temporal, Box]) -> Optional[PeriodSet]: + ... + + def intersection(self, other: Union[Time, Temporal]) -> Optional[Time]: + """ + Returns the temporal intersection of ``self`` and ``other``. + + Args: + other: temporal object to intersect with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_set_set, intersection_spanset_span, + intersection_spanset_spanset + """ + from .period import Period + from .periodset import PeriodSet + if isinstance(other, datetime): + result = intersection_timestampset_timestamp(self._inner, + datetime_to_timestamptz(other)) + return timestamptz_to_datetime(result) if result is not None else None + elif isinstance(other, TimestampSet): + result = intersection_set_set(self._inner, other._inner) + return TimestampSet(_inner=result) if result is not None else None + elif isinstance(other, Period): + return self.to_periodset().intersection(other) + elif isinstance(other, PeriodSet): + return self.to_periodset().intersection(other) + elif isinstance(other, Temporal): + return self.intersection(other.time()) + elif isinstance(other, get_args(Box)): + return self.intersection(other.to_period()) + else: + return super().intersection(other) + + @overload + def minus(self, other: Union[datetime, TimestampSet]) -> Optional[TimestampSet]: + ... + + @overload + def minus(self, other: Union[Period, PeriodSet, Temporal, Box]) -> Optional[PeriodSet]: + ... + + def minus(self, other: Union[Time, Temporal, Box]) -> Optional[Time]: + """ + Returns the temporal difference of ``self`` and ``other``. + + Args: + other: temporal object to diff with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + minus_timestampset_timestamp, minus_set_set, minus_spanset_span, + minus_spanset_spanset + """ + from .period import Period + from .periodset import PeriodSet + if isinstance(other, datetime): + result = minus_timestampset_timestamp(self._inner, + datetime_to_timestamptz(other)) + return TimestampSet(_inner=result) if result is not None else None + elif isinstance(other, TimestampSet): + result = minus_set_set(self._inner, other._inner) + return TimestampSet(_inner=result) if result is not None else None + elif isinstance(other, Period): + return self.to_periodset().minus(other) + elif isinstance(other, PeriodSet): + return self.to_periodset().minus(other) + elif isinstance(other, Temporal): + return self.minus(other.time()) + elif isinstance(other, get_args(Box)): + return self.minus(other.to_period()) + else: + return super().minus(other) + + def subtract_from(self, other: datetime) -> Optional[datetime]: + """ + Returns the difference of ``other`` and ``self``. + + Args: + other: A :class:`datetime` instance + + Returns: + A :class:`datetime` instance or ``None`` if the difference is empty. + + MEOS Functions: + minus_timestamp_timestampset + + See Also: + :meth:`minus` + """ + return timestamptz_to_datetime(minus_timestamp_timestampset(datetime_to_timestamptz(other), self._inner)) + + @overload + def union(self, other: Union[datetime, TimestampSet]) -> TimestampSet: + ... + + @overload + def union(self, other: Union[Period, PeriodSet, Temporal, Box]) -> PeriodSet: + ... + + def union(self, other: Union[Time, Temporal, Box]) -> Union[PeriodSet, TimestampSet]: + """ + Returns the temporal union of ``self`` and ``other``. + + Args: + other: temporal object to merge with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + union_timestampset_timestamp, union_set_set, union_spanset_span, + union_spanset_spanset + """ + from .period import Period + from .periodset import PeriodSet + if isinstance(other, datetime): + return TimestampSet(_inner=union_timestampset_timestamp(self._inner, datetime_to_timestamptz(other))) + elif isinstance(other, TimestampSet): + return TimestampSet(_inner=union_set_set(self._inner, other._inner)) + elif isinstance(other, Period): + return self.to_periodset().union(other) + elif isinstance(other, PeriodSet): + return self.to_periodset().union(other) + elif isinstance(other, Temporal): + return self.union(other.time()) + elif isinstance(other, get_args(Box)): + return self.union(other.to_period()) + else: + return super().union(other) + + # ------------------------- Comparisons ----------------------------------- + + # ------------------------- Plot Operations ------------------------------- + def plot(self, *args, **kwargs): + from ...plotters import TimePlotter + return TimePlotter.plot_timestampset(self, *args, **kwargs) diff --git a/pymeos/pymeos/db/asyncpg.py b/pymeos/pymeos/db/asyncpg.py index e2f42828..b9277f5a 100644 --- a/pymeos/pymeos/db/asyncpg.py +++ b/pymeos/pymeos/db/asyncpg.py @@ -39,4 +39,4 @@ async def register(cls, connection: asyncpg.connection.Connection) -> None: """ classes = [TimestampSet, Period, PeriodSet, TBox, TBool, TInt, TFloat, TText, STBox, TGeomPoint, TGeogPoint] for cl in classes: - await connection.set_type_codec(cl.__name__.lower(), encoder=str, decoder=cl.read_from_cursor) + await connection.set_type_codec(cl._mobilitydb_name, encoder=str, decoder=cl.read_from_cursor) diff --git a/pymeos/pymeos/db/psycopg.py b/pymeos/pymeos/db/psycopg.py index 694cfb27..5632bfb5 100644 --- a/pymeos/pymeos/db/psycopg.py +++ b/pymeos/pymeos/db/psycopg.py @@ -57,7 +57,7 @@ def register(cls, connection: psycopg.Connection): cursor = connection.cursor() classes = [TimestampSet, Period, PeriodSet, TBox, TBool, TInt, TFloat, TText, STBox, TGeomPoint, TGeogPoint] for cl in classes: - cursor.execute(f'SELECT NULL::{cl.__name__}') + cursor.execute(f'SELECT NULL::{cl._mobilitydb_name}') oid = cursor.description[0][1] connection.adapters.register_loader(oid, _pymeos_loader_factory(cl)) connection.adapters.register_dumper(cl, _PymeosDumper) diff --git a/pymeos/pymeos/db/psycopg2.py b/pymeos/pymeos/db/psycopg2.py index db397282..47cf1e4c 100644 --- a/pymeos/pymeos/db/psycopg2.py +++ b/pymeos/pymeos/db/psycopg2.py @@ -47,6 +47,6 @@ def register(cls, connection: psycopg2.extensions.connection) -> None: # Add MobilityDB types to PostgreSQL adapter and specify the reader function for each type. classes = [TimestampSet, Period, PeriodSet, TBox, TBool, TInt, TFloat, TText, STBox, TGeomPoint, TGeogPoint] for cl in classes: - cursor.execute(f'SELECT NULL::{cl.__name__}') + cursor.execute(f'SELECT NULL::{cl._mobilitydb_name}') oid = cursor.description[0][1] - extensions.register_type(extensions.new_type((oid,), cl.__name__, cl.read_from_cursor)) + extensions.register_type(extensions.new_type((oid,), cl._mobilitydb_name, cl.read_from_cursor)) diff --git a/pymeos/pymeos/main/tbool.py b/pymeos/pymeos/main/tbool.py index f4b063dd..669df77f 100644 --- a/pymeos/pymeos/main/tbool.py +++ b/pymeos/pymeos/main/tbool.py @@ -7,13 +7,16 @@ from pymeos_cffi import * from ..temporal import TInterpolation, Temporal, TInstant, TSequence, TSequenceSet -from ..time import * +from ..collections import * class TBool(Temporal[bool, 'TBool', 'TBoolInst', 'TBoolSeq', 'TBoolSeqSet'], ABC): """ Base class for temporal boolean. """ + + _mobilitydb_name = 'tbool' + BaseClass = bool _parse_function = tbool_in @@ -24,7 +27,8 @@ def __init__(self, _inner) -> None: @staticmethod def from_base_temporal(value: bool, base: Temporal) -> TBool: """ - Create a temporal Boolean from a Boolean value and the time frame of another temporal object. + Create a temporal Boolean from a Boolean value and the time frame of + another temporal object. Args: value: Boolean value. @@ -46,7 +50,8 @@ def from_base_time(value: bool, base: datetime) -> TBoolInst: @staticmethod @overload - def from_base_time(value: bool, base: Union[TimestampSet, Period]) -> TBoolSeq: + def from_base_time(value: bool, base: Union[TimestampSet, Period]) -> \ + TBoolSeq: ... @staticmethod @@ -71,13 +76,17 @@ def from_base_time(value: bool, base: Time) -> TBool: tboolseq_from_base_period, tboolseqset_from_base_periodset """ if isinstance(base, datetime): - return TBoolInst(_inner=tboolinst_make(value, datetime_to_timestamptz(base))) + return TBoolInst(_inner=tboolinst_make(value, + datetime_to_timestamptz(base))) elif isinstance(base, TimestampSet): - return TBoolSeq(_inner=tboolseq_from_base_timestampset(value, base._inner)) + return TBoolSeq(_inner=tboolseq_from_base_timestampset(value, + base._inner)) elif isinstance(base, Period): - return TBoolSeq(_inner=tboolseq_from_base_period(value, base._inner)) + return TBoolSeq(_inner=tboolseq_from_base_period(value, + base._inner)) elif isinstance(base, PeriodSet): - return TBoolSeqSet(_inner=tboolseqset_from_base_periodset(value, base._inner)) + return TBoolSeqSet(_inner=tboolseqset_from_base_periodset(value, + base._inner)) raise TypeError(f'Operation not supported with type {base.__class__}') # ------------------------- Output ---------------------------------------- @@ -141,7 +150,8 @@ def value_at_timestamp(self, timestamp) -> bool: MEOS Function: tbool_value_at_timestamp """ - return tbool_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) + return tbool_value_at_timestamp(self._inner, + datetime_to_timestamptz(timestamp), True) # ------------------------- Ever and Always Comparisons ------------------- def always_eq(self, value: bool) -> bool: @@ -231,7 +241,8 @@ def temporal_not_equal(self, other: Union[bool, Temporal]) -> TBool: # ------------------------- Restrictions ---------------------------------- def at(self, other: Union[bool, Time]) -> TBool: """ - Returns a new temporal boolean with the values of `self` restricted to the time or value `other`. + Returns a new temporal boolean with the values of `self` restricted to + the time or value `other`. Args: other: Time or value to restrict to. @@ -240,7 +251,8 @@ def at(self, other: Union[bool, Time]) -> TBool: A new temporal boolean. MEOS Functions: - tbool_at_value, temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset + tbool_at_value, temporal_at_timestamp, temporal_at_timestampset, + temporal_at_period, temporal_at_periodset """ if isinstance(other, bool): result = tbool_at_value(self._inner, other) @@ -250,7 +262,8 @@ def at(self, other: Union[bool, Time]) -> TBool: def minus(self, other: Union[bool, Time]) -> TBool: """ - Returns a new temporal boolean with the values of `self` restricted to the complement of the time or value + Returns a new temporal boolean with the values of `self` restricted to + the complement of the time or value `other`. Args: @@ -260,7 +273,8 @@ def minus(self, other: Union[bool, Time]) -> TBool: A new temporal boolean. MEOS Functions: - tbool_minus_value, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, + tbool_minus_value, temporal_minus_timestamp, + temporal_minus_timestampset, temporal_minus_period, temporal_minus_periodset """ if isinstance(other, bool): @@ -278,7 +292,8 @@ def temporal_and(self, other: Union[bool, TBool]) -> TBool: other: A temporal or boolean object to combine with `self`. Returns: - A :class:`TBool` with the temporal conjunction of `self` and `other`. + A :class:`TBool` with the temporal conjunction of `self` and + `other`. MEOS Functions: tand_tbool_bool, tand_tbool_tbool @@ -286,7 +301,8 @@ def temporal_and(self, other: Union[bool, TBool]) -> TBool: if isinstance(other, bool): return self.__class__(_inner=tand_tbool_bool(self._inner, other)) elif isinstance(other, TBool): - return self.__class__(_inner=tand_tbool_tbool(self._inner, other._inner)) + return self.__class__(_inner=tand_tbool_tbool(self._inner, + other._inner)) raise TypeError(f'Operation not supported with type {other.__class__}') def __and__(self, other): @@ -320,7 +336,8 @@ def temporal_or(self, other: Union[bool, TBool]) -> TBool: if isinstance(other, bool): return self.__class__(_inner=tor_tbool_bool(self._inner, other)) elif isinstance(other, TBool): - return self.__class__(_inner=tor_tbool_tbool(self._inner, other._inner)) + return self.__class__(_inner=tor_tbool_tbool(self._inner, + other._inner)) raise TypeError(f'Operation not supported with type {other.__class__}') def __or__(self, other): @@ -404,7 +421,8 @@ def when_false(self) -> Optional[PeriodSet]: @staticmethod def read_from_cursor(value, _=None): """ - Reads a :class:`TBool` from a database cursor. Used when automatically loading objects from the database. + Reads a :class:`TBool` from a database cursor. Used when automatically + loading objects from the database. Users should use the class constructor instead. """ if not value: @@ -421,7 +439,6 @@ def read_from_cursor(value, _=None): raise Exception("ERROR: Could not parse temporal boolean value") - class TBoolInst(TInstant[bool, 'TBool', 'TBoolInst', 'TBoolSeq', 'TBoolSeqSet'], TBool): """ Class for representing temporal boolean values at a single instant. @@ -429,30 +446,40 @@ class TBoolInst(TInstant[bool, 'TBool', 'TBoolInst', 'TBoolSeq', 'TBoolSeqSet'], _make_function = tboolinst_make _cast_function = bool - def __init__(self, string: Optional[str] = None, *, value: Optional[Union[str, bool]] = None, + def __init__(self, string: Optional[str] = None, *, + value: Optional[Union[str, bool]] = None, timestamp: Optional[Union[str, datetime]] = None, _inner=None): - super().__init__(string=string, value=value, timestamp=timestamp, _inner=_inner) + super().__init__(string=string, value=value, timestamp=timestamp, + _inner=_inner) -class TBoolSeq(TSequence[bool, 'TBool', 'TBoolInst', 'TBoolSeq', 'TBoolSeqSet'], TBool): +class TBoolSeq(TSequence[bool, 'TBool', 'TBoolInst', 'TBoolSeq', +'TBoolSeqSet'], TBool): """ Class for representing temporal boolean values over a period of time. """ ComponentClass = TBoolInst - def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[Union[str, TBoolInst]]] = None, - lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, int] = False, - interpolation: TInterpolation = TInterpolation.STEPWISE, normalize: bool = True, _inner=None): - super().__init__(string=string, instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, - expandable=expandable, interpolation=interpolation, normalize=normalize, _inner=_inner) + def __init__(self, string: Optional[str] = None, *, + instant_list: Optional[List[Union[str, TBoolInst]]] = None, + lower_inc: bool = True, upper_inc: bool = False, + interpolation: TInterpolation = TInterpolation.STEPWISE, + normalize: bool = True, _inner=None): + super().__init__(string=string, instant_list=instant_list, + lower_inc=lower_inc, upper_inc=upper_inc, + interpolation=interpolation, + normalize=normalize, _inner=_inner) -class TBoolSeqSet(TSequenceSet[bool, 'TBool', 'TBoolInst', 'TBoolSeq', 'TBoolSeqSet'], TBool): +class TBoolSeqSet(TSequenceSet[bool, 'TBool', 'TBoolInst', 'TBoolSeq', +'TBoolSeqSet'], TBool): """ Class for representing temporal boolean values over a period of time with gaps. """ ComponentClass = TBoolSeq - def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List[Union[str, TBoolSeq]]] = None, + def __init__(self, string: Optional[str] = None, *, + sequence_list: Optional[List[Union[str, TBoolSeq]]] = None, normalize: bool = True, _inner=None): - super().__init__(string=string, sequence_list=sequence_list, normalize=normalize, _inner=_inner) + super().__init__(string=string, sequence_list=sequence_list, + normalize=normalize, _inner=_inner) diff --git a/pymeos/pymeos/main/tfloat.py b/pymeos/pymeos/main/tfloat.py index 0a74e9ff..eb0a32bc 100644 --- a/pymeos/pymeos/main/tfloat.py +++ b/pymeos/pymeos/main/tfloat.py @@ -5,11 +5,10 @@ from typing import Optional, List, Union, TYPE_CHECKING, Set, overload from pymeos_cffi import * -from spans.types import floatrange, intrange from .tnumber import TNumber from ..temporal import TInterpolation, Temporal, TInstant, TSequence, TSequenceSet -from ..time import * +from ..collections import * if TYPE_CHECKING: from ..boxes import TBox @@ -17,14 +16,18 @@ class TFloat(TNumber[float, 'TFloat', 'TFloatInst', 'TFloatSeq', 'TFloatSeqSet'], ABC): + _mobilitydb_name = 'tfloat' + BaseClass = float _parse_function = tfloat_in # ------------------------- Constructors ---------------------------------- @staticmethod - def from_base_temporal(value: float, base: Temporal, interpolation: TInterpolation = TInterpolation.LINEAR) -> TFloat: + def from_base_temporal(value: float, base: Temporal, + interpolation: TInterpolation = TInterpolation.LINEAR) -> TFloat: """ - Returns a new temporal float with the value `value` and the temporal frame of `base`. + Returns a new temporal float with the value `value` and the temporal + frame of `base`. Args: value: Value of the temporal float. @@ -47,7 +50,8 @@ def from_base_time(value: float, base: datetime) -> TFloatInst: @staticmethod @overload - def from_base_time(value: float, base: Union[TimestampSet, Period]) -> TFloatSeq: + def from_base_time(value: float, base: Union[TimestampSet, Period]) -> \ + TFloatSeq: ... @staticmethod @@ -56,9 +60,11 @@ def from_base_time(value: float, base: PeriodSet) -> TFloatSeqSet: ... @staticmethod - def from_base_time(value: float, base: Time, interpolation: TInterpolation = None) -> TFloat: + def from_base_time(value: float, base: Time, + interpolation: TInterpolation = None) -> TFloat: """ - Returns a new temporal float with the value `value` and the temporal frame of `base`. + Returns a new temporal float with the value `value` and the temporal + frame of `base`. Args: value: Value of the temporal float. @@ -69,20 +75,25 @@ def from_base_time(value: float, base: Time, interpolation: TInterpolation = Non A new temporal float. MEOS Functions: - tfloatinst_make, tfloatseq_from_base_timestampset, tfloatseq_from_base_time, tfloatseqset_from_base_time + tfloatinst_make, tfloatseq_from_base_timestampset, + tfloatseq_from_base_time, tfloatseqset_from_base_time """ if isinstance(base, datetime): - return TFloatInst(_inner=tfloatinst_make(value, datetime_to_timestamptz(base))) + return TFloatInst(_inner=tfloatinst_make(value, + datetime_to_timestamptz(base))) elif isinstance(base, TimestampSet): - return TFloatSeq(_inner=tfloatseq_from_base_timestampset(value, base._inner)) + return TFloatSeq(_inner=tfloatseq_from_base_timestampset(value, + base._inner)) elif isinstance(base, Period): - return TFloatSeq(_inner=tfloatseq_from_base_period(value, base._inner, interpolation)) + return TFloatSeq(_inner=tfloatseq_from_base_period(value, + base._inner, interpolation)) elif isinstance(base, PeriodSet): - return TFloatSeqSet(_inner=tfloatseqset_from_base_periodset(value, base._inner, interpolation)) + return TFloatSeqSet(_inner=tfloatseqset_from_base_periodset(value, + base._inner, interpolation)) raise TypeError(f'Operation not supported with type {base.__class__}') # ------------------------- Output ---------------------------------------- - def __str__(self, max_decimals=15): + def __str__(self, max_decimals: int = 15): """ Returns a string representation of `self`. @@ -94,12 +105,12 @@ def __str__(self, max_decimals=15): """ return tfloat_out(self._inner, max_decimals) - def as_wkt(self, precision: int = 15) -> str: + def as_wkt(self, max_decimals: int = 15) -> str: """ Returns a WKT representation of `self`. Args: - precision: The number of decimals to use. + max_decimals: The number of decimals to use. Returns: A WKT representation of `self`. @@ -107,13 +118,14 @@ def as_wkt(self, precision: int = 15) -> str: MEOS Functions: tfloat_out """ - return tfloat_out(self._inner, precision) + return tfloat_out(self._inner, max_decimals) # ------------------------- Conversions ---------------------------------- def to_tint(self) -> TInt: """ Returns a new temporal integer with the values of `self` floored. - This operation can only be performed when the interpolation is stepwise or discrete. + This operation can only be performed when the interpolation is stepwise + or discrete. Returns: A new temporal integer. @@ -126,48 +138,46 @@ def to_tint(self) -> TInt: """ from ..factory import _TemporalFactory if self.interpolation() == TInterpolation.LINEAR: - raise ValueError("Cannot convert a temporal float with linear interpolation to a temporal integer") + raise ValueError("Cannot convert a temporal float with linear " \ + "interpolation to a temporal integer") return _TemporalFactory.create_temporal(tfloat_to_tint(self._inner)) - def to_floatrange(self) -> floatrange: + def to_floatrange(self) -> FloatSpan: """ Returns value span of `self`. Returns: - An :class:`floatrange` with the value span of `self`. + An :class:`FloatSpan` with the value span of `self`. MEOS Functions: tnumber_to_span """ - return floatspan_to_floatrange(tnumber_to_span(self._inner)) + return FloatSpan(_inner=tnumber_to_span(self._inner)) # ------------------------- Accessors ------------------------------------- - def value_range(self) -> floatrange: + def value_span(self) -> FloatSpan: """ Returns the value span of `self`. Returns: - An :class:`floatrange` with the value span of `self`. + An :class:`FloatSpan` with the value span of `self`. MEOS Functions: tnumber_to_span """ return self.to_floatrange() - def value_ranges(self) -> List[floatrange]: + def value_spans(self) -> FloatSpanSet: """ Returns the value spans of `self` taking into account gaps. Returns: - A list of :class:`floatrange` with the value spans of `self`. + A :class:`FloatSpanSet` with the value spans of `self`. MEOS Functions: - tfloat_spanset + tnumber_valuespans """ - spanset = tnumber_valuespans(self._inner) - spans = spanset_spans(spanset) - count = spanset_num_spans(spanset) - return [floatspan_to_floatrange(spans[i]) for i in range(count)] + return FloatSpanSet(_inner=(tnumber_valuespans(self._inner))) def start_value(self) -> float: """ @@ -196,7 +206,8 @@ def end_value(self) -> float: def value_set(self) -> Set[float]: """ Returns the set of values of `self`. - Note that when the interpolation is linear, the set will contain only the waypoints. + Note that when the interpolation is linear, the set will contain only + the waypoints. Returns: A :class:`set` with the values of `self`. @@ -240,7 +251,8 @@ def always_equal(self, value: float) -> bool: value: :class:`float` to compare. Returns: - `True` if the values of `self` are always equal to `value`, `False` otherwise. + `True` if the values of `self` are always equal to `value`, + `False` otherwise. MEOS Functions: tfloat_always_eq @@ -255,7 +267,8 @@ def always_not_equal(self, value: float) -> bool: value: :class:`float` to compare. Returns: - `True` if the values of `self` are always not equal to `value`, `False` otherwise. + `True` if the values of `self` are always not equal to `value`, + `False` otherwise. MEOS Functions: tfloat_ever_eq @@ -270,7 +283,8 @@ def always_less(self, value: float) -> bool: value: :class:`float` to compare. Returns: - `True` if the values of `self` are always less than `value`, `False` otherwise. + `True` if the values of `self` are always less than `value`, + `False` otherwise. MEOS Functions: tfloat_always_lt @@ -279,13 +293,15 @@ def always_less(self, value: float) -> bool: def always_less_or_equal(self, value: float) -> bool: """ - Returns whether the values of `self` are always less than or equal to `value`. + Returns whether the values of `self` are always less than or equal to + `value`. Args: value: :class:`float` to compare. Returns: - `True` if the values of `self` are always less than or equal to `value`, `False` otherwise. + `True` if the values of `self` are always less than or equal to + `value`, `False` otherwise. MEOS Functions: tfloat_always_le @@ -294,13 +310,15 @@ def always_less_or_equal(self, value: float) -> bool: def always_greater_or_equal(self, value: float) -> bool: """ - Returns whether the values of `self` are always greater than or equal to `value`. + Returns whether the values of `self` are always greater than or equal + to `value`. Args: value: :class:`float` to compare. Returns: - `True` if the values of `self` are always greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are always greater than or equal to + `value`, `False` otherwise. MEOS Functions: tfloat_ever_lt @@ -315,7 +333,8 @@ def always_greater(self, value: float) -> bool: value: :class:`float` to compare. Returns: - `True` if the values of `self` are always greater than `value`, `False` otherwise. + `True` if the values of `self` are always greater than `value`, + `False` otherwise. MEOS Functions: tfloat_ever_le @@ -330,7 +349,8 @@ def ever_less(self, value: float) -> bool: value: :class:`float` to compare. Returns: - `True` if the values of `self` are ever less than `value`, `False` otherwise. + `True` if the values of `self` are ever less than `value`, + `False` otherwise. MEOS Functions: tfloat_ever_lt @@ -339,13 +359,15 @@ def ever_less(self, value: float) -> bool: def ever_less_or_equal(self, value: float) -> bool: """ - Returns whether the values of `self` are ever less than or equal to `value`. + Returns whether the values of `self` are ever less than or equal to + `value`. Args: value: :class:`float` to compare. Returns: - `True` if the values of `self` are ever less than or equal to `value`, `False` otherwise. + `True` if the values of `self` are ever less than or equal to + `value`, `False` otherwise. MEOS Functions: tfloat_ever_le @@ -360,7 +382,8 @@ def ever_equal(self, value: float) -> bool: value: :class:`float` to compare. Returns: - `True` if the values of `self` are ever equal to `value`, `False` otherwise. + `True` if the values of `self` are ever equal to `value`, `False` + otherwise. MEOS Functions: tfloat_ever_eq @@ -375,7 +398,8 @@ def ever_not_equal(self, value: float) -> bool: value: :class:`float` to compare. Returns: - `True` if the values of `self` are ever not equal to `value`, `False` otherwise. + `True` if the values of `self` are ever not equal to `value`, + `False` otherwise. MEOS Functions: tfloat_always_eq @@ -384,13 +408,15 @@ def ever_not_equal(self, value: float) -> bool: def ever_greater_or_equal(self, value: float) -> bool: """ - Returns whether the values of `self` are ever greater than or equal to `value`. + Returns whether the values of `self` are ever greater than or equal to + `value`. Args: value: :class:`float` to compare. Returns: - `True` if the values of `self` are ever greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are ever greater than or equal to + `value`, `False` otherwise. MEOS Functions: tfloat_always_lt @@ -405,7 +431,8 @@ def ever_greater(self, value: float) -> bool: value: :class:`float` to compare. Returns: - `True` if the values of `self` are ever greater than `value`, `False` otherwise. + `True` if the values of `self` are ever greater than `value`, + `False` otherwise. MEOS Functions: tfloat_always_le @@ -420,7 +447,8 @@ def never_equal(self, value: float) -> bool: value: :class:`float` value to compare. Returns: - `True` if the values of `self` are never equal to `value`, `False` otherwise. + `True` if the values of `self` are never equal to `value`, + `False` otherwise. MEOS Functions: tfloat_ever_eq @@ -435,7 +463,8 @@ def never_not_equal(self, value: float) -> bool: value: :class:`float` value to compare. Returns: - `True` if the values of `self` are never not equal to `value`, `False` otherwise. + `True` if the values of `self` are never not equal to `value`, + `False` otherwise. MEOS Functions: tfloat_always_eq @@ -450,7 +479,8 @@ def never_less(self, value: float) -> bool: value: :class:`float` value to compare. Returns: - `True` if the values of `self` are never less than `value`, `False` otherwise. + `True` if the values of `self` are never less than `value`, + `False` otherwise. MEOS Functions: tfloat_ever_lt @@ -459,13 +489,15 @@ def never_less(self, value: float) -> bool: def never_less_or_equal(self, value: float) -> bool: """ - Returns whether the values of `self` are never less than or equal to `value`. + Returns whether the values of `self` are never less than or equal to + `value`. Args: value: :class:`float` value to compare. Returns: - `True` if the values of `self` are never less than or equal to `value`, `False` otherwise. + `True` if the values of `self` are never less than or equal to + `value`, `False` otherwise. MEOS Functions: tfloat_ever_le @@ -474,13 +506,15 @@ def never_less_or_equal(self, value: float) -> bool: def never_greater_or_equal(self, value: float) -> bool: """ - Returns whether the values of `self` are never greater than or equal to `value`. + Returns whether the values of `self` are never greater than or equal to + `value`. Args: value: :class:`float` value to compare. Returns: - `True` if the values of `self` are never greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are never greater than or equal to + `value`, `False` otherwise. MEOS Functions: tfloat_always_lt @@ -495,7 +529,8 @@ def never_greater(self, value: float) -> bool: value: :class:`float` value to compare. Returns: - `True` if the values of `self` are never greater than `value`, `False` otherwise. + `True` if the values of `self` are never greater than `value`, + `False` otherwise. MEOS Functions: tfloat_always_le @@ -508,7 +543,8 @@ def temporal_equal(self, other: Union[int, float, Temporal]) -> Temporal: Returns the temporal equality relation between `self` and `other`. Args: - other: An :class:`int`, :class:`float` or temporal object to compare to `self`. + other: An :class:`int`, :class:`float` or temporal object to + compare to `self`. Returns: A :class:`TBool` with the result of the temporal equality relation. @@ -527,7 +563,8 @@ def temporal_not_equal(self, other: Union[int, float, Temporal]) -> Temporal: Returns the temporal not equal relation between `self` and `other`. Args: - other: An :class:`int`, :class:`float` or temporal object to compare to `self`. + other: An :class:`int`, :class:`float` or temporal object to + compare to `self`. Returns: A :class:`TBool` with the result of the temporal not equal relation. @@ -546,7 +583,8 @@ def temporal_less(self, other: Union[int, float, Temporal]) -> Temporal: Returns the temporal less than relation between `self` and `other`. Args: - other: An :class:`int`, :class:`float` or temporal object to compare to `self`. + other: An :class:`int`, :class:`float` or temporal object to + compare to `self`. Returns: A :class:`TBool` with the result of the temporal less than relation. @@ -560,15 +598,18 @@ def temporal_less(self, other: Union[int, float, Temporal]) -> Temporal: return super().temporal_less(other) return Temporal._factory(result) - def temporal_less_or_equal(self, other: Union[int, float, Temporal]) -> Temporal: + def temporal_less_or_equal(self, other: Union[int, float, Temporal]) -> \ + Temporal: """ Returns the temporal less or equal relation between `self` and `other`. Args: - other: An :class:`int`, :class:`float` or temporal object to compare to `self`. + other: An :class:`int`, :class:`float` or temporal object to + compare to `self`. Returns: - A :class:`TBool` with the result of the temporal less or equal relation. + A :class:`TBool` with the result of the temporal less or equal + relation. MEOS Functions: tle_tfloat_float, tle_temporal_temporal @@ -579,15 +620,18 @@ def temporal_less_or_equal(self, other: Union[int, float, Temporal]) -> Temporal return super().temporal_less_or_equal(other) return Temporal._factory(result) - def temporal_greater_or_equal(self, other: Union[int, float, Temporal]) -> Temporal: + def temporal_greater_or_equal(self, other: Union[int, float, Temporal]) \ + -> Temporal: """ Returns the temporal greater or equal relation between `self` and `other`. Args: - other: An :class:`int`, :class:`float` or temporal object to compare to `self`. + other: An :class:`int`, :class:`float` or temporal object to + compare to `self`. Returns: - A :class:`TBool` with the result of the temporal greater or equal relation. + A :class:`TBool` with the result of the temporal greater or equal + relation. MEOS Functions: tge_tfloat_float, tge_temporal_temporal @@ -603,10 +647,12 @@ def temporal_greater(self, other: Union[int, float, Temporal]) -> Temporal: Returns the temporal greater than relation between `self` and `other`. Args: - other: An :class:`int`, :class:`float` or temporal object to compare to `self`. + other: An :class:`int`, :class:`float` or temporal object to + compare to `self`. Returns: - A :class:`TBool` with the result of the temporal greater than relation. + A :class:`TBool` with the result of the temporal greater than + relation. MEOS Functions: tgt_tfloat_float, tgt_temporal_temporal @@ -618,10 +664,11 @@ def temporal_greater(self, other: Union[int, float, Temporal]) -> Temporal: return Temporal._factory(result) # ------------------------- Restrictions ---------------------------------- - def at(self, other: Union[int, float, List[int], List[float], - floatrange, List[floatrange], TBox, Time]) -> TFloat: + def at(self, other: Union[float, int, FloatSet, IntSet, FloatSpan, IntSpan, \ + FloatSpanSet, IntSpanSet, TBox, Time]) -> TFloat: """ - Returns a new temporal float with the values of `self` restricted to the value or time `other`. + Returns a new temporal float with the values of `self` restricted to + the value or time `other`. Args: other: Value or time to restrict to. @@ -630,29 +677,27 @@ def at(self, other: Union[int, float, List[int], List[float], A new temporal float. MEOS Functions: - tfloat_at_value, tfloat_at_values, tfloat_at_span, tfloat_at_spanset, + tfloat_at_value, temporal_at_values, tnumber_at_span, tnumber_at_spanset, temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset """ if isinstance(other, int) or isinstance(other, float): result = tfloat_at_value(self._inner, float(other)) - elif isinstance(other, floatrange): - result = tnumber_at_span(self._inner, floatrange_to_floatspan(other)) - elif isinstance(other, list) and (isinstance(other[0], int) or isinstance(other[0], float)): - results = [tfloat_at_value(self._inner, float(other)) for value in other if other is not None] - result = temporal_merge_array(results, len(results)) - elif isinstance(other, list) and (isinstance(other[0], floatrange) or isinstance(other[0], intrange)): - results = [tnumber_at_span(self._inner, value) for value in other if other is not None] - result = temporal_merge_array(results, len(results)) + elif isinstance(other, IntSet): + return super().at(other.to_floatset()) + elif isinstance(other, IntSpan): + return super().at(other.to_floatspan()) + elif isinstance(other, IntSpanSet): + return super().at(other.to_floatspanset()) else: return super().at(other) return Temporal._factory(result) - def minus(self, other: Union[int, float, List[int], List[float], - floatrange, List[floatrange], TBox, Time]) -> Temporal: + def minus(self, other: Union[float, int, FloatSet, IntSet, FloatSpan, IntSpan, \ + FloatSpanSet, IntSpanSet, TBox, Time]) -> Temporal: """ - Returns a new temporal float with the values of `self` restricted to the complement of the time or value - `other`. + Returns a new temporal float with the values of `self` restricted to + the complement of the time or value `other`. Args: other: Time or value to restrict to the complement of. @@ -661,16 +706,18 @@ def minus(self, other: Union[int, float, List[int], List[float], A new temporal float. MEOS Functions: - tfloat_minus_value, tnumber_minus_span, + tfloat_minus_value, temporal_minus_values, tnumber_minus_span, tnumber_minus_spanset, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, temporal_minus_periodset """ if isinstance(other, int) or isinstance(other, float): result = tfloat_minus_value(self._inner, float(other)) - elif isinstance(other, floatrange): - result = tnumber_minus_span(self._inner, floatrange_to_floatspan(other)) - elif isinstance(other, list) and isinstance(other[0], float): - result = reduce(tfloat_minus_value, other, self._inner) + elif isinstance(other, IntSet): + return super().minus(other.to_floatset()) + elif isinstance(other, IntSpan): + return super().minus(other.to_floatspan()) + elif isinstance(other, IntSpanSet): + return super().minus(other.to_floatspanset()) else: return super().minus(other) return Temporal._factory(result) @@ -688,7 +735,8 @@ def value_at_timestamp(self, timestamp) -> float: MEOS Functions: tfloat_value_at_timestamp """ - return tfloat_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) + return tfloat_value_at_timestamp(self._inner, + datetime_to_timestamptz(timestamp), True) def derivative(self) -> TFloat: """ @@ -709,7 +757,8 @@ def to_degrees(self, normalize: bool = True) -> TFloat: Returns a copy of `self` converted from radians to degrees. Args: - normalize: If True, the result will be normalized to the range [0, 360). + normalize: If True, the result will be normalized to the range + [0, 360). Returns: A new :class:`TFloat` instance. @@ -718,7 +767,8 @@ def to_degrees(self, normalize: bool = True) -> TFloat: tfloat_degrees """ from ..factory import _TemporalFactory - return _TemporalFactory.create_temporal(tfloat_degrees(self._inner, normalize)) + return _TemporalFactory.create_temporal(tfloat_degrees(self._inner, + normalize)) def to_radians(self) -> TFloat: """ @@ -733,12 +783,12 @@ def to_radians(self) -> TFloat: from ..factory import _TemporalFactory return _TemporalFactory.create_temporal(tfloat_radians(self._inner)) - def round(self, maxdd : int = 0) -> TFloat: + def round(self, max_decimals: int = 0) -> TFloat: """ Returns `self` rounded to the given number of decimal digits. Args: - maxdd: Maximum number of decimal digits. + max_decimals: Maximum number of decimal digits. Returns: A new :class:`TFloat` instance. @@ -747,10 +797,12 @@ def round(self, maxdd : int = 0) -> TFloat: tfloat_round """ from ..factory import _TemporalFactory - return _TemporalFactory.create_temporal(tfloat_round(self._inner, maxdd)) + return _TemporalFactory.create_temporal(tfloat_round(self._inner, + max_decimals)) # ------------------------- Split Operations ------------------------------ - def value_split(self, size: float, start: Optional[float] = 0) -> List[Temporal]: + def value_split(self, size: float, start: Optional[float] = 0) -> \ + List[Temporal]: """ Splits `self` into fragments with respect to value buckets @@ -764,20 +816,26 @@ def value_split(self, size: float, start: Optional[float] = 0) -> List[Temporal] MEOS Functions: tfloat_value_split """ - tiles, new_count = tfloat_value_split(self._inner, size, start) + fragments, _, count = tfloat_value_split(self._inner, size, start) from ..factory import _TemporalFactory - return [_TemporalFactory.create_temporal(tiles[i]) for i in range(new_count)] + return [_TemporalFactory.create_temporal(fragments[i]) for i in \ + range(count)] - def time_value_split(self, value_start: float, value_size: float, time_start: Union[str, datetime], - duration: Union[str, timedelta]) -> List[Temporal]: + def value_time_split(self, value_size: float, + duration: Union[str, timedelta], + value_start: Optional[float] = 0.0, + time_start: Optional[Union[str, datetime]] = None) -> \ + List[Temporal]: """ Splits `self` into fragments with respect to value and period buckets. Args: - value_start: Start value of the first value bucket. value_size: Size of the value buckets. - time_start: Start time of the first period bucket. duration: Duration of the period buckets. + value_start: Start value of the first value bucket. If None, the + start value used by default is 0 + time_start: Start time of the first period bucket. If None, the + start time used by default is Monday, January 3, 2000. Returns: A list of temporal floats. @@ -785,17 +843,25 @@ def time_value_split(self, value_start: float, value_size: float, time_start: Un MEOS Functions: tfloat_value_time_split """ - st = datetime_to_timestamptz(time_start) if isinstance(time_start, datetime) \ - else pg_timestamptz_in(time_start, -1) - dt = timedelta_to_interval(duration) if isinstance(duration, timedelta) else pg_interval_in(duration, -1) - tiles, new_count = tfloat_value_time_split(self._inner, value_size, value_start, dt, st) - return [Temporal._factory(tiles[i]) for i in range(new_count)] + if time_start is None: + st = pg_timestamptz_in('2000-01-03', -1) + else: + st = datetime_to_timestamptz(time_start) \ + if isinstance(time_start, datetime) \ + else pg_timestamptz_in(time_start, -1) + dt = timedelta_to_interval(duration) \ + if isinstance(duration, timedelta) \ + else pg_interval_in(duration, -1) + fragments, _, _, count = tfloat_value_time_split(self._inner, + value_size, dt, value_start, st) + return [Temporal._factory(fragments[i]) for i in range(count)] # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ - Reads a :class:`TFloat` from a database cursor. Used when automatically loading objects from the database. + Reads a :class:`TFloat` from a database cursor. Used when automatically + loading objects from the database. Users should use the class constructor instead. """ if not value: @@ -818,37 +884,48 @@ def read_from_cursor(value, _=None): raise Exception("ERROR: Could not parse temporal float value") -class TFloatInst(TInstant[float, 'TFloat', 'TFloatInst', 'TFloatSeq', 'TFloatSeqSet'], TFloat): +class TFloatInst(TInstant[float, 'TFloat', 'TFloatInst', 'TFloatSeq', +'TFloatSeqSet'], TFloat): """ Class for representing temporal floats at a single instant. """ _make_function = tfloatinst_make _cast_function = float - def __init__(self, string: Optional[str] = None, *, value: Optional[Union[str, float]] = None, + def __init__(self, string: Optional[str] = None, *, + value: Optional[Union[str, float]] = None, timestamp: Optional[Union[str, datetime]] = None, _inner=None): - super().__init__(string=string, value=value, timestamp=timestamp, _inner=_inner) + super().__init__(string=string, value=value, timestamp=timestamp, + _inner=_inner) -class TFloatSeq(TSequence[float, 'TFloat', 'TFloatInst', 'TFloatSeq', 'TFloatSeqSet'], TFloat): +class TFloatSeq(TSequence[float, 'TFloat', 'TFloatInst', 'TFloatSeq', +'TFloatSeqSet'], TFloat): """ Class for representing temporal floats over a period of time. """ ComponentClass = TFloatInst - def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[Union[str, TFloatInst]]] = None, - lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, float] = False, - interpolation: TInterpolation = TInterpolation.LINEAR, normalize: bool = True, _inner=None): - super().__init__(string=string, instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, - expandable=expandable, interpolation=interpolation, normalize=normalize, _inner=_inner) + def __init__(self, string: Optional[str] = None, *, + instant_list: Optional[List[Union[str, TFloatInst]]] = None, + lower_inc: bool = True, upper_inc: bool = False, + interpolation: TInterpolation = TInterpolation.LINEAR, + normalize: bool = True, _inner=None): + super().__init__(string=string, instant_list=instant_list, + lower_inc=lower_inc, upper_inc=upper_inc, + interpolation=interpolation, + normalize=normalize, _inner=_inner) -class TFloatSeqSet(TSequenceSet[float, 'TFloat', 'TFloatInst', 'TFloatSeq', 'TFloatSeqSet'], TFloat): +class TFloatSeqSet(TSequenceSet[float, 'TFloat', 'TFloatInst', 'TFloatSeq', +'TFloatSeqSet'], TFloat): """ Class for representing temporal floats over a period of time with gaps. """ ComponentClass = TFloatSeq - def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List[Union[str, TFloatSeq]]] = None, + def __init__(self, string: Optional[str] = None, *, + sequence_list: Optional[List[Union[str, TFloatSeq]]] = None, normalize: bool = True, _inner=None): - super().__init__(string=string, sequence_list=sequence_list, normalize=normalize, _inner=_inner) + super().__init__(string=string, sequence_list=sequence_list, + normalize=normalize, _inner=_inner) diff --git a/pymeos/pymeos/main/tint.py b/pymeos/pymeos/main/tint.py index ff8a7b0c..745fd20a 100644 --- a/pymeos/pymeos/main/tint.py +++ b/pymeos/pymeos/main/tint.py @@ -1,15 +1,13 @@ from __future__ import annotations from abc import ABC -from functools import reduce from typing import Optional, Union, List, TYPE_CHECKING, Set, overload from pymeos_cffi import * -from spans.types import intrange, floatrange from .tnumber import TNumber +from ..collections import * from ..temporal import TInterpolation, Temporal, TInstant, TSequence, TSequenceSet -from ..time import * if TYPE_CHECKING: from ..boxes import TBox @@ -17,6 +15,8 @@ class TInt(TNumber[int, 'TInt', 'TIntInst', 'TIntSeq', 'TIntSeqSet'], ABC): + _mobilitydb_name = 'tint' + BaseClass = int _parse_function = tint_in @@ -24,7 +24,8 @@ class TInt(TNumber[int, 'TInt', 'TIntInst', 'TIntSeq', 'TIntSeqSet'], ABC): @staticmethod def from_base_temporal(value: int, base: Temporal) -> TInt: """ - Returns a new temporal integer with the value `value` and the temporal frame of `base`. + Returns a new temporal integer with the value `value` and the temporal + frame of `base`. Args: value: Value of the temporal integer. @@ -57,7 +58,8 @@ def from_base_time(value: int, base: PeriodSet) -> TIntSeqSet: @staticmethod def from_base_time(value: int, base: Time) -> TInt: """ - Returns a new temporal int with the value `value` and the temporal frame of `base`. + Returns a new temporal int with the value `value` and the temporal + frame of `base`. Args: value: Value of the temporal int. @@ -71,13 +73,16 @@ def from_base_time(value: int, base: Time) -> TInt: tintseq_from_base_period, tintseqset_from_base_periodset """ if isinstance(base, datetime): - return TIntInst(_inner=tintinst_make(value, datetime_to_timestamptz(base))) + return TIntInst(_inner=tintinst_make(value, + datetime_to_timestamptz(base))) elif isinstance(base, TimestampSet): - return TIntSeq(_inner=tintseq_from_base_timestampset(value, base._inner)) + return TIntSeq(_inner=tintseq_from_base_timestampset(value, + base._inner)) elif isinstance(base, Period): return TIntSeq(_inner=tintseq_from_base_period(value, base._inner)) elif isinstance(base, PeriodSet): - return TIntSeqSet(_inner=tintseqset_from_base_periodset(value, base._inner)) + return TIntSeqSet(_inner=tintseqset_from_base_periodset(value, + base._inner)) raise TypeError(f'Operation not supported with type {base.__class__}') # ------------------------- Output ---------------------------------------- @@ -119,45 +124,42 @@ def to_tfloat(self) -> TFloat: from ..factory import _TemporalFactory return _TemporalFactory.create_temporal(tint_to_tfloat(self._inner)) - def to_intrange(self) -> intrange: + def to_intspan(self) -> IntSpan: """ Returns value span of `self`. Returns: - An :class:`intrange` with the value span of `self`. + An :class:`IntSpan` with the value span of `self`. MEOS Functions: tnumber_to_span """ - return intspan_to_intrange(tnumber_to_span(self._inner)) + return IntSpan(_inner=tnumber_to_span(self._inner)) # ------------------------- Accessors ------------------------------------- - def value_range(self) -> intrange: + def value_span(self) -> IntSpan: """ Returns the value span of `self`. Returns: - An :class:`intrange` with the value span of `self`. + An :class:`IntSpan` with the value span of `self`. MEOS Functions: tnumber_to_span """ - return self.to_intrange() + return self.to_intspan() - def value_ranges(self) -> List[intrange]: + def value_spans(self) -> IntSpanSet: """ Returns the value spans of `self` taking into account gaps. Returns: - A list of :class:`intrange` with the value spans of `self`. + An :class:`IntSpanSet` with the value spans of `self`. MEOS Functions: - tint_spanset + tnumber_valuespans """ - spanset = tnumber_valuespans(self._inner) - spans = spanset_spans(spanset) - count = spanset_num_spans(spanset) - return [intspan_to_intrange(spans[i]) for i in range(count)] + return IntSpanSet(_inner=tnumber_valuespans(self._inner)) def start_value(self) -> int: """ @@ -233,7 +235,8 @@ def value_at_timestamp(self, timestamp) -> int: MEOS Functions: tint_value_at_timestamp """ - return tint_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) + return tint_value_at_timestamp(self._inner, + datetime_to_timestamptz(timestamp), True) # ------------------------- Ever and Always Comparisons ------------------- def always_less(self, value: int) -> bool: @@ -244,7 +247,8 @@ def always_less(self, value: int) -> bool: value: :class:`int` to compare. Returns: - `True` if the values of `self` are always less than `value`, `False` otherwise. + `True` if the values of `self` are always less than `value`, + `False` otherwise. MEOS Functions: tint_always_lt @@ -253,13 +257,15 @@ def always_less(self, value: int) -> bool: def always_less_or_equal(self, value: int) -> bool: """ - Returns whether the values of `self` are always less than or equal to `value`. + Returns whether the values of `self` are always less than or equal to + `value`. Args: value: :class:`int` to compare. Returns: - `True` if the values of `self` are always less than or equal to `value`, `False` otherwise. + `True` if the values of `self` are always less than or equal to + `value`, `False` otherwise. MEOS Functions: tint_always_le @@ -274,7 +280,8 @@ def always_equal(self, value: int) -> bool: value: :class:`int` to compare. Returns: - `True` if the values of `self` are always equal to `value`, `False` otherwise. + `True` if the values of `self` are always equal to `value`, + `False` otherwise. MEOS Functions: tint_always_eq @@ -289,7 +296,8 @@ def always_not_equal(self, value: int) -> bool: value: :class:`int` to compare. Returns: - `True` if the values of `self` are always not equal to `value`, `False` otherwise. + `True` if the values of `self` are always not equal to `value`, + `False` otherwise. MEOS Functions: tint_ever_eq @@ -298,13 +306,15 @@ def always_not_equal(self, value: int) -> bool: def always_greater_or_equal(self, value: int) -> bool: """ - Returns whether the values of `self` are always greater than or equal to `value`. + Returns whether the values of `self` are always greater than or equal + to `value`. Args: value: :class:`int` to compare. Returns: - `True` if the values of `self` are always greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are always greater than or equal to + `value`, `False` otherwise. MEOS Functions: tint_ever_lt @@ -319,7 +329,8 @@ def always_greater(self, value: int) -> bool: value: :class:`int` to compare. Returns: - `True` if the values of `self` are always greater than `value`, `False` otherwise. + `True` if the values of `self` are always greater than `value`, + `False` otherwise. MEOS Functions: tint_ever_le @@ -334,7 +345,8 @@ def ever_less(self, value: int) -> bool: value: :class:`int` to compare. Returns: - `True` if the values of `self` are ever less than `value`, `False` otherwise. + `True` if the values of `self` are ever less than `value`, + `False` otherwise. MEOS Functions: tint_ever_lt @@ -343,13 +355,15 @@ def ever_less(self, value: int) -> bool: def ever_less_or_equal(self, value: int) -> bool: """ - Returns whether the values of `self` are ever less than or equal to `value`. + Returns whether the values of `self` are ever less than or equal to + `value`. Args: value: :class:`int` to compare. Returns: - `True` if the values of `self` are ever less than or equal to `value`, `False` otherwise. + `True` if the values of `self` are ever less than or equal to + `value`, `False` otherwise. MEOS Functions: tint_ever_le @@ -364,7 +378,8 @@ def ever_equal(self, value: int) -> bool: value: :class:`int` to compare. Returns: - `True` if the values of `self` are ever equal to `value`, `False` otherwise. + `True` if the values of `self` are ever equal to `value`, + `False` otherwise. MEOS Functions: tint_ever_eq @@ -379,7 +394,8 @@ def ever_not_equal(self, value: int) -> bool: value: :class:`int` to compare. Returns: - `True` if the values of `self` are ever not equal to `value`, `False` otherwise. + `True` if the values of `self` are ever not equal to `value`, + `False` otherwise. MEOS Functions: tint_always_eq @@ -388,13 +404,15 @@ def ever_not_equal(self, value: int) -> bool: def ever_greater_or_equal(self, value: int) -> bool: """ - Returns whether the values of `self` are ever greater than or equal to `value`. + Returns whether the values of `self` are ever greater than or equal to + `value`. Args: value: :class:`int` to compare. Returns: - `True` if the values of `self` are ever greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are ever greater than or equal to + `value`, `False` otherwise. MEOS Functions: tint_always_lt @@ -409,7 +427,8 @@ def ever_greater(self, value: int) -> bool: value: :class:`int` to compare. Returns: - `True` if the values of `self` are ever greater than `value`, `False` otherwise. + `True` if the values of `self` are ever greater than `value`, + `False` otherwise. MEOS Functions: tint_always_le @@ -424,7 +443,8 @@ def never_less(self, value: int) -> bool: value: :class:`int` value to compare. Returns: - `True` if the values of `self` are never less than `value`, `False` otherwise. + `True` if the values of `self` are never less than `value`, + `False` otherwise. MEOS Functions: tint_ever_lt @@ -433,13 +453,15 @@ def never_less(self, value: int) -> bool: def never_less_or_equal(self, value: int) -> bool: """ - Returns whether the values of `self` are never less than or equal to `value`. + Returns whether the values of `self` are never less than or equal to + `value`. Args: value: :class:`int` value to compare. Returns: - `True` if the values of `self` are never less than or equal to `value`, `False` otherwise. + `True` if the values of `self` are never less than or equal to + `value`, `False` otherwise. MEOS Functions: tint_ever_le @@ -454,7 +476,8 @@ def never_equal(self, value: int) -> bool: value: :class:`int` value to compare. Returns: - `True` if the values of `self` are never equal to `value`, `False` otherwise. + `True` if the values of `self` are never equal to `value`, + `False` otherwise. MEOS Functions: tint_ever_eq @@ -469,7 +492,8 @@ def never_not_equal(self, value: int) -> bool: value: :class:`int` value to compare. Returns: - `True` if the values of `self` are never not equal to `value`, `False` otherwise. + `True` if the values of `self` are never not equal to `value`, + `False` otherwise. MEOS Functions: tint_always_eq @@ -478,13 +502,15 @@ def never_not_equal(self, value: int) -> bool: def never_greater_or_equal(self, value: int) -> bool: """ - Returns whether the values of `self` are never greater than or equal to `value`. + Returns whether the values of `self` are never greater than or equal to + `value`. Args: value: :class:`int` value to compare. Returns: - `True` if the values of `self` are never greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are never greater than or equal to + `value`, `False` otherwise. MEOS Functions: tint_always_lt @@ -499,7 +525,8 @@ def never_greater(self, value: int) -> bool: value: :class:`int` value to compare. Returns: - `True` if the values of `self` are never greater than `value`, `False` otherwise. + `True` if the values of `self` are never greater than `value`, + `False` otherwise. MEOS Functions: tint_always_le @@ -572,7 +599,8 @@ def temporal_less_or_equal(self, other: Union[int, Temporal]) -> Temporal: other: A :class:`int` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal less or equal relation. + A :class:`TBool` with the result of the temporal less or equal + relation. MEOS Functions: tle_tint_int, tle_temporal_temporal @@ -591,7 +619,8 @@ def temporal_greater_or_equal(self, other: Union[int, Temporal]) -> Temporal: other: A :class:`int` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal greater or equal relation. + A :class:`TBool` with the result of the temporal greater or equal + relation. MEOS Functions: tge_tint_int, tge_temporal_temporal @@ -610,7 +639,8 @@ def temporal_greater(self, other: Union[int, Temporal]) -> Temporal: other: A :class:`int` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal greater than relation. + A :class:`TBool` with the result of the temporal greater than + relation. MEOS Functions: tgt_tint_int, tgt_temporal_temporal @@ -622,11 +652,11 @@ def temporal_greater(self, other: Union[int, Temporal]) -> Temporal: return Temporal._factory(result) # ------------------------- Restrictions ---------------------------------- - def at(self, other: Union[int, List[int], - intrange, floatrange, List[intrange], List[floatrange], TBox, - datetime, TimestampSet, Period, PeriodSet]) -> Temporal: + def at(self, other: Union[int, float, IntSet, FloatSet, IntSpan, FloatSpan, \ + IntSpanSet, FloatSpanSet, TBox, Time]) -> Temporal: """ - Returns a new temporal int with th e values of `self` restricted to the time or value `other`. + Returns a new temporal int with th e values of `self` restricted to + the time or value `other`. Args: other: Time or value to restrict to. @@ -635,24 +665,27 @@ def at(self, other: Union[int, List[int], A new temporal int. MEOS Functions: - tint_at_value, temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset + tint_at_value, temporal_at_values, tnumber_at_span, tnumber_at_spanset, + temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, + temporal_at_periodset """ - if isinstance(other, int): - result = tint_at_value(self._inner, other) - elif isinstance(other, list) and isinstance(other[0], int): - # result = tint_at_values(self._inner, other) - results = [tint_at_value(self._inner, value) for value in other if other is not None] - result = temporal_merge_array(results, len(results)) + if isinstance(other, int) or isinstance(other, float): + result = tint_at_value(self._inner, int(other)) + elif isinstance(other, FloatSet): + return super().at(other.to_intset()) + elif isinstance(other, FloatSpan): + return super().at(other.to_intspan()) + elif isinstance(other, FloatSpanSet): + return super().at(other.to_intspanset()) else: return super().at(other) return Temporal._factory(result) - def minus(self, other: Union[int, List[int], - intrange, floatrange, List[intrange], List[floatrange], TBox, - datetime, TimestampSet, Period, PeriodSet]) -> Temporal: + def minus(self, other: Union[int, float, IntSet, FloatSet, IntSpan, FloatSpan, \ + IntSpanSet, FloatSpanSet, TBox, Time]) -> Temporal: """ - Returns a new temporal int with the values of `self` restricted to the complement of the time or value - `other`. + Returns a new temporal int with the values of `self` restricted to the + complement of the time or value `other`. Args: other: Time or value to restrict to the complement of. @@ -661,36 +694,39 @@ def minus(self, other: Union[int, List[int], A new temporal int. MEOS Functions: - tint_minus_value, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, - temporal_minus_periodset + tint_minus_value, temporal_minus_values, tnumber_minus_span, tnumber_minus_spanset, + temporal_minus_timestamp, temporal_minus_timestampset, + temporal_minus_period, temporal_minus_periodset """ - if isinstance(other, int): - result = tint_minus_value(self._inner, other) - elif isinstance(other, list) and isinstance(other[0], int): - # result = reduce(tint_minus_value, other, self._inner) - # result = tint_minus_values(self._inner, other) - # results = [tint_minus_value(self._inner, value) for value in other if other is not None] - # result = temporal_merge_array(results, len(results)) - result = tint_minus_value(self._inner, other) - for i in 1..len(other): - result = result.minus_value(other[i]) + if isinstance(other, int) or isinstance(other, float): + result = tint_minus_value(self._inner, int(other)) + elif isinstance(other, FloatSet): + return super().minus(other.to_intset()) + elif isinstance(other, FloatSpan): + return super().minus(other.to_intspan()) + elif isinstance(other, FloatSpanSet): + return super().minus(other.to_intspanset()) else: return super().minus(other) return Temporal._factory(result) # ------------------------- Distance -------------------------------------- - def nearest_approach_distance(self, other: Union[int, float, TNumber, TBox]) -> float: + def nearest_approach_distance(self, + other: Union[int, float, TNumber, TBox]) -> float: """ Returns the nearest approach distance between `self` and `other`. Args: - other: A :class:`int`, :class:`float`, :class:`TNumber` or :class:`TBox` to compare to `self`. + other: A :class:`int`, :class:`float`, :class:`TNumber` or + :class:`TBox` to compare to `self`. Returns: - A :class:`float` with the nearest approach distance between `self` and `other`. + A :class:`float` with the nearest approach distance between `self` + and `other`. MEOS Functions: - nad_tint_int, nad_tint_tint, nad_tfloat_float, nad_tfloat_tfloat, nad_tnumber_tbox + nad_tint_int, nad_tint_tint, nad_tfloat_float, nad_tfloat_tfloat, + nad_tnumber_tbox """ if isinstance(other, int): return nad_tint_int(self._inner, other) @@ -714,37 +750,50 @@ def value_split(self, size: int, start: Optional[int] = 0) -> List[TInt]: MEOS Functions: tint_value_split """ - tiles, new_count = tint_value_split(self._inner, size, start) - return [Temporal._factory(tiles[i]) for i in range(new_count)] + fragments, values, count = tint_value_split(self._inner, size, start) + return [Temporal._factory(fragments[i]) for i in range(count)] - def time_value_split(self, value_start: int, value_size: int, time_start: Union[str, datetime], - duration: Union[str, timedelta]) -> List[TInt]: + def value_time_split(self, value_size: int, + duration: Union[str, timedelta], + value_start: Optional[int] = 0, + time_start: Optional[Union[str, datetime]] = None) -> \ + List[TInt]: """ Splits `self` into fragments with respect to value and period buckets. Args: - value_start: Start value of the first value bucket. value_size: Size of the value buckets. - time_start: Start time of the first period bucket. duration: Duration of the period buckets. + value_start: Start value of the first value bucket. If None, the + start value used by default is 0 + time_start: Start time of the first period bucket. If None, the + start time used by default is Monday, January 3, 2000. Returns: - A list of temporal ints. + A list of temporal integers. MEOS Functions: tint_value_time_split """ - st = datetime_to_timestamptz(time_start) if isinstance(time_start, datetime) \ - else pg_timestamptz_in(time_start, -1) - dt = timedelta_to_interval(duration) if isinstance(duration, timedelta) else pg_interval_in(duration, -1) - tiles, new_count = tint_value_time_split(self._inner, value_size, value_start, dt, st) - return [Temporal._factory(tiles[i]) for i in range(new_count)] + if time_start is None: + st = pg_timestamptz_in('2000-01-03', -1) + else: + st = datetime_to_timestamptz(time_start) \ + if isinstance(time_start, datetime) \ + else pg_timestamptz_in(time_start, -1) + dt = timedelta_to_interval(duration) \ + if isinstance(duration, timedelta) \ + else pg_interval_in(duration, -1) + tiles, _, _, count = tint_value_time_split(self._inner, value_size, dt, + value_start, st) + return [Temporal._factory(tiles[i]) for i in range(count)] # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ - Reads a :class:`TInt` from a database cursor. Used when automatically loading objects from the database. + Reads a :class:`TInt` from a database cursor. Used when automatically + loading objects from the database. Users should use the class constructor instead. """ if not value: @@ -768,9 +817,11 @@ class TIntInst(TInstant[int, 'TInt', 'TIntInst', 'TIntSeq', 'TIntSeqSet'], TInt) _make_function = tintinst_make _cast_function = int - def __init__(self, string: Optional[str] = None, *, value: Optional[Union[str, int]] = None, + def __init__(self, string: Optional[str] = None, *, + value: Optional[Union[str, int]] = None, timestamp: Optional[Union[str, datetime]] = None, _inner=None): - super().__init__(string=string, value=value, timestamp=timestamp, _inner=_inner) + super().__init__(string=string, value=value, timestamp=timestamp, + _inner=_inner) class TIntSeq(TSequence[int, 'TInt', 'TIntInst', 'TIntSeq', 'TIntSeqSet'], TInt): @@ -779,19 +830,25 @@ class TIntSeq(TSequence[int, 'TInt', 'TIntInst', 'TIntSeq', 'TIntSeqSet'], TInt) """ ComponentClass = TIntInst - def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[Union[str, TIntInst]]] = None, - lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, int] = False, - interpolation: TInterpolation = TInterpolation.STEPWISE, normalize: bool = True, _inner=None): - super().__init__(string=string, instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, - expandable=expandable, interpolation=interpolation, normalize=normalize, _inner=_inner) + def __init__(self, string: Optional[str] = None, *, + instant_list: Optional[List[Union[str, TIntInst]]] = None, + lower_inc: bool = True, upper_inc: bool = False, + interpolation: TInterpolation = TInterpolation.STEPWISE, + normalize: bool = True, _inner=None): + super().__init__(string=string, instant_list=instant_list, + lower_inc=lower_inc, upper_inc=upper_inc, + interpolation=interpolation, normalize=normalize, _inner=_inner) -class TIntSeqSet(TSequenceSet[int, 'TInt', 'TIntInst', 'TIntSeq', 'TIntSeqSet'], TInt): +class TIntSeqSet(TSequenceSet[int, 'TInt', 'TIntInst', 'TIntSeq', 'TIntSeqSet'], + TInt): """ Class for representing temporal integers over a period of time with gaps. """ ComponentClass = TIntSeq - def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List[Union[str, TIntSeq]]] = None, + def __init__(self, string: Optional[str] = None, *, + sequence_list: Optional[List[Union[str, TIntSeq]]] = None, normalize: bool = True, _inner=None): - super().__init__(string=string, sequence_list=sequence_list, normalize=normalize, _inner=_inner) + super().__init__(string=string, sequence_list=sequence_list, + normalize=normalize, _inner=_inner) diff --git a/pymeos/pymeos/main/tnumber.py b/pymeos/pymeos/main/tnumber.py index 3464f5f0..e90dc925 100644 --- a/pymeos/pymeos/main/tnumber.py +++ b/pymeos/pymeos/main/tnumber.py @@ -1,16 +1,16 @@ from __future__ import annotations from abc import ABC -from typing import Union, List, TYPE_CHECKING, TypeVar +from typing import Union, TYPE_CHECKING, TypeVar from pymeos_cffi import * -from spans import intrange, floatrange +from ..collections import IntSet, FloatSet from ..temporal import Temporal if TYPE_CHECKING: - from ..boxes import TBox - from ..time import Time + from ..boxes import Box, TBox + from ..collections import Time, IntSpan, IntSpanSet, FloatSpan, FloatSpanSet from .tint import TInt from .tfloat import TFloat @@ -19,6 +19,7 @@ TI = TypeVar('TI', 'TInstant[int]', 'TInstant[float]') TS = TypeVar('TS', 'TSequence[int]', 'TSequence[float]') TSS = TypeVar('TSS', 'TSequenceSet[int]', 'TSequenceSet[float]') +Self = TypeVar('Self', bound='TNumber[Any]') class TNumber(Temporal[TBase, TG, TI, TS, TSS], ABC): @@ -61,10 +62,83 @@ def time_weighted_average(self) -> float: """ return tnumber_twavg(self._inner) + # ------------------------- Transformations ------------------------------- + def shift_value(self: Self, delta: Union[int, float]) -> Self: + """ + Returns a new :class:`TNumber` with the value dimension shifted by + ``delta``. + + Args: + delta: value to shift + + MEOS Functions: + tint_shift_value, tfloat_shift_value + """ + from .tint import TInt + from .tfloat import TFloat + if isinstance(self, TInt): + shifted = tint_shift_value(self._inner, int(delta)) + elif isinstance(self, TFloat): + shifted = tfloat_shift_value(self._inner, float(delta)) + else: + raise TypeError(f'Operation not supported with type {self.__class__}') + return Temporal._factory(shifted) + + def scale_value(self: Self, width: Union[int, float]) -> Self: + """ + Returns a new :class:`TNumber` scaled so the value dimension has + width ``width``. + + Args: + width: value representing the width of the new temporal number + + MEOS Functions: + tint_scale_value, tfloat_scale_value + """ + from .tint import TInt + from .tfloat import TFloat + if isinstance(self, TInt): + scaled = tint_scale_value(self._inner, int(width)) + elif isinstance(self, TFloat): + scaled = tfloat_scale_value(self._inner, float(width)) + else: + raise TypeError(f'Operation not supported with type {self.__class__}') + return Temporal._factory(scaled) + + def shift_scale_value(self: Self, shift: Union[int, float] = None, + width: Union[int, float] = None) -> Self: + """ + Returns a new :class:`TNumber` with the value dimension shifted by + ``shift`` and scaled so the value dimension has width ``width``. + + Args: + shift: value to shift + width: value representing the width of the new temporal number + + MEOS Functions: + tint_shift_scale_value, tfloat_shift_scale_value + """ + from .tint import TInt + from .tfloat import TFloat + assert shift is not None or width is not None, \ + 'shift and width must not be both None' + + if isinstance(self, TInt): + scaled = tint_shift_scale_value(self._inner, + int(shift) if shift else None, int(width) if width else None) + elif isinstance(self, TFloat): + scaled = tfloat_shift_scale_value(self._inner, + float(shift) if shift else None, float(width) if width else None) + else: + raise TypeError(f'Operation not supported with type {self.__class__}') + return Temporal._factory(scaled) + # ------------------------- Restrictions ---------------------------------- - def at(self, other: Union[intrange, floatrange, List[intrange], List[floatrange], TBox, Time]) -> TG: + def at(self, other: Union[IntSet, FloatSet, IntSpan, FloatSpan, IntSpanSet, FloatSpanSet, + TBox, Time]) -> TG: """ - Returns a new temporal object with the values of `self` restricted to the value or time `other`. + Returns a new temporal object with the values of `self` restricted to + the value or time `other`. Args: other: A time or value object to restrict the values of `self` to. @@ -73,56 +147,50 @@ def at(self, other: Union[intrange, floatrange, List[intrange], List[floatrange] A new temporal object of the same subtype as `self`. MEOS Functions: - tnumber_at_span, tnumber_at_spanset, tnumber_at_tbox, - temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset + temporal_at_values, tnumber_at_span, tnumber_at_spanset, tnumber_at_tbox, + temporal_at_timestamp, temporal_at_timestampset, + temporal_at_period, temporal_at_periodset """ from ..boxes import TBox - if isinstance(other, intrange): - result = tnumber_at_span(self._inner, intrange_to_intspan(other)) - elif isinstance(other, floatrange): - result = tnumber_at_span(self._inner, floatrange_to_floatspan(other)) - elif isinstance(other, list) and isinstance(other[0], intrange): - spans = [intrange_to_intspan(ir) for ir in other] - spanset = spanset_make(spans, len(spans), True) - result = tnumber_at_spanset(self._inner, spanset) - elif isinstance(other, list) and isinstance(other[0], floatrange): - spans = [floatrange_to_floatspan(fr) for fr in other] - spanset = spanset_make(spans, len(spans), True) - result = tnumber_at_spanset(self._inner, spanset) + from ..collections import IntSet, FloatSet, IntSpan, FloatSpan, IntSpanSet, FloatSpanSet + if isinstance(other, IntSet) or isinstance(other, FloatSet): + result = temporal_at_values(self._inner, other._inner) + elif isinstance(other, IntSpan) or isinstance(other, FloatSpan): + result = tnumber_at_span(self._inner, other._inner) + elif isinstance(other, IntSpanSet) or isinstance(other, FloatSpanSet): + result = tnumber_at_spanset(self._inner, other._inner) elif isinstance(other, TBox): result = tnumber_at_tbox(self._inner, other._inner) else: return super().at(other) return Temporal._factory(result) - def minus(self, other: Union[intrange, floatrange, List[intrange], List[floatrange], TBox, Time]) -> TG: + def minus(self, other: Union[IntSet, FloatSet, IntSpan, FloatSpan, IntSpanSet, FloatSpanSet, + TBox, Time]) -> TG: """ - Returns a new temporal object with the values of `self` restricted to the complement of the value or time - `other`. + Returns a new temporal object with the values of `self` restricted to + the complement of the value or time `other`. Args: - other: A time or value object to restrict the values of `self` to the complement of. + other: A time or value object to restrict the values of `self` to + the complement of. Returns: A new temporal object of the same subtype as `self`. MEOS Functions: - tnumber_minus_span, tnumber_minus_spanset, tnumber_minus_tbox, - temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, temporal_minus_periodset + temporal_minus_values, tnumber_minus_span, tnumber_minus_spanset, tnumber_minus_tbox, + temporal_minus_timestamp, temporal_minus_timestampset, + temporal_minus_period, temporal_minus_periodset """ from ..boxes import TBox - if isinstance(other, intrange): - result = tnumber_minus_span(self._inner, intrange_to_intspan(other)) - elif isinstance(other, floatrange): - result = tnumber_minus_span(self._inner, floatrange_to_floatspan(other)) - elif isinstance(other, list) and isinstance(other[0], intrange): - spans = [intrange_to_intspan(ir) for ir in other] - spanset = spanset_make(spans, len(spans), True) - result = tnumber_minus_spanset(self._inner, spanset) - elif isinstance(other, list) and isinstance(other[0], floatrange): - spans = [floatrange_to_floatspan(fr) for fr in other] - spanset = spanset_make(spans, len(spans), True) - result = tnumber_minus_spanset(self._inner, spanset) + from ..collections import IntSet, FloatSet, IntSpan, FloatSpan, IntSpanSet, FloatSpanSet + if isinstance(other, IntSet) or isinstance(other, FloatSet): + result = temporal_minus_values(self._inner, other._inner) + elif isinstance(other, IntSpan) or isinstance(other, FloatSpan): + result = tnumber_minus_span(self._inner, other._inner) + elif isinstance(other, IntSpanSet) or isinstance(other, FloatSpanSet): + result = tnumber_minus_spanset(self._inner, other._inner) elif isinstance(other, TBox): result = tnumber_minus_tbox(self._inner, other._inner) else: @@ -132,7 +200,8 @@ def minus(self, other: Union[intrange, floatrange, List[intrange], List[floatran # ------------------------- Position Operations --------------------------- def is_left(self, other: Union[Temporal, Box]) -> bool: """ - Returns whether the bounding box of `self` is left to the bounding box of `other`. + Returns whether the bounding box of `self` is left to the bounding box + of `other`. Args: other: A box or a temporal object to compare to `self`. @@ -147,7 +216,8 @@ def is_left(self, other: Union[Temporal, Box]) -> bool: def is_over_or_left(self, other: Union[Temporal, Box]) -> bool: """ - Returns whether the bounding box of `self` is over or left to the bounding box of `other`. + Returns whether the bounding box of `self` is over or left to the + bounding box of `other`. Args: other: A box or a temporal object to compare to `self`. @@ -162,7 +232,8 @@ def is_over_or_left(self, other: Union[Temporal, Box]) -> bool: def is_right(self, other: Union[Temporal, Box]) -> bool: """ - Returns whether the bounding box of `self` is right to the bounding box of `other`. + Returns whether the bounding box of `self` is right to the bounding + box of `other`. Args: other: A box or a temporal object to compare to `self`. @@ -177,7 +248,8 @@ def is_right(self, other: Union[Temporal, Box]) -> bool: def is_over_or_right(self, other: Union[Temporal, Box]) -> bool: """ - Returns whether the bounding box of `self` is over or right to the bounding box of `other`. + Returns whether the bounding box of `self` is over or right to the + bounding box of `other`. Args: other: A box or a temporal object to compare to `self`. @@ -196,7 +268,8 @@ def add(self, other: Union[int, float, TNumber]) -> TNumber: Returns a new temporal object with the values of `self` plus `other`. Args: - other: A :class:`int`, :class:`float` or :class:`TNumber` to add to `self`. + other: A :class:`int`, :class:`float` or :class:`TNumber` to add + to `self`. Returns: A new temporal object of the same subtype as `self`. @@ -241,7 +314,8 @@ def sub(self, other: Union[int, float, TNumber]) -> TNumber: Returns a new temporal object with the values of `self` minus `other`. Args: - other: A :class:`int`, :class:`float` or :class:`TNumber` to subtract from `self`. + other: A :class:`int`, :class:`float` or :class:`TNumber` to + subtract from `self`. Returns: A new temporal object of the same subtype as `self`. @@ -286,10 +360,12 @@ def rsub(self, other: Union[int, float]) -> TNumber: def mul(self, other: Union[int, float, TNumber]) -> TNumber: """ - Returns a new temporal object with the values of `self` multiplied by `other`. + Returns a new temporal object with the values of `self` multiplied by + `other`. Args: - other: A :class:`int`, :class:`float` or :class:`TNumber` to multiply `self` by. + other: A :class:`int`, :class:`float` or :class:`TNumber` to + multiply `self` by. Returns: A new temporal object of the same subtype as `self`. @@ -311,7 +387,8 @@ def mul(self, other: Union[int, float, TNumber]) -> TNumber: def rmul(self, other: Union[int, float]) -> TNumber: """ - Returns a new temporal object with the values of `self` multiplied by `other`. + Returns a new temporal object with the values of `self` multiplied by + `other`. Args: other: A :class:`int` or :class:`float` to multiply by `self`. @@ -334,10 +411,12 @@ def rmul(self, other: Union[int, float]) -> TNumber: def div(self, other: Union[int, float, TNumber]) -> TNumber: """ - Returns a new temporal object with the values of `self` divided by `other`. + Returns a new temporal object with the values of `self` divided by + `other`. Args: - other: A :class:`int`, :class:`float` or :class:`TNumber` to divide `self` by. + other: A :class:`int`, :class:`float` or :class:`TNumber` to divide + `self` by. Returns: A new temporal object of the same subtype as `self`. @@ -359,7 +438,8 @@ def div(self, other: Union[int, float, TNumber]) -> TNumber: def rdiv(self, other: Union[int, float]) -> TNumber: """ - Returns a new temporal object with the values of `other` divided by `self`. + Returns a new temporal object with the values of `other` divided by + `self`. Args: other: A :class:`int` or :class:`float` to divide by `self`. @@ -385,7 +465,8 @@ def __add__(self, other): Returns a new temporal object with the values of `self` plus `other`. Args: - other: A :class:`int`, :class:`float` or :class:`TNumber` to add to `self`. + other: A :class:`int`, :class:`float` or :class:`TNumber` to add to + `self`. Returns: A new temporal object of the same subtype as `self`. @@ -412,7 +493,8 @@ def __sub__(self, other): Returns a new temporal object with the values of `self` minus `other`. Args: - other: A :class:`int`, :class:`float` or :class:`TNumber` to subtract from `self`. + other: A :class:`int`, :class:`float` or :class:`TNumber` to + subtract from `self`. Returns: A new temporal object of the same subtype as `self`. @@ -439,10 +521,12 @@ def __rsub__(self, other): def __mul__(self, other): """ - Returns a new temporal object with the values of `self` multiplied by `other`. + Returns a new temporal object with the values of `self` multiplied by + `other`. Args: - other: A :class:`int`, :class:`float` or :class:`TNumber` to multiply `self` by. + other: A :class:`int`, :class:`float` or :class:`TNumber` to + multiply `self` by. Returns: A new temporal object of the same subtype as `self`. @@ -454,7 +538,8 @@ def __mul__(self, other): def __rmul__(self, other): """ - Returns a new temporal object with the values of `self` multiplied by `other`. + Returns a new temporal object with the values of `self` multiplied + by `other`. Args: other: A :class:`int` or :class:`float` to multiply by `self`. @@ -469,10 +554,12 @@ def __rmul__(self, other): def __truediv__(self, other): """ - Returns a new temporal object with the values of `self` divided by `other`. + Returns a new temporal object with the values of `self` divided by + `other`. Args: - other: A :class:`int`, :class:`float` or :class:`TNumber` to divide `self` by. + other: A :class:`int`, :class:`float` or :class:`TNumber` to divide + `self` by. Returns: A new temporal object of the same subtype as `self`. @@ -484,7 +571,8 @@ def __truediv__(self, other): def __rtruediv__(self, other): """ - Returns a new temporal object with the values of `other` divided by `self`. + Returns a new temporal object with the values of `other` divided by + `self`. Args: other: A :class:`int` or :class:`float` to divide by `self`. @@ -529,7 +617,8 @@ def distance(self, other: Union[int, float, TNumber]) -> TFloat: Returns the temporal distance between `self` and `other`. Args: - other: A :class:`int`, :class:`float` or :class:`TNumber` to compare to `self`. + other: A :class:`int`, :class:`float` or :class:`TNumber` to + compare to `self`. Returns: A :class:`TFloat` with the distance between `self` and `other`. @@ -547,15 +636,18 @@ def distance(self, other: Union[int, float, TNumber]) -> TFloat: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def nearest_approach_distance(self, other: Union[int, float, TNumber, TBox]) -> float: + def nearest_approach_distance(self, other: Union[int, float, + TNumber, TBox]) -> float: """ Returns the nearest approach distance between `self` and `other`. Args: - other: A :class:`int`, :class:`float`, :class:`TNumber` or :class:`TBox` to compare to `self`. + other: A :class:`int`, :class:`float`, :class:`TNumber` or + :class:`TBox` to compare to `self`. Returns: - A :class:`float` with the nearest approach distance between `self` and `other`. + A :class:`float` with the nearest approach distance between `self` + and `other`. MEOS Functions: nad_tfloat_float, nad_tfloat_tfloat, nad_tnumber_tbox @@ -570,4 +662,3 @@ def nearest_approach_distance(self, other: Union[int, float, TNumber, TBox]) -> return nad_tnumber_tbox(self._inner, other._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - diff --git a/pymeos/pymeos/main/tpoint.py b/pymeos/pymeos/main/tpoint.py index 0d0084a0..44d44093 100644 --- a/pymeos/pymeos/main/tpoint.py +++ b/pymeos/pymeos/main/tpoint.py @@ -4,26 +4,36 @@ from functools import reduce from typing import Optional, List, TYPE_CHECKING, Set, Tuple, Union, TypeVar, Type, overload -import postgis as pg import shapely.geometry as shp import shapely.geometry.base as shpb -from geopandas import GeoDataFrame from pymeos_cffi import * from .tbool import TBool -from .tfloat import TFloat, TFloatSeqSet +from .tfloat import TFloat, TFloatInst, TFloatSeq, TFloatSeqSet from ..temporal import Temporal, TInstant, TSequence, TSequenceSet, TInterpolation -from ..time import * +from ..collections import * if TYPE_CHECKING: - from ..boxes import STBox + from ..boxes import STBox, Box + from geopandas import GeoDataFrame + + +def import_geopandas(): + try: + import geopandas as gpd + return gpd + except ImportError: + print('Geopandas not found. Please install geopandas to use this function.') + raise + TG = TypeVar('TG', bound='TPoint') TI = TypeVar('TI', bound='TPointInst') TS = TypeVar('TS', bound='TPointSeq') TSS = TypeVar('TSS', bound='TPointSeqSet') Self = TypeVar('Self', bound='TPoint') - +TF = TypeVar('TF', bound='TFloat', covariant=True) + class TPoint(Temporal[shp.Point, TG, TI, TS, TSS], ABC): """ @@ -99,6 +109,23 @@ def as_geojson(self, option: int = 1, precision: int = 15, srs: Optional[str] = """ return gserialized_as_geojson(tpoint_trajectory(self._inner), option, precision, srs) + def to_shapely_geometry(self, precision: int = 15) -> shpb.BaseGeometry: + """ + Returns the trajectory of the temporal point as a Shapely geometry. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` representing the + trajectory. + + MEOS Functions: + gserialized_to_shapely_geometry + """ + return gserialized_to_shapely_geometry(tpoint_trajectory(self._inner), + precision) + # ------------------------- Accessors ------------------------------------- def bounding_box(self) -> STBox: """ @@ -219,7 +246,7 @@ def speed(self) -> TFloat: result = tpoint_speed(self._inner) return Temporal._factory(result) - def x(self) -> TFloat: + def x(self) -> TF: """ Returns the x coordinate of the temporal point. @@ -227,12 +254,12 @@ def x(self) -> TFloat: A :class:`TFloat` with the x coordinate of the temporal point. MEOS Functions: - tpoint_get_coord + tpoint_get_x """ - result = tpoint_get_coord(self._inner, 0) + result = tpoint_get_x(self._inner) return Temporal._factory(result) - def y(self) -> TFloat: + def y(self) -> TF: """ Returns the y coordinate of the temporal point. @@ -240,12 +267,12 @@ def y(self) -> TFloat: A :class:`TFloat` with the y coordinate of the temporal point. MEOS Functions: - tpoint_get_coord + tpoint_get_y """ - result = tpoint_get_coord(self._inner, 1) + result = tpoint_get_y(self._inner) return Temporal._factory(result) - def z(self) -> TFloat: + def z(self) -> TF: """ Returns the z coordinate of the temporal point. @@ -253,9 +280,9 @@ def z(self) -> TFloat: A :class:`TFloat` with the z coordinate of the temporal point. MEOS Functions: - tpoint_get_coord + tpoint_get_z """ - result = tpoint_get_coord(self._inner, 2) + result = tpoint_get_z(self._inner) return Temporal._factory(result) def has_z(self) -> bool: @@ -296,7 +323,7 @@ def is_simple(self) -> bool: """ return tpoint_is_simple(self._inner) - def bearing(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint]) -> TFloat: + def bearing(self, other: Union[shpb.BaseGeometry, TPoint]) -> TFloat: """ Returns the temporal bearing between the temporal point and `other`. @@ -309,8 +336,8 @@ def bearing(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint]) -> TFloa MEOS Functions: bearing_tpoint_point, bearing_tpoint_tpoint """ - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = bearing_tpoint_point(self._inner, gs, False) elif isinstance(other, TPoint): result = bearing_tpoint_tpoint(self._inner, other._inner) @@ -372,7 +399,7 @@ def time_weighted_centroid(self, precision: int = 15) -> shp.Point: return gserialized_to_shapely_geometry(tpoint_twcentroid(self._inner), precision) # type: ignore # ------------------------- Spatial Reference System ---------------------- - def srid(self) -> int: + def srid(self) -> int: """ Returns the SRID. @@ -393,7 +420,7 @@ def set_srid(self: Self, srid: int) -> Self: return self.__class__(_inner=tpoint_set_srid(self._inner, srid)) # ------------------------- Transformations ------------------------------- - def round(self, maxdd : int = 0) -> TPoint: + def round(self, max_decimals: int = 0) -> TPoint: """ Round the coordinate values to a number of decimal places. @@ -403,7 +430,7 @@ def round(self, maxdd : int = 0) -> TPoint: MEOS Functions: tpoint_round """ - result = tpoint_round(self._inner, maxdd) + result = tpoint_round(self._inner, max_decimals) return Temporal._factory(result) def make_simple(self) -> List[TPoint]: @@ -440,8 +467,7 @@ def expand(self, other: Union[int, float]) -> STBox: return STBox(_inner=result) # ------------------------- Restrictions ---------------------------------- - def at(self, - other: Union[pg.Geometry, List[pg.Geometry], shpb.BaseGeometry, List[shpb.BaseGeometry], STBox, Time]) -> TG: + def at(self, other: Union[shpb.BaseGeometry, GeoSet, STBox, Time]) -> TG: """ Returns a new temporal object with the values of `self` restricted to `other`. @@ -452,26 +478,22 @@ def at(self, A new :TPoint: with the values of `self` restricted to `other`. MEOS Functions: - tpoint_at_geometry, tpoint_at_stbox, + tpoint_at_value, tpoint_at_stbox, temporal_at_values, temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = tpoint_at_value(self._inner, gs) - elif isinstance(other, list): - gss = [geometry_to_gserialized(gm, isinstance(self, TGeogPoint)) for gm in other] - results = [tpoint_at_value(self._inner, gs) for gs in gss] - result = temporal_merge_array(results, len(results)) + elif isinstance(other, GeoSet): + result = temporal_at_values(self._inner, other._inner) elif isinstance(other, STBox): result = tpoint_at_stbox(self._inner, other._inner, True) else: return super().at(other) return Temporal._factory(result) - def minus(self, - other: Union[pg.Geometry, List[pg.Geometry], shpb.BaseGeometry, List[shpb.BaseGeometry], STBox, Time] - ) -> TG: + def minus(self, other: Union[shpb.BaseGeometry, GeoSet, STBox, Time]) -> TG: """ Returns a new temporal object with the values of `self` restricted to the complement of `other`. @@ -482,16 +504,15 @@ def minus(self, A new :TPoint: with the values of `self` restricted to the complement of `other`. MEOS Functions: - tpoint_minus_geometry, tpoint_minus_stbox, + tpoint_minus_value, tpoint_minus_stbox, temporal_minus_values, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, temporal_minus_periodset """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = tpoint_minus_value(self._inner, gs) - elif isinstance(other, list): - gss = [geometry_to_gserialized(gm, isinstance(self, TGeogPoint)) for gm in other] - result = reduce(tpoint_minus_value, gss, self._inner) + elif isinstance(other, GeoSet): + result = temporal_minus_values(self._inner, other._inner) elif isinstance(other, STBox): result = tpoint_minus_stbox(self._inner, other._inner, True) else: @@ -680,7 +701,7 @@ def is_over_or_behind(self, other: Union[Temporal, Box]) -> bool: return self.bounding_box().is_over_or_behind(other) # ------------------------- Ever Spatial Relationships -------------------- - def is_ever_contained_in(self, container: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> bool: + def is_ever_contained_in(self, container: Union[shpb.BaseGeometry, STBox]) -> bool: """ Returns whether the temporal point is ever contained by `container`. @@ -694,8 +715,8 @@ def is_ever_contained_in(self, container: Union[pg.Geometry, shpb.BaseGeometry, econtains_geo_tpoint """ from ..boxes import STBox - if isinstance(container, pg.Geometry) or isinstance(container, shpb.BaseGeometry): - gs = geometry_to_gserialized(container, isinstance(self, TGeogPoint)) + if isinstance(container, shpb.BaseGeometry): + gs = geo_to_gserialized(container, isinstance(self, TGeogPoint)) result = econtains_geo_tpoint(gs, self._inner) elif isinstance(container, STBox): result = econtains_geo_tpoint(stbox_to_geo(container._inner), self._inner) @@ -703,7 +724,7 @@ def is_ever_contained_in(self, container: Union[pg.Geometry, shpb.BaseGeometry, raise TypeError(f'Operation not supported with type {container.__class__}') return result == 1 - def is_ever_disjoint(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox]) -> bool: + def is_ever_disjoint(self, other: Union[shpb.BaseGeometry, TPoint, STBox]) -> bool: """ Returns whether the temporal point is ever disjoint from `other`. @@ -717,8 +738,8 @@ def is_ever_disjoint(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, edisjoint_tpoint_geo, edisjoint_tpoint_tpoint """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = edisjoint_tpoint_geo(self._inner, gs) elif isinstance(other, STBox): result = edisjoint_tpoint_geo(self._inner, stbox_to_geo(other._inner)) @@ -728,7 +749,7 @@ def is_ever_disjoint(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, raise TypeError(f'Operation not supported with type {other.__class__}') return result == 1 - def is_ever_within_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox], + def is_ever_within_distance(self, other: Union[shpb.BaseGeometry, TPoint, STBox], distance: float) -> bool: """ Returns whether the temporal point is ever within `distance` of `other`. @@ -744,8 +765,8 @@ def is_ever_within_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, T edwithin_tpoint_geo, edwithin_tpoint_tpoint """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = edwithin_tpoint_geo(self._inner, gs, distance) elif isinstance(other, STBox): result = edwithin_tpoint_geo(self._inner, stbox_to_geo(other._inner), distance) @@ -755,7 +776,7 @@ def is_ever_within_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, T raise TypeError(f'Operation not supported with type {other.__class__}') return result == 1 - def ever_intersects(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox]) -> bool: + def ever_intersects(self, other: Union[shpb.BaseGeometry, TPoint, STBox]) -> bool: """ Returns whether the temporal point ever intersects `other`. @@ -769,8 +790,8 @@ def ever_intersects(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, S eintersects_tpoint_geo, eintersects_tpoint_tpoint """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = eintersects_tpoint_geo(self._inner, gs) elif isinstance(other, STBox): result = eintersects_tpoint_geo(self._inner, stbox_to_geo(other._inner)) @@ -780,7 +801,7 @@ def ever_intersects(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, S raise TypeError(f'Operation not supported with type {other.__class__}') return result == 1 - def ever_touches(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> bool: + def ever_touches(self, other: Union[shpb.BaseGeometry, STBox]) -> bool: """ Returns whether the temporal point ever touches `other`. @@ -794,8 +815,8 @@ def ever_touches(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> b etouches_tpoint_geo """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = etouches_tpoint_geo(self._inner, gs) elif isinstance(other, STBox): result = etouches_tpoint_geo(self._inner, stbox_to_geo(other._inner)) @@ -804,7 +825,7 @@ def ever_touches(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> b return result == 1 # ------------------------- Temporal Spatial Relationships ---------------- - def is_spatially_contained_in(self, container: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool: + def is_spatially_contained_in(self, container: Union[shpb.BaseGeometry, STBox]) -> TBool: """ Returns a new temporal boolean indicating whether the temporal point is contained by `container`. @@ -818,8 +839,8 @@ def is_spatially_contained_in(self, container: Union[pg.Geometry, shpb.BaseGeome tcontains_geo_tpoint """ from ..boxes import STBox - if isinstance(container, pg.Geometry) or isinstance(container, shpb.BaseGeometry): - gs = geometry_to_gserialized(container, isinstance(self, TGeogPoint)) + if isinstance(container, shpb.BaseGeometry): + gs = geo_to_gserialized(container, isinstance(self, TGeogPoint)) result = tcontains_geo_tpoint(gs, self._inner, False, False) elif isinstance(container, STBox): gs = stbox_to_geo(container._inner) @@ -828,7 +849,7 @@ def is_spatially_contained_in(self, container: Union[pg.Geometry, shpb.BaseGeome raise TypeError(f'Operation not supported with type {container.__class__}') return Temporal._factory(result) - def disjoint(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool: + def disjoint(self, other: Union[shpb.BaseGeometry, STBox]) -> TBool: """ Returns a new temporal boolean indicating whether the temporal point intersects `other`. @@ -842,8 +863,8 @@ def disjoint(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool tintersects_tpoint_geo """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = tdisjoint_tpoint_geo(self._inner, gs, False, False) elif isinstance(other, STBox): result = tdisjoint_tpoint_geo(self._inner, stbox_to_geo(other._inner), False, False) @@ -851,7 +872,7 @@ def disjoint(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def within_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox], distance: float) -> TBool: + def within_distance(self, other: Union[shpb.BaseGeometry, TPoint, STBox], distance: float) -> TBool: """ Returns a new temporal boolean indicating whether the temporal point is within `distance` of `other`. @@ -866,8 +887,8 @@ def within_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, S tdwithin_tpoint_geo, tdwithin_tpoint_tpoint """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = tdwithin_tpoint_geo(self._inner, gs, distance, False, False) elif isinstance(other, STBox): result = tdwithin_tpoint_geo(self._inner, stbox_to_geo(other._inner), distance, False, False) @@ -877,7 +898,7 @@ def within_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, S raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def intersects(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool: + def intersects(self, other: Union[shpb.BaseGeometry, STBox]) -> TBool: """ Returns a new temporal boolean indicating whether the temporal point intersects `other`. @@ -891,8 +912,8 @@ def intersects(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBo tintersects_tpoint_geo """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = tintersects_tpoint_geo(self._inner, gs, False, False) elif isinstance(other, STBox): result = tintersects_tpoint_geo(self._inner, stbox_to_geo(other._inner), False, False) @@ -900,7 +921,7 @@ def intersects(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBo raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def touches(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool: + def touches(self, other: Union[shpb.BaseGeometry, STBox]) -> TBool: """ Returns a new temporal boolean indicating whether the temporal point touches `other`. @@ -914,8 +935,8 @@ def touches(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool: ttouches_tpoint_geo """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = ttouches_tpoint_geo(self._inner, gs, False, False) elif isinstance(other, STBox): result = ttouches_tpoint_geo(self._inner, stbox_to_geo(other._inner), False, False) @@ -924,7 +945,7 @@ def touches(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool: return Temporal._factory(result) # ------------------------- Distance Operations --------------------------- - def distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox]) -> TFloat: + def distance(self, other: Union[shpb.BaseGeometry, TPoint, STBox]) -> TFloat: """ Returns the temporal distance between the temporal point and `other`. @@ -935,21 +956,21 @@ def distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox]) A new :class:`TFloat` indicating the temporal distance between the temporal point and `other`. MEOS Functions: - distance_tpoint_geo, distance_tpoint_tpoint + distance_tpoint_point, distance_tpoint_tpoint """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) - result = distance_tpoint_geo(self._inner, gs) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) + result = distance_tpoint_point(self._inner, gs) elif isinstance(other, STBox): - result = distance_tpoint_geo(self._inner, stbox_to_geo(other._inner)) + result = distance_tpoint_point(self._inner, stbox_to_geo(other._inner)) elif isinstance(other, TPoint): result = distance_tpoint_tpoint(self._inner, other._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def nearest_approach_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox, TPoint]) -> float: + def nearest_approach_distance(self, other: Union[shpb.BaseGeometry, STBox, TPoint]) -> float: """ Returns the nearest approach distance between the temporal point and `other`. @@ -963,8 +984,8 @@ def nearest_approach_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, nad_tpoint_geo, nad_tpoint_stbox, nad_tpoint_tpoint """ from ..boxes import STBox - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) return nad_tpoint_geo(self._inner, gs) elif isinstance(other, STBox): return nad_tpoint_stbox(self._inner, other._inner) @@ -973,7 +994,7 @@ def nearest_approach_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, else: raise TypeError(f'Operation not supported with type {other.__class__}') - def nearest_approach_instant(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint]) -> TI: + def nearest_approach_instant(self, other: Union[shpb.BaseGeometry, TPoint]) -> TI: """ Returns the nearest approach instant between the temporal point and `other`. @@ -986,8 +1007,8 @@ def nearest_approach_instant(self, other: Union[pg.Geometry, shpb.BaseGeometry, MEOS Functions: nai_tpoint_geo, nai_tpoint_tpoint """ - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = nai_tpoint_geo(self._inner, gs) elif isinstance(other, TPoint): result = nai_tpoint_tpoint(self._inner, other._inner) @@ -995,7 +1016,7 @@ def nearest_approach_instant(self, other: Union[pg.Geometry, shpb.BaseGeometry, raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def shortest_line(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint]) -> shpb.BaseGeometry: + def shortest_line(self, other: Union[shpb.BaseGeometry, TPoint]) -> shpb.BaseGeometry: """ Returns the shortest line between the temporal point and `other`. @@ -1009,8 +1030,8 @@ def shortest_line(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint]) -> MEOS Functions: shortestline_tpoint_geo, shortestline_tpoint_tpoint """ - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, isinstance(self, TGeogPoint)) result = shortestline_tpoint_geo(self._inner, gs) elif isinstance(other, TPoint): result = shortestline_tpoint_tpoint(self._inner, other._inner) @@ -1020,19 +1041,23 @@ def shortest_line(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint]) -> # ------------------------- Tiling Operations ----------------------------- def tile(self, size: float, duration: Optional[Union[timedelta, str]] = None, - origin: Optional[Union[shpb.BaseGeometry, pg.Geometry]] = None, + origin: Optional[shpb.BaseGeometry] = None, start: Union[datetime, str, None] = None) -> List[List[List[List[TG]]]]: """ - Split the temporal point into segments following the tiling of the bounding box. + Split the temporal point into segments following the tiling of the + bounding box. Args: - size: The size of the spatial tiles. If `self` has a spatial dimension and this - argument is not provided, the tiling will be only temporal. - duration: The duration of the temporal tiles. If `self` has a time dimension and this - argument is not provided, the tiling will be only spatial. - origin: The origin of the spatial tiling. If not provided, the origin will be (0, 0, 0). - start: The start time of the temporal tiling. If not provided, the start time will be the starting time of - the `STBox` time dimension. + size: The size of the spatial tiles. If `self` has a spatial + dimension and this argument is not provided, the tiling will be + only temporal. + duration: The duration of the temporal tiles. If `self` has a time + dimension and this argument is not provided, the tiling will be + only spatial. + origin: The origin of the spatial tiling. If not provided, the + origin will be (0, 0, 0). + start: The start time of the temporal tiling. If not provided, + the start time used by default is Monday, January 3, 2000. Returns: A 4D matrix (XxYxZxT) of :class:`TPoint` objects. @@ -1047,19 +1072,23 @@ def tile(self, size: float, duration: Optional[Union[timedelta, str]] = None, for z_dim in y_dim] for y_dim in x_dim] for x_dim in tiles] def tile_flat(self, size: float, duration: Optional[Union[timedelta, str]] = None, - origin: Optional[Union[shpb.BaseGeometry, pg.Geometry]] = None, + origin: Optional[shpb.BaseGeometry] = None, start: Union[datetime, str, None] = None) -> List[TG]: """ - Split the temporal point into segments following the tiling of the bounding box. + Split the temporal point into segments following the tiling of the + bounding box. Args: - size: The size of the spatial tiles. If `self` has a spatial dimension and this - argument is not provided, the tiling will be only temporal. - duration: The duration of the temporal tiles. If `self` has a time dimension and this - argument is not provided, the tiling will be only spatial. - origin: The origin of the spatial tiling. If not provided, the origin will be (0, 0, 0). - start: The start time of the temporal tiling. If not provided, the start time will be the starting time of - the `STBox` time dimension. + size: The size of the spatial tiles. If `self` has a spatial + dimension and this argument is not provided, the tiling will be + only temporal. + duration: The duration of the temporal tiles. If `self` has a time + dimension and this argument is not provided, the tiling will be + only spatial. + origin: The origin of the spatial tiling. If not provided, the + origin will be (0, 0, 0). + start: The start time of the temporal tiling. If not provided, the + the start time used by default is Monday, January 3, 2000. Returns: A :class:`list` of :class:`TPoint` objects. @@ -1072,6 +1101,81 @@ def tile_flat(self, size: float, duration: Optional[Union[timedelta, str]] = Non tiles = bbox.tile_flat(size, duration, origin, start) return [x for x in (self.at(tile) for tile in tiles) if x] + # ------------------------- Split Operations ------------------------------ + def space_split(self, xsize: float, ysize: Optional[float] = None, + zsize: Optional[float] = None, origin: Optional[shpb.BaseGeometry] = None, + bitmatrix: Optional[bool] = False) -> List[Temporal]: + """ + Splits `self` into fragments with respect to space buckets + + Args: + xsize: Size of the x dimension. + ysize: Size of the y dimension. + zsize: Size of the z dimension. + origin: The origin of the spatial tiling. If not provided, the + origin will be (0, 0, 0). + + Returns: + A list of temporal points. + + MEOS Functions: + tpoint_value_split + """ + ysz = ysize if ysize is not None else xsize + zsz = zsize if zsize is not None else xsize + gs = geo_to_gserialized(origin, self.geodetic()) if origin is not None \ + else pgis_geography_in('Point(0 0 0)', -1) \ + if isinstance(self, TGeogPoint) \ + else pgis_geometry_in('Point(0 0 0)', -1) + fragments, values, count = tpoint_space_split(self._inner, + xsize, ysz, zsz, gs, bitmatrix) + from ..factory import _TemporalFactory + return [_TemporalFactory.create_temporal(fragments[i]) for i in \ + range(count)] + + def space_time_split(self, xsize: float, duration: Union[str, timedelta], + ysize: Optional[float] = None, zsize: Optional[float] = None, + origin: Optional[shpb.BaseGeometry] = None, + time_start: Optional[Union[str, datetime]] = None, + bitmatrix: Optional[bool] = False) -> List[Temporal]: + """ + Splits `self` into fragments with respect to space and period buckets. + + Args: + xsize: Size of the x dimension. + ysize: Size of the y dimension. + zsize: Size of the z dimension. + duration: Duration of the period buckets. + origin: The origin of the spatial tiling. If not provided, the + origin will be (0, 0, 0). + time_start: Start time of the first period bucket. If None, the + start time used by default is Monday, January 3, 2000. + + Returns: + A list of temporal floats. + + MEOS Functions: + tfloat_value_time_split + """ + ysz = ysize if ysize is not None else xsize + zsz = zsize if zsize is not None else xsize + dt = timedelta_to_interval(duration) \ + if isinstance(duration, timedelta) \ + else pg_interval_in(duration, -1) + gs = geo_to_gserialized(origin, self.geodetic()) if origin is not None \ + else pgis_geography_in('Point(0 0 0)', -1) \ + if isinstance(self, TGeogPoint) \ + else pgis_geometry_in('Point(0 0 0)', -1) + if time_start is None: + st = pg_timestamptz_in('2000-01-03', -1) + else: + st = datetime_to_timestamptz(time_start) \ + if isinstance(time_start, datetime) \ + else pg_timestamptz_in(time_start, -1) + fragments, points, times, count = tpoint_space_time_split(self._inner, + xsize, ysz, zsz, dt, gs, st, bitmatrix) + return [Temporal._factory(fragments[i]) for i in range(count)] + class TPointInst(TInstant[shpb.BaseGeometry, TG, TI, TS, TSS], TPoint[TG, TI, TS, TSS], ABC): """ @@ -1090,44 +1194,29 @@ def value(self, precision: int = 15) -> shp.Point: """ return self.start_value(precision=precision) + def x(self) -> TFloatInst: + return super().x() + + def y(self) -> TFloatInst: + return super().y() + + def z(self) -> TFloatInst: + return super().z() + class TPointSeq(TSequence[shpb.BaseGeometry, TG, TI, TS, TSS], TPoint[TG, TI, TS, TSS], ABC): """ Abstract class for temporal point sequences. """ - @staticmethod - def from_arrays(t: List[Union[datetime, str]], x: List[float], y: List[float], z: Optional[List[float]] = None, - srid: int = 0, geodetic: bool = False, lower_inc: bool = True, upper_inc: bool = False, - interpolation: TInterpolation = TInterpolation.LINEAR, normalize: bool = True) -> TPointSeq: - """ - Creates a temporal point sequence from arrays of timestamps and coordinates. + def x(self) -> TFloatSeq: + return super().x() - Args: - t: The array of timestamps. - x: The array of x coordinates. - y: The array of y coordinates. - z: The array of z coordinates. - srid: The spatial reference system identifier. - geodetic: Whether the coordinates are geodetic. - lower_inc: Whether the lower bound is inclusive. - upper_inc: Whether the upper bound is inclusive. - interpolation: The interpolation method. - normalize: Whether to normalize the timestamps. + def y(self) -> TFloatSeq: + return super().y() - Returns: - A new :class:`TPointSeq` object. - - MEOS Functions: - tpointseq_make_coords - """ - from ..factory import _TemporalFactory - assert len(t) == len(x) == len(y) - times = [datetime_to_timestamptz(ti) if isinstance(ti, datetime) else pg_timestamptz_in(ti, -1) for ti in t] - return _TemporalFactory.create_temporal( - tpointseq_make_coords(x, y, z, times, len(t), srid, geodetic, lower_inc, upper_inc, interpolation, - normalize) - ) + def z(self) -> TFloatSeq: + return super().z() def plot(self, *args, **kwargs): """ @@ -1160,15 +1249,28 @@ def to_dataframe(self, precision: int = 15) -> GeoDataFrame: precision: The precision of the returned geometry. Returns: - A new :class:`GeoDataFrame` representing the temporal point sequence set. + A new :class:`GeoDataFrame` representing the temporal point + sequence set. """ + gpd = import_geopandas() sequences = self.sequences() data = { - 'sequence': [i + 1 for i, seq in enumerate(sequences) for _ in range(seq.num_instants())], + 'sequence': [i + 1 for i, seq in enumerate(sequences) for _ in + range(seq.num_instants())], 'time': [t for seq in sequences for t in seq.timestamps()], - 'geometry': [v for seq in sequences for v in seq.values(precision=precision)] + 'geometry': [v for seq in sequences for v in + seq.values(precision=precision)] } - return GeoDataFrame(data, crs=self.srid()).set_index(keys=['sequence', 'time']) + return gpd.GeoDataFrame(data, crs=self.srid()).set_index(keys=['sequence', 'time']) + + def x(self) -> TFloatSeqSet: + return super().x() + + def y(self) -> TFloatSeqSet: + return super().y() + + def z(self) -> TFloatSeqSet: + return super().z() def plot(self, *args, **kwargs): """ @@ -1188,51 +1290,59 @@ def plot(self, *args, **kwargs): return TemporalPointSequenceSetPlotter.plot_xy(self, *args, **kwargs) -class TGeomPoint(TPoint['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', 'TGeomPointSeqSet'], ABC): +class TGeomPoint(TPoint['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', +'TGeomPointSeqSet'], ABC): """ Abstract class for temporal geometric points. """ + + _mobilitydb_name = 'tgeompoint' + BaseClass = shp.Point _parse_function = tgeompoint_in # ------------------------- Output ---------------------------------------- @staticmethod - def from_base_temporal(value: Union[pg.Geometry, shpb.BaseGeometry], base: Temporal) -> TGeomPoint: + def from_base_temporal(value: shpb.BaseGeometry, + base: Temporal) -> TGeomPoint: """ - Creates a temporal geometric point from a base geometry and the time frame of another temporal object. + Creates a temporal geometric point from a base geometry and the time + frame of another temporal object. Args: value: The base geometry. base: The temporal object defining the time frame. - interpolation: The interpolation method. Returns: A new :class:`TGeomPoint` object. MEOS Functions: - tgeompoint_from_base_temp + tpoint_from_base_temp """ - gs = geometry_to_gserialized(value, False) - result = tgeompoint_from_base_temp(gs, base._inner) + gs = geometry_to_gserialized(value) + result = tpoint_from_base_temp(gs, base._inner) return Temporal._factory(result) @staticmethod @overload - def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: datetime) -> TGeomPointInst: + def from_base_time(value: shpb.BaseGeometry, + base: datetime) -> TGeomPointInst: ... @staticmethod @overload - def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: Union[TimestampSet, Period]) -> TGeomPointSeq: + def from_base_time(value: shpb.BaseGeometry, + base: Union[TimestampSet, Period]) -> TGeomPointSeq: ... @staticmethod @overload - def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: PeriodSet) -> TGeomPointSeqSet: + def from_base_time(value: shpb.BaseGeometry, + base: PeriodSet) -> TGeomPointSeqSet: ... @staticmethod - def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: Time, + def from_base_time(value: shpb.BaseGeometry, base: Time, interpolation: TInterpolation = None) -> TGeomPoint: """ Creates a temporal geometric point from a base geometry and a time value. @@ -1246,18 +1356,22 @@ def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: Time, A new :class:`TGeomPoint` object. MEOS Functions: - tgeompointinst_make, tgeompointseq_from_base_timestampset, - tgeompointseq_from_base_period, tgeompointseqset_from_base_periodset + tpointinst_make, tpointseq_from_base_timestampset, + tpointseq_from_base_period, tpointseqset_from_base_periodset """ - gs = geometry_to_gserialized(value, False) + gs = geometry_to_gserialized(value) if isinstance(base, datetime): - return TGeomPointInst(_inner=tgeompointinst_make(gs, datetime_to_timestamptz(base))) + return TGeomPointInst(_inner=tpointinst_make(gs, + datetime_to_timestamptz(base))) elif isinstance(base, TimestampSet): - return TGeomPointSeq(_inner=tgeompointseq_from_base_timestampset(gs, base._inner)) + return TGeomPointSeq(_inner=tpointseq_from_base_timestampset(gs, + base._inner)) elif isinstance(base, Period): - return TGeomPointSeq(_inner=tgeompointseq_from_base_period(gs, base._inner, interpolation)) + return TGeomPointSeq(_inner=tpointseq_from_base_period(gs, + base._inner, interpolation)) elif isinstance(base, PeriodSet): - return TGeomPointSeqSet(_inner=tgeompointseqset_from_base_periodset(gs, base._inner, interpolation)) + return TGeomPointSeqSet(_inner=tpointseqset_from_base_periodset(gs, + base._inner, interpolation)) raise TypeError(f'Operation not supported with type {base.__class__}') # ------------------------- Conversions ---------------------------------- @@ -1269,26 +1383,11 @@ def to_geographic(self) -> TGeogPoint: A new :class:`TGeogPoint` object. MEOS Functions: - tgeompoint_tgeogpoint + tgeompoint_to_tgeogpoint """ - result = tgeompoint_tgeogpoint(self._inner, True) + result = tgeompoint_to_tgeogpoint(self._inner) return Temporal._factory(result) - def to_shapely_geometry(self, precision: int = 15) -> shpb.BaseGeometry: - """ - Returns the trajectory of the temporal point as a Shapely geometry. - - Args: - precision: The precision of the returned geometry. - - Returns: - A new :class:`~shapely.geometry.base.BaseGeometry` representing the trajectory. - - MEOS Functions: - gserialized_to_shapely_geometry - """ - return gserialized_to_shapely_geometry(tpoint_trajectory(self._inner), precision) - def to_dataframe(self) -> GeoDataFrame: """ Returns the trajectory of the temporal point as a GeoPandas DataFrame. @@ -1296,14 +1395,15 @@ def to_dataframe(self) -> GeoDataFrame: Returns: A new :class:`GeoDataFrame` representing the trajectory. """ + gpd = import_geopandas() data = { 'time': self.timestamps(), 'geometry': [i.value() for i in self.instants()] } - return GeoDataFrame(data, crs=self.srid()).set_index(keys=['time']) + return gpd.GeoDataFrame(data, crs=self.srid()).set_index(keys=['time']) # ------------------------- Ever and Always Comparisons ------------------- - def always_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def always_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is always equal to `value`. @@ -1314,12 +1414,12 @@ def always_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: True if `self` is always equal to `value`, False otherwise. MEOS Functions: - tgeompoint_always_eq + tpoint_always_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return tgeompoint_always_eq(self._inner, gs) + gs = geometry_to_gserialized(value) + return tpoint_always_eq(self._inner, gs) - def always_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def always_not_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is always different to `value`. @@ -1330,12 +1430,12 @@ def always_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool True if `self` is always different to `value`, False otherwise. MEOS Functions: - tgeompoint_ever_eq + tpoint_ever_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return not tgeompoint_ever_eq(self._inner, gs) + gs = geometry_to_gserialized(value) + return not tpoint_ever_eq(self._inner, gs) - def ever_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def ever_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is ever equal to `value`. @@ -1346,12 +1446,12 @@ def ever_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: True if `self` is ever equal to `value`, False otherwise. MEOS Functions: - tgeompoint_ever_eq + tpoint_ever_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return tgeompoint_ever_eq(self._inner, gs) + gs = geometry_to_gserialized(value) + return tpoint_ever_eq(self._inner, gs) - def ever_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def ever_not_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is ever different to `value`. @@ -1362,12 +1462,12 @@ def ever_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: True if `self` is ever different to `value`, False otherwise. MEOS Functions: - tgeompoint_always_eq + tpoint_always_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return not tgeompoint_always_eq(self._inner, gs) + gs = geometry_to_gserialized(value) + return not tpoint_always_eq(self._inner, gs) - def never_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def never_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is never equal to `value`. @@ -1378,12 +1478,12 @@ def never_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: True if `self` is never equal to `value`, False otherwise. MEOS Functions: - tgeompoint_ever_eq + tpoint_ever_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return not tgeompoint_ever_eq(self._inner, gs) + gs = geometry_to_gserialized(value) + return not tpoint_ever_eq(self._inner, gs) - def never_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def never_not_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is never different to `value`. @@ -1394,13 +1494,13 @@ def never_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: True if `self` is never different to `value`, False otherwise. MEOS Functions: - tgeompoint_always_eq + tpoint_always_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return tgeompoint_always_eq(self._inner, gs) + gs = geometry_to_gserialized(value) + return tpoint_always_eq(self._inner, gs) # ------------------------- Temporal Comparisons -------------------------- - def temporal_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> TBool: + def temporal_equal(self, other: Union[shp.Point, Temporal]) -> TBool: """ Returns the temporal equality relation between `self` and `other`. @@ -1411,16 +1511,16 @@ def temporal_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> TBool: A :class:`TBool` with the result of the temporal equality relation. MEOS Functions: - teq_tgeompoint_point, teq_temporal_temporal + teq_tpoint_point, teq_temporal_temporal """ - if isinstance(other, pg.Point) or isinstance(other, shp.Point): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) - result = teq_tgeompoint_point(self._inner, gs) + if isinstance(other, shp.Point): + gs = geometry_to_gserialized(other) + result = teq_tpoint_point(self._inner, gs) else: return super().temporal_equal(other) return Temporal._factory(result) - def temporal_not_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> Temporal: + def temporal_not_equal(self, other: Union[shp.Point, Temporal]) -> Temporal: """ Returns the temporal inequality relation between `self` and `other`. @@ -1431,11 +1531,11 @@ def temporal_not_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> Tem A :class:`TBool` with the result of the temporal inequality relation. MEOS Functions: - tne_tgeompoint_point, tne_temporal_temporal + tne_tpoint_point, tne_temporal_temporal """ - if isinstance(other, pg.Point) or isinstance(other, shp.Point): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) - result = tne_tgeompoint_point(self._inner, gs) + if isinstance(other, shp.Point): + gs = geometry_to_gserialized(other) + result = tne_tpoint_point(self._inner, gs) else: return super().temporal_not_equal(other) return Temporal._factory(result) @@ -1444,7 +1544,8 @@ def temporal_not_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> Tem @staticmethod def read_from_cursor(value, _=None): """ - Reads a :class:`TGeogPoint` from a database cursor. Used when automatically loading objects from the database. + Reads a :class:`TGeogPoint` from a database cursor. Used when + automatically loading objects from the database. Users should use the class constructor instead. """ if not value: @@ -1467,51 +1568,59 @@ def read_from_cursor(value, _=None): raise Exception("ERROR: Could not parse temporal point value") -class TGeogPoint(TPoint['TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', 'TGeogPointSeqSet'], ABC): +class TGeogPoint(TPoint['TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', +'TGeogPointSeqSet'], ABC): """ Abstract class for representing temporal geographic points. """ + + _mobilitydb_name = 'tgeogpoint' + BaseClass = shp.Point _parse_function = tgeogpoint_in # ------------------------- Output ---------------------------------------- @staticmethod - def from_base_temporal(value: Union[pg.Geometry, shpb.BaseGeometry], base: Temporal) -> TGeogPoint: + def from_base_temporal(value: shpb.BaseGeometry, + base: Temporal) -> TGeogPoint: """ - Creates a temporal geographic point from a base geometry and the time frame of another temporal object. + Creates a temporal geographic point from a base geometry and the time + frame of another temporal object. Args: value: The base geometry. base: The temporal object defining the time frame. - interpolation: The interpolation method. Returns: A new :class:`TGeogPoint` object. MEOS Functions: - tgeogpoint_from_base_temp + tpoint_from_base_temp """ - gs = geometry_to_gserialized(value, True) - result = tgeogpoint_from_base_temp(gs, base._inner) + gs = geography_to_gserialized(value) + result = tpoint_from_base_temp(gs, base._inner) return Temporal._factory(result) @staticmethod @overload - def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: datetime) -> TGeogPointInst: + def from_base_time(value: shpb.BaseGeometry, + base: datetime) -> TGeogPointInst: ... @staticmethod @overload - def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: Union[TimestampSet, Period]) -> TGeogPointSeq: + def from_base_time(value: shpb.BaseGeometry, + base: Union[TimestampSet, Period]) -> TGeogPointSeq: ... @staticmethod @overload - def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: PeriodSet) -> TGeogPointSeqSet: + def from_base_time(value: shpb.BaseGeometry, + base: PeriodSet) -> TGeogPointSeqSet: ... @staticmethod - def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: Time, + def from_base_time(value: shpb.BaseGeometry, base: Time, interpolation: TInterpolation = None) -> TGeogPoint: """ Creates a temporal geographic point from a base geometry and a time object. @@ -1525,18 +1634,22 @@ def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: Time, A new :class:`TGeogPoint` object. MEOS Functions: - tgeogpointinst_make, tgeogpointseq_from_base_timestampset, - tgeogpointseq_from_base_period, tgeogpointseqset_from_base_periodset + tpointinst_make, tpointseq_from_base_timestampset, + tpointseq_from_base_period, tpointseqset_from_base_periodset """ - gs = geometry_to_gserialized(value, True) + gs = geography_to_gserialized(value) if isinstance(base, datetime): - return TGeogPointInst(_inner=tgeogpointinst_make(gs, datetime_to_timestamptz(base))) + return TGeogPointInst(_inner=tpointinst_make(gs, + datetime_to_timestamptz(base))) elif isinstance(base, TimestampSet): - return TGeogPointSeq(_inner=tgeogpointseq_from_base_timestampset(gs, base._inner)) + return TGeogPointSeq(_inner=tpointseq_from_base_timestampset(gs, + base._inner)) elif isinstance(base, Period): - return TGeogPointSeq(_inner=tgeogpointseq_from_base_period(gs, base._inner, interpolation)) + return TGeogPointSeq(_inner=tpointseq_from_base_period(gs, + base._inner, interpolation)) elif isinstance(base, PeriodSet): - return TGeogPointSeqSet(_inner=tgeogpointseqset_from_base_periodset(gs, base._inner, interpolation)) + return TGeogPointSeqSet(_inner=tpointseqset_from_base_periodset(gs, + base._inner, interpolation)) raise TypeError(f'Operation not supported with type {base.__class__}') # ------------------------- Conversions ---------------------------------- @@ -1548,13 +1661,13 @@ def to_geometric(self) -> TGeomPoint: A new :class:`TGeomPoint` object. MEOS Functions: - tgeompoint_tgeogpoint + tgeogpoint_to_tgeompoint """ - result = tgeompoint_tgeogpoint(self._inner, False) + result = tgeogpoint_to_tgeompoint(self._inner) return Temporal._factory(result) # ------------------------- Ever and Always Comparisons ------------------- - def always_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def always_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is always equal to `value`. @@ -1565,12 +1678,12 @@ def always_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: True if `self` is always equal to `value`, False otherwise. MEOS Functions: - tgeogpoint_always_eq + tpoint_always_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return tgeogpoint_always_eq(self._inner, gs) + gs = geography_to_gserialized(value) + return tpoint_always_eq(self._inner, gs) - def always_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def always_not_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is always different to `value`. @@ -1581,12 +1694,12 @@ def always_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool True if `self` is always different to `value`, False otherwise. MEOS Functions: - tgeogpoint_ever_eq + tpoint_ever_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return not tgeogpoint_ever_eq(self._inner, gs) + gs = geography_to_gserialized(value) + return not tpoint_ever_eq(self._inner, gs) - def ever_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def ever_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is ever equal to `value`. @@ -1597,12 +1710,12 @@ def ever_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: True if `self` is ever equal to `value`, False otherwise. MEOS Functions: - tgeogpoint_ever_eq + tpoint_ever_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return tgeogpoint_ever_eq(self._inner, gs) + gs = geography_to_gserialized(value) + return tpoint_ever_eq(self._inner, gs) - def ever_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def ever_not_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is ever different to `value`. @@ -1613,12 +1726,12 @@ def ever_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: True if `self` is ever different to `value`, False otherwise. MEOS Functions: - tgeogpoint_always_eq + tpoint_always_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return not tgeogpoint_always_eq(self._inner, gs) + gs = geography_to_gserialized(value) + return not tpoint_always_eq(self._inner, gs) - def never_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def never_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is never equal to `value`. @@ -1629,12 +1742,12 @@ def never_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: True if `self` is never equal to `value`, False otherwise. MEOS Functions: - tgeogpoint_ever_eq + tpoint_ever_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return not tgeogpoint_ever_eq(self._inner, gs) + gs = geography_to_gserialized(value) + return not tpoint_ever_eq(self._inner, gs) - def never_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: + def never_not_equal(self, value: shpb.BaseGeometry) -> bool: """ Returns whether `self` is never different to `value`. @@ -1645,13 +1758,13 @@ def never_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: True if `self` is never different to `value`, False otherwise. MEOS Functions: - tgeogpoint_always_eq + tpoint_always_eq """ - gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) - return tgeogpoint_always_eq(self._inner, gs) + gs = geography_to_gserialized(value) + return tpoint_always_eq(self._inner, gs) # ------------------------- Temporal Comparisons -------------------------- - def temporal_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> TBool: + def temporal_equal(self, other: Union[shp.Point, Temporal]) -> TBool: """ Returns the temporal equality relation between `self` and `other`. @@ -1662,16 +1775,16 @@ def temporal_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> TBool: A :class:`TBool` with the result of the temporal equality relation. MEOS Functions: - teq_tgeogpoint_point, teq_temporal_temporal + teq_tpoint_point, teq_temporal_temporal """ - if isinstance(other, pg.Point) or isinstance(other, shp.Point): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) - result = teq_tgeogpoint_point(self._inner, gs) + if isinstance(other, shp.Point): + gs = geography_to_gserialized(other) + result = teq_tpoint_point(self._inner, gs) else: return super().temporal_equal(other) return Temporal._factory(result) - def temporal_not_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> TBool: + def temporal_not_equal(self, other: Union[shp.Point, Temporal]) -> TBool: """ Returns the temporal inequality relation between `self` and `other`. @@ -1682,11 +1795,11 @@ def temporal_not_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> TBo A :class:`TBool` with the result of the temporal inequality relation. MEOS Functions: - tne_tgeogpoint_point, tne_temporal_temporal + tne_tpoint_point, tne_temporal_temporal """ - if isinstance(other, pg.Point) or isinstance(other, shp.Point): - gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) - result = tne_tgeogpoint_point(self._inner, gs) + if isinstance(other, shp.Point): + gs = geography_to_gserialized(other) + result = tne_tpoint_point(self._inner, gs) else: return super().temporal_not_equal(other) return Temporal._factory(result) @@ -1695,7 +1808,8 @@ def temporal_not_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> TBo @staticmethod def read_from_cursor(value, _=None): """ - Reads a :class:`TGeogPoint` from a database cursor. Used when automatically loading objects from the database. + Reads a :class:`TGeogPoint` from a database cursor. Used when + automatically loading objects from the database. Users should use the class constructor instead. """ if not value: @@ -1725,15 +1839,22 @@ class TGeomPointInst(TPointInst['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', _make_function = lambda *args: None _cast_function = lambda x: None - def __init__(self, string: Optional[str] = None, *, point: Optional[Union[str, pg.Point, shp.Point]] = None, + def __init__(self, string: Optional[str] = None, *, + point: Optional[Union[str, shp.Point, Tuple[float, float], Tuple[float, float, float]]] = None, timestamp: Optional[Union[str, datetime]] = None, srid: Optional[int] = 0, _inner=None) -> None: - super().__init__(string=string, value=point, timestamp=timestamp, _inner=_inner) + super().__init__(string=string, value=point, timestamp=timestamp, + _inner=_inner) if self._inner is None: - self._inner = tgeompoint_in(f"SRID={srid};{point}@{timestamp}") + if isinstance(point, tuple): + p = f'POINT({" ".join(str(x) for x in point)})' + else: + p = f'{point}' + self._inner = tgeompoint_in(f"SRID={srid};{p}@{timestamp}") -class TGeogPointInst(TPointInst['TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', 'TGeogPointSeqSet'], TGeogPoint): +class TGeogPointInst(TPointInst['TGeogPoint', 'TGeogPointInst', +'TGeogPointSeq', 'TGeogPointSeqSet'], TGeogPoint): """ Class for representing temporal geographic points at a single instant. """ @@ -1741,17 +1862,19 @@ class TGeogPointInst(TPointInst['TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', _cast_function = lambda x: None def __init__(self, string: Optional[str] = None, *, - point: Optional[Union[str, pg.Point, shp.Point, - Tuple[float, float]]] = None, + point: Optional[Union[str, shp.Point, Tuple[float, float], Tuple[float, float, float]]] = None, timestamp: Optional[Union[str, datetime]] = None, srid: Optional[int] = 4326, _inner=None) -> None: - super().__init__(string=string, value=point, timestamp=timestamp, _inner=_inner) + super().__init__(string=string, value=point, timestamp=timestamp, + _inner=_inner) if self._inner is None: - p = f'POINT({point[0]} {point[1]})' if isinstance(point, tuple) else f'{point}' + p = f'POINT({point[0]} {point[1]})' if isinstance(point, tuple) \ + else f'{point}' self._inner = tgeogpoint_in(f"SRID={srid};{p}@{timestamp}") -class TGeomPointSeq(TPointSeq['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', 'TGeomPointSeqSet'], TGeomPoint): +class TGeomPointSeq(TPointSeq['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', +'TGeomPointSeqSet'], TGeomPoint): """ Class for representing temporal geometric points over a period of time. """ @@ -1760,50 +1883,58 @@ class TGeomPointSeq(TPointSeq['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', ' def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[Union[str, TGeomPointInst]]] = None, lower_inc: bool = True, upper_inc: bool = False, - expandable: Union[bool, int] = False, interpolation: TInterpolation = TInterpolation.LINEAR, normalize: bool = True, _inner=None): - super().__init__(string=string, instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, - expandable=expandable, interpolation=interpolation, normalize=normalize, _inner=_inner) + super().__init__(string=string, instant_list=instant_list, + lower_inc=lower_inc, upper_inc=upper_inc, + interpolation=interpolation, + normalize=normalize, _inner=_inner) -class TGeogPointSeq(TPointSeq['TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', 'TGeogPointSeqSet'], TGeogPoint): +class TGeogPointSeq(TPointSeq['TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', +'TGeogPointSeqSet'], TGeogPoint): """ Class for representing temporal geographic points over a period of time. """ ComponentClass = TGeogPointInst - def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[Union[str, TGeogPointInst]]] = None, - lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, int] = False, - interpolation: TInterpolation = TInterpolation.LINEAR, normalize: bool = True, _inner=None): - super().__init__(string=string, instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, - expandable=expandable, interpolation=interpolation, + def __init__(self, string: Optional[str] = None, *, + instant_list: Optional[List[Union[str, TGeogPointInst]]] = None, + lower_inc: bool = True, upper_inc: bool = False, + interpolation: TInterpolation = TInterpolation.LINEAR, + normalize: bool = True, _inner=None): + super().__init__(string=string, instant_list=instant_list, + lower_inc=lower_inc, upper_inc=upper_inc, + interpolation=interpolation, normalize=normalize, _inner=_inner) -class TGeomPointSeqSet(TPointSeqSet['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', 'TGeomPointSeqSet'], TGeomPoint): +class TGeomPointSeqSet(TPointSeqSet['TGeomPoint', 'TGeomPointInst', +'TGeomPointSeq', 'TGeomPointSeqSet'], TGeomPoint): """ - Class for representing temporal geometric points over a period of time with gaps. + Class for representing temporal geometric points over a period of time + with gaps. """ ComponentClass = TGeomPointSeq def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List[Union[str, TGeomPointSeq]]] = None, - expandable: Union[bool, int] = False, normalize: bool = True, _inner=None): - super().__init__(string=string, sequence_list=sequence_list, - expandable=expandable, normalize=normalize, _inner=_inner) + super().__init__(string=string, sequence_list=sequence_list, + normalize=normalize, _inner=_inner) -class TGeogPointSeqSet(TPointSeqSet['TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', 'TGeogPointSeqSet'], TGeogPoint): +class TGeogPointSeqSet(TPointSeqSet['TGeogPoint', 'TGeogPointInst', +'TGeogPointSeq', 'TGeogPointSeqSet'], TGeogPoint): """ - Class for representing temporal geographic points over a period of time with gaps. + Class for representing temporal geographic points over a period of time + with gaps. """ ComponentClass = TGeogPointSeq def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List[Union[str, TGeogPointSeq]]] = None, - expandable: Union[bool, int] = False, normalize: bool = True, _inner=None): - super().__init__(string=string, sequence_list=sequence_list, - expandable=expandable, normalize=normalize, _inner=_inner) + super().__init__(string=string, sequence_list=sequence_list, + normalize=normalize, + _inner=_inner) diff --git a/pymeos/pymeos/main/ttext.py b/pymeos/pymeos/main/ttext.py index 331b29fa..10daf832 100644 --- a/pymeos/pymeos/main/ttext.py +++ b/pymeos/pymeos/main/ttext.py @@ -7,13 +7,15 @@ from pymeos_cffi import * from ..temporal import TInterpolation, Temporal, TInstant, TSequence, TSequenceSet -from ..time import * +from ..collections import * if TYPE_CHECKING: from .tbool import TBool class TText(Temporal[str, 'TText', 'TTextInst', 'TTextSeq', 'TTextSeqSet'], ABC): + _mobilitydb_name = 'ttext' + BaseClass = str _parse_function = ttext_in @@ -25,7 +27,8 @@ def __init__(self, _inner) -> None: @staticmethod def from_base_temporal(value: str, base: Temporal) -> TText: """ - Create a temporal string from a string and the time frame of another temporal object. + Create a temporal string from a string and the time frame of another + temporal object. Args: value: string value of the temporal. @@ -47,7 +50,8 @@ def from_base_time(value: str, base: datetime) -> TTextInst: @staticmethod @overload - def from_base_time(value: str, base: Union[TimestampSet, Period]) -> TTextSeq: + def from_base_time(value: str, base: Union[TimestampSet, Period]) -> \ + TTextSeq: ... @staticmethod @@ -72,13 +76,17 @@ def from_base_time(value: str, base: Time) -> TText: ttextseq_from_base_period, ttextseqset_from_base_periodset """ if isinstance(base, datetime): - return TTextInst(_inner=ttextinst_make(value, datetime_to_timestamptz(base))) + return TTextInst(_inner=ttextinst_make(value, + datetime_to_timestamptz(base))) elif isinstance(base, TimestampSet): - return TTextSeq(_inner=ttextseq_from_base_timestampset(value, base._inner)) + return TTextSeq(_inner=ttextseq_from_base_timestampset(value, + base._inner)) elif isinstance(base, Period): - return TTextSeq(_inner=ttextseq_from_base_period(value, base._inner)) + return TTextSeq(_inner=ttextseq_from_base_period(value, + base._inner)) elif isinstance(base, PeriodSet): - return TTextSeqSet(_inner=ttextseqset_from_base_periodset(value, base._inner)) + return TTextSeqSet(_inner=ttextseqset_from_base_periodset(value, + base._inner)) raise TypeError(f'Operation not supported with type {base.__class__}') # ------------------------- Output ---------------------------------------- @@ -170,7 +178,8 @@ def end_value(self) -> str: def upper(self) -> TText: """ - Returns a new temporal string with the values of `self` converted to upper case. + Returns a new temporal string with the values of `self` converted to + upper case. Returns: A new temporal string. @@ -182,7 +191,8 @@ def upper(self) -> TText: def lower(self) -> TText: """ - Returns a new temporal string with the values of `self` converted to lower case. + Returns a new temporal string with the values of `self` converted to + lower case. Returns: A new temporal string. @@ -205,7 +215,8 @@ def value_at_timestamp(self, timestamp: datetime) -> str: MEOS Functions: ttext_value_at_timestamp """ - result = ttext_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) + result = ttext_value_at_timestamp(self._inner, + datetime_to_timestamptz(timestamp), True) return text2cstring(result[0]) # ------------------------- Ever and Always Comparisons ------------------- @@ -217,7 +228,8 @@ def always_equal(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are always equal to `value`, `False` otherwise. + `True` if the values of `self` are always equal to `value`, + `False` otherwise. MEOS Functions: ttext_always_eq @@ -232,7 +244,8 @@ def always_not_equal(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are always not equal to `value`, `False` otherwise. + `True` if the values of `self` are always not equal to `value`, + `False` otherwise. MEOS Functions: ttext_ever_eq @@ -247,7 +260,8 @@ def always_less(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are always less than `value`, `False` otherwise. + `True` if the values of `self` are always less than `value`, + `False` otherwise. MEOS Functions: ttext_always_lt @@ -256,13 +270,15 @@ def always_less(self, value: str) -> bool: def always_less_or_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are always less than or equal to `value`. + Returns whether the values of `self` are always less than or equal to + `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are always less than or equal to `value`, `False` otherwise. + `True` if the values of `self` are always less than or equal to + `value`, `False` otherwise. MEOS Functions: ttext_always_le @@ -277,7 +293,8 @@ def always_greater(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are always greater than `value`, `False` otherwise. + `True` if the values of `self` are always greater than `value`, + `False` otherwise. MEOS Functions: ttext_ever_le @@ -286,13 +303,15 @@ def always_greater(self, value: str) -> bool: def always_greater_or_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are always greater than or equal to `value`. + Returns whether the values of `self` are always greater than or equal + to `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are always greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are always greater than or equal to + `value`, `False` otherwise. MEOS Functions: ttext_ever_lt @@ -307,7 +326,8 @@ def ever_equal(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are ever equal to `value`, `False` otherwise. + `True` if the values of `self` are ever equal to `value`, `False` + otherwise. MEOS Functions: ttext_ever_eq @@ -322,7 +342,8 @@ def ever_not_equal(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are ever not equal to `value`, `False` otherwise. + `True` if the values of `self` are ever not equal to `value`, + `False` otherwise. MEOS Functions: ttext_always_eq @@ -337,7 +358,8 @@ def ever_less(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are ever less than `value`, `False` otherwise. + `True` if the values of `self` are ever less than `value`, `False` + otherwise. MEOS Functions: ttext_ever_lt @@ -346,13 +368,15 @@ def ever_less(self, value: str) -> bool: def ever_less_or_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are ever less than or equal to `value`. + Returns whether the values of `self` are ever less than or equal to + `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are ever less than or equal to `value`, `False` otherwise. + `True` if the values of `self` are ever less than or equal to + `value`, `False` otherwise. MEOS Functions: ttext_ever_le @@ -367,7 +391,8 @@ def ever_greater(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are ever greater than `value`, `False` otherwise. + `True` if the values of `self` are ever greater than `value`, + `False` otherwise. MEOS Functions: ttext_always_le @@ -376,13 +401,15 @@ def ever_greater(self, value: str) -> bool: def ever_greater_or_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are ever greater than or equal to `value`. + Returns whether the values of `self` are ever greater than or equal to + `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are ever greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are ever greater than or equal to + `value`, `False` otherwise. MEOS Functions: ttext_always_lt @@ -397,7 +424,8 @@ def never_equal(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are never equal to `value`, `False` otherwise. + `True` if the values of `self` are never equal to `value`, `False` + otherwise. MEOS Functions: ttext_ever_eq @@ -412,7 +440,8 @@ def never_not_equal(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are never not equal to `value`, `False` otherwise. + `True` if the values of `self` are never not equal to `value`, + `False` otherwise. MEOS Functions: ttext_always_eq @@ -427,7 +456,8 @@ def never_less(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are never less than `value`, `False` otherwise. + `True` if the values of `self` are never less than `value`, `False` + otherwise. MEOS Functions: ttext_ever_lt @@ -436,13 +466,15 @@ def never_less(self, value: str) -> bool: def never_less_or_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are never less than or equal to `value`. + Returns whether the values of `self` are never less than or equal to + `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are never less than or equal to `value`, `False` otherwise. + `True` if the values of `self` are never less than or equal to + `value`, `False` otherwise. MEOS Functions: ttext_ever_le @@ -451,13 +483,15 @@ def never_less_or_equal(self, value: str) -> bool: def never_greater_or_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are never greater than or equal to `value`. + Returns whether the values of `self` are never greater than or equal to + `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are never greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are never greater than or equal to + `value`, `False` otherwise. MEOS Functions: ttext_always_lt @@ -472,7 +506,8 @@ def never_greater(self, value: str) -> bool: value: String value to compare. Returns: - `True` if the values of `self` are never greater than `value`, `False` otherwise. + `True` if the values of `self` are never greater than `value`, + `False` otherwise. MEOS Functions: ttext_always_le @@ -545,7 +580,8 @@ def temporal_less_or_equal(self, other: Union[str, Temporal]) -> Temporal: other: A string or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal less or equal relation. + A :class:`TBool` with the result of the temporal less or equal + relation. MEOS Functions: tle_ttext_text, tle_temporal_temporal @@ -564,7 +600,8 @@ def temporal_greater(self, other: Union[str, Temporal]) -> Temporal: other: A string or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal greater than relation. + A :class:`TBool` with the result of the temporal greater than + relation. MEOS Functions: tgt_ttext_text, tgt_temporal_temporal @@ -577,13 +614,15 @@ def temporal_greater(self, other: Union[str, Temporal]) -> Temporal: def temporal_greater_or_equal(self, other: Union[str, Temporal]) -> Temporal: """ - Returns the temporal greater or equal relation between `self` and `other`. + Returns the temporal greater or equal relation between `self` and + `other`. Args: other: A string or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal greater or equal relation. + A :class:`TBool` with the result of the temporal greater or equal + relation. MEOS Functions: tge_ttext_text, tge_temporal_temporal @@ -595,9 +634,11 @@ def temporal_greater_or_equal(self, other: Union[str, Temporal]) -> Temporal: return Temporal._factory(result) # ------------------------- Restrictions ---------------------------------- - def at(self, other: Union[str, List[str], datetime, TimestampSet, Period, PeriodSet]) -> TText: + def at(self, other: Union[str, List[str], datetime, TimestampSet, Period, + PeriodSet]) -> TText: """ - Returns a new temporal string with the values of `self` restricted to the time or value `other`. + Returns a new temporal string with the values of `self` restricted to + the time or value `other`. Args: other: Time or value to restrict to. @@ -606,22 +647,22 @@ def at(self, other: Union[str, List[str], datetime, TimestampSet, Period, Period A new temporal string. MEOS Functions: - ttext_at_value, temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset + ttext_at_value, temporal_at_timestamp, temporal_at_timestampset, + temporal_at_period, temporal_at_periodset """ if isinstance(other, str): result = ttext_at_value(self._inner, other) - elif isinstance(other, list): - # result = ttext_at_values(self._inner, other) - results = [ttext_at_value(self._inner, value) for value in other] - result = temporal_merge_array(results, len(results)) + elif isinstance(other, list) and isinstance(other[0], str): + result = temporal_at_values(self._inner, textset_make(other)) else: return super().at(other) return Temporal._factory(result) - def minus(self, other: Union[str, List[str], datetime, TimestampSet, Period, PeriodSet]) -> TText: + def minus(self, other: Union[str, List[str], datetime, TimestampSet, + Period, PeriodSet]) -> TText: """ - Returns a new temporal string with the values of `self` restricted to the complement of the time or value - `other`. + Returns a new temporal string with the values of `self` restricted to + the complement of the time or value `other`. Args: other: Time or value to restrict to the complement of. @@ -630,13 +671,14 @@ def minus(self, other: Union[str, List[str], datetime, TimestampSet, Period, Per A new temporal string. MEOS Functions: - ttext_minus_value, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, + ttext_minus_value, temporal_minus_timestamp, + temporal_minus_timestampset, temporal_minus_period, temporal_minus_periodset """ if isinstance(other, str): result = ttext_minus_value(self._inner, other) - elif isinstance(other, list): - result = reduce(ttext_minus_value, other, self._inner) + elif isinstance(other, list) and isinstance(other[0], str): + result = temporal_minus_values(self._inner, textset_make(other)) else: return super().minus(other) return Temporal._factory(result) @@ -644,11 +686,13 @@ def minus(self, other: Union[str, List[str], datetime, TimestampSet, Period, Per # ------------------------- Text Operations ------------------------------ def concatenate(self, other: Union[str, TText], other_before: bool = False): """ - Returns a new temporal string with the values of `self` concatenated with the values of `other`. + Returns a new temporal string with the values of `self` concatenated + with the values of `other`. Args: other: Temporal string or string to concatenate. - other_before: If `True` the values of `other` are prepended to the values of `self`. + other_before: If `True` the values of `other` are prepended to the + values of `self`. Returns: A new temporal string. @@ -658,10 +702,12 @@ def concatenate(self, other: Union[str, TText], other_before: bool = False): """ if isinstance(other, str): - result = textcat_ttext_text(self._inner, other) if not other_before \ + result = textcat_ttext_text(self._inner, other) \ + if not other_before \ else textcat_text_ttext(other, self._inner) elif isinstance(other, TText): - result = textcat_ttext_ttext(self._inner, other._inner) if not other_before \ + result = textcat_ttext_ttext(self._inner, other._inner) \ + if not other_before \ else textcat_ttext_ttext(other._inner, self._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') @@ -669,7 +715,8 @@ def concatenate(self, other: Union[str, TText], other_before: bool = False): def __add__(self, other): """ - Returns a new temporal string with the values of `self` concatenated with the values of `other`. + Returns a new temporal string with the values of `self` concatenated + with the values of `other`. Args: other: Temporal string or string to concatenate. @@ -684,7 +731,8 @@ def __add__(self, other): def __radd__(self, other): """ - Returns a new temporal string with the values of `other` concatenated with the values of `self`. + Returns a new temporal string with the values of `other` concatenated + with the values of `self`. Args: other: Temporal string or string to concatenate. @@ -701,7 +749,8 @@ def __radd__(self, other): @staticmethod def read_from_cursor(value, _=None): """ - Reads a :class:`TText` from a database cursor. Used when automatically loading objects from the database. + Reads a :class:`TText` from a database cursor. Used when automatically + loading objects from the database. Users should use the class constructor instead. """ if not value: @@ -718,37 +767,49 @@ def read_from_cursor(value, _=None): raise Exception("ERROR: Could not parse temporal text value") -class TTextInst(TInstant[str, 'TText', 'TTextInst', 'TTextSeq', 'TTextSeqSet'], TText): +class TTextInst(TInstant[str, 'TText', 'TTextInst', 'TTextSeq', +'TTextSeqSet'], TText): """ Class for representing temporal strings at a single instant. """ _make_function = ttextinst_make _cast_function = str - def __init__(self, string: Optional[str] = None, *, value: Optional[str] = None, - timestamp: Optional[Union[str, datetime]] = None, _inner=None): - super().__init__(string=string, value=value, timestamp=timestamp, _inner=_inner) + def __init__(self, string: Optional[str] = None, *, + value: Optional[str] = None, + timestamp: Optional[Union[str, datetime]] = None, + _inner=None): + super().__init__(string=string, value=value, timestamp=timestamp, + _inner=_inner) -class TTextSeq(TSequence[str, 'TText', 'TTextInst', 'TTextSeq', 'TTextSeqSet'], TText): +class TTextSeq(TSequence[str, 'TText', 'TTextInst', 'TTextSeq', +'TTextSeqSet'], TText): """ Class for representing temporal strings over a period of time. """ ComponentClass = TTextInst - def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[Union[str, TTextInst]]] = None, - lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, int] = False, - interpolation: TInterpolation = TInterpolation.STEPWISE, normalize: bool = True, _inner=None): - super().__init__(string=string, instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, - expandable=expandable, interpolation=interpolation, normalize=normalize, _inner=_inner) + def __init__(self, string: Optional[str] = None, *, + instant_list: Optional[List[Union[str, TTextInst]]] = None, + lower_inc: bool = True, upper_inc: bool = False, + interpolation: TInterpolation = TInterpolation.STEPWISE, + normalize: bool = True, _inner=None): + super().__init__(string=string, instant_list=instant_list, + lower_inc=lower_inc, upper_inc=upper_inc, + interpolation=interpolation, + normalize=normalize, _inner=_inner) -class TTextSeqSet(TSequenceSet[str, 'TText', 'TTextInst', 'TTextSeq', 'TTextSeqSet'], TText): +class TTextSeqSet(TSequenceSet[str, 'TText', 'TTextInst', 'TTextSeq', +'TTextSeqSet'], TText): """ Class for representing temporal strings over a period of time with gaps. """ ComponentClass = TTextSeq - def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List[Union[str, TTextSeq]]] = None, + def __init__(self, string: Optional[str] = None, *, + sequence_list: Optional[List[Union[str, TTextSeq]]] = None, normalize: bool = True, _inner=None): - super().__init__(string=string, sequence_list=sequence_list, normalize=normalize, _inner=_inner) + super().__init__(string=string, sequence_list=sequence_list, + normalize=normalize, _inner=_inner) diff --git a/pymeos/pymeos/meos_init.py b/pymeos/pymeos/meos_init.py index 2a4e0677..1999949e 100644 --- a/pymeos/pymeos/meos_init.py +++ b/pymeos/pymeos/meos_init.py @@ -1,20 +1,32 @@ from typing import Optional -from pymeos_cffi import meos_initialize, meos_finalize +from pymeos_cffi import meos_initialize, meos_finalize, meos_set_debug -def pymeos_initialize(timezone: Optional[str] = None) -> None: +def pymeos_initialize(timezone: Optional[str] = None, debug: bool = False) -> None: """ Initializes the underlying MEOS platform. Must be called before any other PyMEOS function. Args: timezone: :class:`str` indicating the desired timezone to be used. Defaults to system timezone. + debug: :class:`bool` indicating whether debug mode should be enabled. Defaults to False. MEOS Functions: meos_initialize """ meos_initialize(timezone) + meos_set_debug(debug) + + +def pymeos_set_debug(debug: bool) -> None: + """ + Sets the debug mode of the underlying MEOS platform. + + Args: + debug: :class:`bool` indicating whether debug mode should be enabled. + """ + meos_set_debug(debug) def pymeos_finalize() -> None: diff --git a/pymeos/pymeos/plotters/__init__.py b/pymeos/pymeos/plotters/__init__.py index 113688fe..358bfa6e 100644 --- a/pymeos/pymeos/plotters/__init__.py +++ b/pymeos/pymeos/plotters/__init__.py @@ -1,7 +1,7 @@ from .box_plotter import BoxPlotter from .point_sequence_plotter import TemporalPointSequencePlotter from .point_sequenceset_plotter import TemporalPointSequenceSetPlotter -from .range_plotter import RangePlotter +from .range_plotter import SpanPlotter from .sequence_plotter import TemporalSequencePlotter from .sequenceset_plotter import TemporalSequenceSetPlotter from .time_plotter import TimePlotter diff --git a/pymeos/pymeos/plotters/box_plotter.py b/pymeos/pymeos/plotters/box_plotter.py index 467feb1b..e9fd5c63 100644 --- a/pymeos/pymeos/plotters/box_plotter.py +++ b/pymeos/pymeos/plotters/box_plotter.py @@ -1,6 +1,6 @@ from matplotlib import pyplot as plt -from .range_plotter import RangePlotter +from .range_plotter import SpanPlotter from .time_plotter import TimePlotter from ..boxes import TBox, STBox @@ -14,7 +14,7 @@ class BoxPlotter: def plot_tbox(tbox: TBox, *args, axes=None, **kwargs): """ Plot a TBox on the given axes. If the TBox has only a temporal or spatial dimension, this is equivalent - to plotting the corresponding Period or Range respectively. + to plotting the corresponding Period or Span respectively. Params: tbox: The :class:`TBox` to plot. @@ -30,7 +30,7 @@ def plot_tbox(tbox: TBox, *args, axes=None, **kwargs): :func:`~pymeos.plotters.time_plotter.TimePlotter.plot_period` """ if not tbox.has_t: - return RangePlotter.plot_range(tbox.to_floatrange(), *args, axes=axes, **kwargs) + return SpanPlotter.plot_span(tbox.to_floatspan(), *args, axes=axes, **kwargs) if not tbox.has_x: return TimePlotter.plot_period(tbox.to_period(), *args, axes=axes, **kwargs) return BoxPlotter._plot_box(tbox.tmin(), tbox.tmax(), tbox.xmin(), tbox.xmax(), *args, axes=axes, **kwargs) diff --git a/pymeos/pymeos/plotters/point_sequence_plotter.py b/pymeos/pymeos/plotters/point_sequence_plotter.py index 20e895a2..68286a47 100644 --- a/pymeos/pymeos/plotters/point_sequence_plotter.py +++ b/pymeos/pymeos/plotters/point_sequence_plotter.py @@ -40,7 +40,7 @@ def plot_xy(sequence: Union[TPointSeq, List[TPointInst]], *args, axes=None, show else: plot_func = base.scatter - ins = sequence.instants() if isinstance(sequence, TPointSeq) else sequence + ins : list[TPointInst] = sequence.instants() if isinstance(sequence, TPointSeq) else sequence x = [i.x().value() for i in ins] y = [i.y().value() for i in ins] diff --git a/pymeos/pymeos/plotters/range_plotter.py b/pymeos/pymeos/plotters/range_plotter.py index 5a1dfbc1..e5584fb4 100644 --- a/pymeos/pymeos/plotters/range_plotter.py +++ b/pymeos/pymeos/plotters/range_plotter.py @@ -1,21 +1,21 @@ from typing import Union from matplotlib import pyplot as plt -from spans import floatrange, intrange +from pymeos import IntSpan, FloatSpan -class RangePlotter: +class SpanPlotter: """ - Plotter for :class:`floatrange` and :class:`intrange` objects. + Plotter for :class:`FloatSpan` and :class:`IntSpan` objects. """ @staticmethod - def plot_range(range: Union[floatrange, intrange], *args, axes=None, **kwargs): + def plot_span(span: Union[IntSpan, FloatSpan], *args, axes=None, **kwargs): """ - Plot a :class:`floatrange` or :class:`intrange` on the given axes. + Plot a :class:`FloatSpan` or :class:`IntSpan` on the given axes. Params: - range: The :class:`floatrange` or :class:`intrange` to plot. + span: The :class:`FloatSpan` or :class:`IntSpan` to plot. axes: The axes to plot on. If None, the current axes are used. *args: Additional arguments to pass to the plot function. **kwargs: Additional keyword arguments to pass to the plot function. @@ -24,8 +24,8 @@ def plot_range(range: Union[floatrange, intrange], *args, axes=None, **kwargs): List with the plotted elements. """ base = axes or plt.gca() - ll = base.axhline(range.lower, *args, linestyle='-' if range.lower_inc else '--', **kwargs) + ll = base.axhline(span.lower(), *args, linestyle='-' if span.lower_inc() else '--', **kwargs) kwargs.pop('label', None) - ul = base.axhline(range.upper, *args, linestyle='-' if range.upper_inc else '--', **kwargs) - s = base.axhspan(range.lower, range.upper, *args, alpha=0.3, **kwargs) + ul = base.axhline(span.upper(), *args, linestyle='-' if span.upper_inc() else '--', **kwargs) + s = base.axhspan(span.lower(), span.upper(), *args, alpha=0.3, **kwargs) return [ll, ul, s] diff --git a/pymeos/pymeos/plotters/time_plotter.py b/pymeos/pymeos/plotters/time_plotter.py index 16bd72bd..10ac6241 100644 --- a/pymeos/pymeos/plotters/time_plotter.py +++ b/pymeos/pymeos/plotters/time_plotter.py @@ -2,7 +2,7 @@ from matplotlib import pyplot as plt -from ..time import TimestampSet, Period, PeriodSet +from ..collections import TimestampSet, Period, PeriodSet class TimePlotter: diff --git a/pymeos/pymeos/temporal/interpolation.py b/pymeos/pymeos/temporal/interpolation.py index 4cb0b577..a6bbbe7b 100644 --- a/pymeos/pymeos/temporal/interpolation.py +++ b/pymeos/pymeos/temporal/interpolation.py @@ -5,7 +5,8 @@ class TInterpolation(IntEnum): """ - Enum for representing the different types of interpolation present in PyMEOS. + Enum for representing the different types of interpolation present in + PyMEOS. """ NONE = 0 DISCRETE = 1 @@ -19,13 +20,15 @@ def from_string(source: str, none: bool = True) -> TInterpolation: Args: source: :class:`string` representing the interpolation - none: indicates whether to return `TIntepolation.NONE` when `source` represents an invalid interpolation + none: indicates whether to return `TIntepolation.NONE` when + `source` represents an invalid interpolation Returns: A new :class:`TInterpolation` instance. Raises: - ValueError: when `source` doesn't represent any valid interpolation and `none` is False + ValueError: when `source` doesn't represent any valid interpolation + and `none` is False """ if source.lower() == 'discrete': diff --git a/pymeos/pymeos/temporal/temporal.py b/pymeos/pymeos/temporal/temporal.py index 42e0b9a0..26ca9eb3 100644 --- a/pymeos/pymeos/temporal/temporal.py +++ b/pymeos/pymeos/temporal/temporal.py @@ -7,10 +7,11 @@ from pymeos_cffi import * from .interpolation import TInterpolation -from ..time import * +from ..collections import * if TYPE_CHECKING: from .tinstant import TInstant + from .tsequence import TSequence from ..main import TBool from ..boxes import Box TBase = TypeVar('TBase') @@ -42,9 +43,6 @@ class Temporal(Generic[TBase, TG, TI, TS, TSS], ABC): _parse_function = None - def _expandable(self) -> bool: - return False - # ------------------------- Constructors ---------------------------------- @classmethod def _factory(cls, inner): @@ -139,32 +137,38 @@ def from_mfjson(cls: Type[Self], mfjson: str) -> Self: @classmethod def from_merge(cls: Type[Self], *temporals: TG) -> Self: """ - Returns a temporal object that is the result of merging the given temporal objects. + Returns a temporal object that is the result of merging the given + temporal objects. Args: *temporals: The temporal objects to merge. Returns: - A temporal object that is the result of merging the given temporal objects. + A temporal object that is the result of merging the given temporal + objects. MEOS Functions: temporal_merge_array """ - result = temporal_merge_array([temp._inner for temp in temporals if temp is not None], len(temporals)) + result = temporal_merge_array([temp._inner for temp in temporals \ + if temp is not None], len(temporals)) return Temporal._factory(result) @classmethod def from_merge_array(cls: Type[Self], temporals: List[TG]) -> Self: """ - Returns a temporal object that is the result of merging the given temporal objects. + Returns a temporal object that is the result of merging the given + temporal objects. Args: temporals: The temporal objects to merge. Returns: - A temporal object that is the result of merging the given temporal objects. + A temporal object that is the result of merging the given temporal + objects. """ - result = temporal_merge_array([temp._inner for temp in temporals if temp is not None], len(temporals)) + result = temporal_merge_array([temp._inner for temp in temporals \ + if temp is not None], len(temporals)) return Temporal._factory(result) # ------------------------- Output ---------------------------------------- @@ -179,7 +183,7 @@ def as_wkt(self) -> str: pass def as_mfjson(self, with_bbox: bool = True, flags: int = 3, - precision: int = 6, srs: Optional[str] = None) -> str: + precision: int = 6, srs: Optional[str] = None) -> str: """ Returns the temporal object as a MF-JSON string. @@ -222,7 +226,7 @@ def as_hexwkb(self) -> str: return temporal_as_hexwkb(self._inner, 4)[0] # ------------------------- Accessors ------------------------------------- - def bounding_box(self) -> TBox: + def bounding_box(self) -> Union[Period, Box]: """ Returns the bounding box of `self`. @@ -301,12 +305,14 @@ def time(self) -> PeriodSet: def duration(self, ignore_gaps=False) -> timedelta: """ - Returns the duration of `self`. By default, the gaps in `self` are taken into account, but this can be - changed by setting `ignore_gaps` to ``True``. This will only potentially alter the result for sequence sets and - discrete sequences. + Returns the duration of `self`. By default, the gaps in `self` are + taken into account, but this can be changed by setting `ignore_gaps` + to ``True``. This will only potentially alter the result for sequence + sets and discrete sequences. Parameters: - ignore_gaps: Whether to take into account potential time gaps in the temporal value. + ignore_gaps: Whether to take into account potential time gaps in + the temporal value. MEOS Functions: temporal_duration @@ -315,7 +321,8 @@ def duration(self, ignore_gaps=False) -> timedelta: def period(self) -> Period: """ - Returns the :class:`Period` on which `self` is defined ignoring potential time gaps. + Returns the :class:`Period` on which `self` is defined ignoring + potential time gaps. MEOS Functions: temporal_to_period @@ -324,7 +331,8 @@ def period(self) -> Period: def timespan(self) -> Period: """ - Returns the :class:`Period` on which `self` is defined ignoring potential time gaps. + Returns the :class:`Period` on which `self` is defined ignoring + potential time gaps. MEOS Functions: temporal_to_period @@ -475,7 +483,8 @@ def __hash__(self) -> int: # ------------------------- Transformations ------------------------------- def set_interpolation(self: Self, interpolation: TInterpolation) -> Self: """ - Returns a new :class:`Temporal` object equal to `self` with the given interpolation. + Returns a new :class:`Temporal` object equal to `self` with the given + interpolation. MEOS Functions: temporal_set_interpolation @@ -483,46 +492,53 @@ def set_interpolation(self: Self, interpolation: TInterpolation) -> Self: new_temp = temporal_set_interp(self._inner, interpolation) return Temporal._factory(new_temp) - def shift(self, delta: timedelta) -> Period: + def shift_time(self, delta: timedelta) -> Self: """ - Returns a new :class:`Temporal` with the temporal dimension shifted by ``delta``. + Returns a new :class:`Temporal` with the temporal dimension shifted by + ``delta``. Args: delta: :class:`datetime.timedelta` instance to shift MEOS Functions: - temporal_shift + temporal_shift_time """ - shifted = temporal_shift(self._inner, timedelta_to_interval(delta)) + shifted = temporal_shift_time(self._inner, timedelta_to_interval(delta)) return Temporal._factory(shifted) - def tscale(self, duration: timedelta) -> Period: + def scale_time(self, duration: timedelta) -> Self: """ - Returns a new :class:`Temporal` scaled so the temporal dimension has duration ``duration``. + Returns a new :class:`Temporal` scaled so the temporal dimension has + duration ``duration``. Args: - duration: :class:`datetime.timedelta` instance representing the duration of the new temporal + duration: :class:`datetime.timedelta` instance representing the + duration of the new temporal MEOS Functions: - temporal_tscale + temporal_scale_time """ - scaled = temporal_tscale(self._inner, timedelta_to_interval(duration)) + scaled = temporal_scale_time(self._inner, timedelta_to_interval(duration)) return Temporal._factory(scaled) - def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[timedelta] = None) -> Self: + def shift_scale_time(self, shift: Optional[timedelta] = None, + duration: Optional[timedelta] = None) -> Self: """ - Returns a new :class:`Temporal` with the time dimension shifted by ``shift`` and scaled so the temporal - dimension has duration ``duration``. + Returns a new :class:`Temporal` with the time dimension shifted by + ``shift`` and scaled so the temporal dimension has duration + ``duration``. Args: shift: :class:`datetime.timedelta` instance to shift - duration: :class:`datetime.timedelta` instance representing the duration of the new temporal + duration: :class:`datetime.timedelta` instance representing the + duration of the new temporal MEOS Functions: - temporal_shift_tscale + temporal_shift_scale_time """ - assert shift is not None or duration is not None, 'shift and duration must not be both None' - scaled = temporal_shift_tscale( + assert shift is not None or duration is not None, \ + 'shift and duration must not be both None' + scaled = temporal_shift_scale_time( self._inner, timedelta_to_interval(shift) if shift else None, timedelta_to_interval(duration) if duration else None @@ -530,20 +546,21 @@ def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[tim return Temporal._factory(scaled) def temporal_sample(self, duration: Union[str, timedelta], - start: Optional[Union[str, datetime]] = None) -> TG: + start: Optional[Union[str, datetime]] = None) -> TG: """ Returns a new :class:`Temporal` downsampled with respect to ``duration``. Args: - duration: A :class:`str` or :class:`timedelta` with the duration of the temporal tiles. - start: A :class:`str` or :class:`datetime` with the start time of the temporal tiles. If None, the start - time of `self` is used. - + duration: A :class:`str` or :class:`timedelta` with the duration of + the temporal tiles. + start: A :class:`str` or :class:`datetime` with the start time of + the temporal tiles. If None, the start time used by default is + Monday, January 3, 2000. MEOS Functions: temporal_tsample """ if start is None: - st = temporal_start_timestamp(self._inner) + st = pg_timestamptz_in('2000-01-03', -1) elif isinstance(start, datetime): st = datetime_to_timestamptz(start) else: @@ -551,25 +568,27 @@ def temporal_sample(self, duration: Union[str, timedelta], if isinstance(duration, timedelta): dt = timedelta_to_interval(duration) else: - pg_interval_in(duration, -1) + dt = pg_interval_in(duration, -1) result = temporal_tsample(self._inner, dt, st) return Temporal._factory(result) def temporal_precision(self, duration: Union[str, timedelta], - start: Optional[Union[str, datetime]] = None) -> TG: + start: Optional[Union[str, datetime]] = None) -> TG: """ Returns a new :class:`Temporal` with precision reduced to ``duration``. Args: - duration: A :class:`str` or :class:`timedelta` with the duration of the temporal tiles. - start: A :class:`str` or :class:`datetime` with the start time of the temporal tiles. If None, the start - time of `self` is used. + duration: A :class:`str` or :class:`timedelta` with the duration + of the temporal tiles. + start: A :class:`str` or :class:`datetime` with the start time of + the temporal tiles. If None, the start time used by default is + Monday, January 3, 2000. MEOS Functions: temporal_tprecision """ if start is None: - st = temporal_start_timestamp(self._inner) + st = pg_timestamptz_in('2000-01-03', -1) elif isinstance(start, datetime): st = datetime_to_timestamptz(start) else: @@ -577,7 +596,7 @@ def temporal_precision(self, duration: Union[str, timedelta], if isinstance(duration, timedelta): dt = timedelta_to_interval(duration) else: - pg_interval_in(duration, -1) + dt = pg_interval_in(duration, -1) result = temporal_tprecision(self._inner, dt, st) return Temporal._factory(result) @@ -591,29 +610,30 @@ def to_instant(self) -> TI: inst = temporal_to_tinstant(self._inner) return Temporal._factory(inst) - def to_sequence(self) -> TS: + def to_sequence(self, interpolation: TInterpolation) -> TS: """ Converts `self` into a :class:`TSequence`. MEOS Functions: temporal_to_sequence """ - seq = temporal_to_tsequence(self._inner) + seq = temporal_to_tsequence(self._inner, interpolation) return Temporal._factory(seq) - def to_sequenceset(self) -> TSS: + def to_sequenceset(self, interpolation: TInterpolation) -> TSS: """ Returns `self` as a :class:`TSequenceSet`. MEOS Functions: temporal_to_tsequenceset """ - ss = temporal_to_tsequenceset(self._inner) + ss = temporal_to_tsequenceset(self._inner, interpolation) return Temporal._factory(ss) def to_dataframe(self) -> DataFrame: """ - Returns `self` as a :class:`pd.DataFrame` with two columns: `time` and `value`. + Returns `self` as a :class:`pd.DataFrame` with two columns: `time` + and `value`. """ data = { 'time': self.timestamps(), @@ -623,9 +643,10 @@ def to_dataframe(self) -> DataFrame: # ------------------------- Modifications --------------------------------- def append_instant(self, instant: TInstant[TBase], max_dist: Optional[float] = 0.0, - max_time: Optional[timedelta] = None) -> TG: + max_time: Optional[timedelta] = None) -> TG: """ - Returns a new :class:`Temporal` object equal to `self` with `instant` appended. + Returns a new :class:`Temporal` object equal to `self` with `instant` + appended. Args: instant: :class:`TInstant` to append @@ -639,15 +660,14 @@ def append_instant(self, instant: TInstant[TBase], max_dist: Optional[float] = 0 interv = None else: interv = timedelta_to_interval(max_time) - new_inner = temporal_append_tinstant(self._inner, instant._inner, max_dist, - interv, self._expandable()) - if new_inner == self._inner: - return self + new_inner = temporal_append_tinstant(self._inner, instant._inner, + max_dist, interv, False) return Temporal._factory(new_inner) def append_sequence(self, sequence: TSequence[TBase]) -> TG: """ - Returns a new :class:`Temporal` object equal to `self` with `sequence` appended. + Returns a new :class:`Temporal` object equal to `self` with `sequence` + appended. Args: sequence: :class:`TSequence` to append @@ -655,15 +675,13 @@ def append_sequence(self, sequence: TSequence[TBase]) -> TG: MEOS Functions: temporal_append_tsequence """ - new_inner = temporal_append_tsequence(self._inner, sequence._inner, - self._expandable()) - if new_inner == self._inner: - return self + new_inner = temporal_append_tsequence(self._inner, sequence._inner, False) return Temporal._factory(new_inner) def merge(self, other: Union[type(None), Temporal[TBase], List[Temporal[TBase]]]) -> TG: """ - Returns a new :class:`Temporal` object that is the result of merging `self` with `other`. + Returns a new :class:`Temporal` object that is the result of merging + `self` with `other`. MEOS Functions: temporal_merge, temporal_merge_array @@ -675,18 +693,21 @@ def merge(self, other: Union[type(None), Temporal[TBase], List[Temporal[TBase]]] if isinstance(other, Temporal): new_temp = temporal_merge(self._inner, other._inner) elif isinstance(other, list): - new_temp = temporal_merge_array([self._inner, *(o._inner for o in other)], len(other) + 1) + new_temp = temporal_merge_array([self._inner, + *(o._inner for o in other)], len(other) + 1) else: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(new_temp) def insert(self, other: TG, connect: bool = True) -> TG: """ - Returns a new :class:`Temporal` object equal to `self` with `other` inserted. + Returns a new :class:`Temporal` object equal to `self` with `other` + inserted. Args: other: :class:`Temporal` object to insert in `self` - connect: wether to connect the inserted elements with the existing elements. + connect: wether to connect the inserted elements with the existing + elements. MEOS Functions: temporal_insert @@ -698,11 +719,13 @@ def insert(self, other: TG, connect: bool = True) -> TG: def update(self, other: TG, connect: bool = True) -> TG: """ - Returns a new :class:`Temporal` object equal to `self` updated with `other`. + Returns a new :class:`Temporal` object equal to `self` updated with + `other`. Args: other: :class:`Temporal` object to update `self` with - connect: wether to connect the updated elements with the existing elements. + connect: wether to connect the updated elements with the + existing elements. MEOS Functions: temporal_update @@ -714,23 +737,29 @@ def update(self, other: TG, connect: bool = True) -> TG: def delete(self, other: Time, connect: bool = True) -> TG: """ - Returns a new :class:`Temporal` object equal to `self` with elements at `other` removed. + Returns a new :class:`Temporal` object equal to `self` with elements at + `other` removed. Args: other: :class:`Time` object to remove from `self` - connect: wether to connect the potential gaps generated by the deletions. + connect: whether to connect the potential gaps generated by the + deletions. MEOS Functions: temporal_update """ if isinstance(other, datetime): - new_inner = temporal_delete_timestamp(self._inner, datetime_to_timestamptz(other), connect) + new_inner = temporal_delete_timestamp(self._inner, + datetime_to_timestamptz(other), connect) elif isinstance(other, TimestampSet): - new_inner = temporal_delete_timestampset(self._inner, other._inner, connect) + new_inner = temporal_delete_timestampset(self._inner, other._inner, + connect) elif isinstance(other, Period): - new_inner = temporal_delete_period(self._inner, other._inner, connect) + new_inner = temporal_delete_period(self._inner, other._inner, + connect) elif isinstance(other, PeriodSet): - new_inner = temporal_delete_periodset(self._inner, other._inner, connect) + new_inner = temporal_delete_periodset(self._inner, other._inner, + connect) else: raise TypeError(f'Operation not supported with type {other.__class__}') if new_inner == self._inner: @@ -740,7 +769,8 @@ def delete(self, other: Time, connect: bool = True) -> TG: # ------------------------- Restrictions ---------------------------------- def at(self, other: Time) -> TG: """ - Returns a new temporal object with the values of `self` restricted to the time `other`. + Returns a new temporal object with the values of `self` restricted to + the time `other`. Args: other: A time object to restrict the values of `self` to. @@ -749,10 +779,12 @@ def at(self, other: Time) -> TG: A new temporal object of the same subtype as `self`. MEOS Functions: - temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset + temporal_at_timestamp, temporal_at_timestampset, + temporal_at_period, temporal_at_periodset """ if isinstance(other, datetime): - result = temporal_at_timestamp(self._inner, datetime_to_timestamptz(other)) + result = temporal_at_timestamp(self._inner, + datetime_to_timestamptz(other)) elif isinstance(other, TimestampSet): result = temporal_at_timestampset(self._inner, other._inner) elif isinstance(other, Period): @@ -765,7 +797,8 @@ def at(self, other: Time) -> TG: def at_min(self) -> TG: """ - Returns a new temporal object containing the times ``self`` is at its minimum value. + Returns a new temporal object containing the times ``self`` is at its + minimum value. Returns: A new temporal object of the same subtype as `self`. @@ -778,7 +811,8 @@ def at_min(self) -> TG: def at_max(self) -> TG: """ - Returns a new temporal object containing the times ``self`` is at its maximum value. + Returns a new temporal object containing the times ``self`` is at its + maximum value. Returns: A new temporal object of the same subtype as `self`. @@ -791,7 +825,8 @@ def at_max(self) -> TG: def minus(self, other: Time) -> TG: """ - Returns a new temporal object with the values of `self` removing those happening at `other`. + Returns a new temporal object with the values of `self` removing those + happening at `other`. Args: other: A time object to remove from `self`. @@ -800,14 +835,16 @@ def minus(self, other: Time) -> TG: A new temporal object of the same subtype as `self`. MEOS Functions: - temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, temporal_minus_periodset + temporal_minus_timestamp, temporal_minus_timestampset, + temporal_minus_period, temporal_minus_periodset """ if isinstance(other, Period): result = temporal_minus_period(self._inner, other._inner) elif isinstance(other, PeriodSet): result = temporal_minus_periodset(self._inner, other._inner) elif isinstance(other, datetime): - result = temporal_minus_timestamp(self._inner, datetime_to_timestamptz(other)) + result = temporal_minus_timestamp(self._inner, + datetime_to_timestamptz(other)) elif isinstance(other, TimestampSet): result = temporal_minus_timestampset(self._inner, other._inner) else: @@ -816,7 +853,8 @@ def minus(self, other: Time) -> TG: def minus_min(self) -> TG: """ - Returns a new temporal object containing the times ``self`` is not at its minimum value. + Returns a new temporal object containing the times ``self`` is not at + its minimum value. Returns: A new temporal object of the same subtype as `self`. @@ -829,7 +867,8 @@ def minus_min(self) -> TG: def minus_max(self) -> TG: """ - Returns a new temporal object containing the times ``self`` is not at its maximum value. + Returns a new temporal object containing the times ``self`` is not at + its maximum value. Returns: A new temporal object of the same subtype as `self`. @@ -843,9 +882,10 @@ def minus_max(self) -> TG: # ------------------------- Topological Operations ------------------------ def is_adjacent(self, other: Union[Time, Temporal, Box]) -> bool: """ - Returns whether the bounding box of `self` is adjacent to the bounding box of `other`. - Temporal subclasses may override this method to provide more specific behavior related to their types and - check adjacency over more dimensions. + Returns whether the bounding box of `self` is adjacent to the bounding + box of `other`. Temporal subclasses may override this method to provide + more specific behavior related to their types an check adjacency over + more dimensions. Args: other: A time or temporal object to compare to `self`. @@ -860,7 +900,8 @@ def is_adjacent(self, other: Union[Time, Temporal, Box]) -> bool: def is_temporally_adjacent(self, other: Union[Time, Temporal, Box]) -> bool: """ - Returns whether the bounding period of `self` is temporally adjacent to the bounding period of `other`. + Returns whether the bounding period of `self` is temporally adjacent + to the bounding period of `other`. Args: other: A time or temporal object to compare to `self`. @@ -875,8 +916,9 @@ def is_temporally_adjacent(self, other: Union[Time, Temporal, Box]) -> bool: def is_contained_in(self, container: Union[Time, Temporal, Box]) -> bool: """ - Returns whether the bounding period of `self` is contained in the bounding period of `container`. - Temporal subclasses may override this method to provide more specific behavior related to their types + Returns whether the bounding period of `self` is contained in the + bounding period of `container`. Temporal subclasses may override this + method to provide more specific behavior related to their types Args: container: A time or temporal object to compare to `self`. @@ -889,9 +931,11 @@ def is_contained_in(self, container: Union[Time, Temporal, Box]) -> bool: """ return self.bounding_box().is_contained_in(container) - def is_temporally_contained_in(self, container: Union[Time, Temporal, Box]) -> bool: + def is_temporally_contained_in(self, + container: Union[Time, Temporal, Box]) -> bool: """ - Returns whether the bounding period of `self` is contained in the bounding period of `container`. + Returns whether the bounding period of `self` is contained in the + bounding period of `container`. Args: container: A time or temporal object to compare to `self`. @@ -906,8 +950,9 @@ def is_temporally_contained_in(self, container: Union[Time, Temporal, Box]) -> b def contains(self, content: Union[Time, Temporal, Box]) -> bool: """ - Returns whether the bounding period of `self` contains the bounding period of `content`. - Temporal subclasses may override this method to provide more specific behavior related to their types + Returns whether the bounding period of `self` contains the bounding + period of `content`. Temporal subclasses may override this method to + provide more specific behavior related to their types Args: content: A time or temporal object to compare to `self`. @@ -922,7 +967,8 @@ def contains(self, content: Union[Time, Temporal, Box]) -> bool: def __contains__(self, item): """ - Returns whether the bounding period of `self` contains the bounding period of `content`. + Returns whether the bounding period of `self` contains the bounding + period of `content`. Args: item: A time or temporal object to compare to `self`. @@ -937,7 +983,8 @@ def __contains__(self, item): def temporally_contains(self, content: Union[Time, Temporal, Box]) -> bool: """ - Returns whether the bounding period of `self` contains the bounding period of `content`. + Returns whether the bounding period of `self` contains the bounding + period of `content`. Args: content: A time or temporal object to compare to `self`. @@ -952,8 +999,9 @@ def temporally_contains(self, content: Union[Time, Temporal, Box]) -> bool: def overlaps(self, other: Union[Time, Temporal, Box]) -> bool: """ - Returns whether the bounding period of `self` overlaps the bounding period of `other`. - Temporal subclasses may override this method to provide more specific behavior related to their types + Returns whether the bounding period of `self` overlaps the bounding + period of `other`. Temporal subclasses may override this method to + provide more specific behavior related to their types Args: other: A time or temporal object to compare to `self`. @@ -968,7 +1016,8 @@ def overlaps(self, other: Union[Time, Temporal, Box]) -> bool: def temporally_overlaps(self, other: Union[Time, Temporal, Box]) -> bool: """ - Returns whether the bounding period of `self` overlaps the bounding period of `other`. + Returns whether the bounding period of `self` overlaps the bounding + period of `other`. Args: other: A time or temporal object to compare to `self`. @@ -983,8 +1032,9 @@ def temporally_overlaps(self, other: Union[Time, Temporal, Box]) -> bool: def is_same(self, other: Union[Time, Temporal, Box]) -> bool: """ - Returns whether the bounding period of `self` is the same as the bounding period of `other`. - Temporal subclasses may override this method to provide more specific behavior related to their types + Returns whether the bounding period of `self` is the same as the + bounding period of `other`. Temporal subclasses may override this + method to provide more specific behavior related to their types Args: other: A time or temporal object to compare to `self`. @@ -1015,7 +1065,8 @@ def is_before(self, other: Union[Time, Temporal, Box]) -> bool: def is_over_or_before(self, other: Union[Time, Temporal, Box]) -> bool: """ - Returns whether `self` is before `other` allowing overlap. That is, `self` doesn't extend after `other`. + Returns whether `self` is before `other` allowing overlap. That is, + `self` doesn't extend after `other`. Args: other: A time or temporal object to compare `self` to. @@ -1045,7 +1096,8 @@ def is_after(self, other: Union[Time, Temporal, Box]) -> bool: def is_over_or_after(self, other: Union[Time, Temporal, Box]) -> bool: """ - Returns whether `self` is after `other` allowing overlap. That is, `self` doesn't extend before `other`. + Returns whether `self` is after `other` allowing overlap. That is, + `self` doesn't extend before `other`. Args: other: A time or temporal object to compare `self` to. @@ -1105,15 +1157,19 @@ def hausdorff_distance(self, other: Temporal) -> float: return temporal_hausdorff_distance(self._inner, other._inner) # ------------------------- Split Operations ------------------------------ - def time_split(self, duration: Union[str, timedelta], start: Optional[Union[str, datetime]] = None) -> List[TG]: + def time_split(self, duration: Union[str, timedelta], + start: Optional[Union[str, datetime]] = None) -> List[TG]: """ - Returns a list of temporal objects of the same subtype as `self` with the same values as `self` but split in - temporal tiles of duration `duration` starting at `start`. + Returns a list of temporal objects of the same subtype as `self` with + the same values as `self` but split in temporal tiles of duration + `duration` starting at `start`. Args: - duration: A :class:`str` or :class:`timedelta` with the duration of the temporal tiles. - start: A :class:`str` or :class:`datetime` with the start time of the temporal tiles. If None, the start - time of `self` is used. + duration: A :class:`str` or :class:`timedelta` with the duration + of the temporal tiles. + start: A :class:`str` or :class:`datetime` with the start time of + the temporal tiles. If None, the start time used by default is + Monday, January 3, 2000. Returns: A list of temporal objects of the same subtype as `self`. @@ -1122,18 +1178,24 @@ def time_split(self, duration: Union[str, timedelta], start: Optional[Union[str, temporal_time_split """ if start is None: - st = temporal_start_timestamp(self._inner) + st = pg_timestamptz_in('2000-01-03', -1) else: - st = datetime_to_timestamptz(start) if isinstance(start, datetime) else pg_timestamptz_in(start, -1) - dt = timedelta_to_interval(duration) if isinstance(duration, timedelta) else pg_interval_in(duration, -1) - tiles, new_count = temporal_time_split(self._inner, dt, st) + st = datetime_to_timestamptz(start) \ + if isinstance(start, datetime) \ + else pg_timestamptz_in(start, -1) + dt = timedelta_to_interval(duration) \ + if isinstance(duration, timedelta) \ + else pg_interval_in(duration, -1) + fragments, times, count = temporal_time_split(self._inner, dt, st) from ..factory import _TemporalFactory - return [_TemporalFactory.create_temporal(tiles[i]) for i in range(new_count)] + return [_TemporalFactory.create_temporal(fragments[i]) \ + for i in range(count)] def time_split_n(self, n: int) -> List[TG]: """ - Returns a list of temporal objects of the same subtype as `self` with the same values as `self` but split in - n temporal tiles of equal duration. + Returns a list of temporal objects of the same subtype as `self` with + the same values as `self` but split in n temporal tiles of equal + duration. Args: n: An :class:`int` with the number of temporal tiles. @@ -1147,20 +1209,23 @@ def time_split_n(self, n: int) -> List[TG]: if self.end_timestamp() == self.start_timestamp(): return [self] st = temporal_start_timestamp(self._inner) - dt = timedelta_to_interval((self.end_timestamp() - self.start_timestamp()) / n) - tiles, new_count = temporal_time_split(self._inner, dt, st) + dt = timedelta_to_interval((self.end_timestamp() - + self.start_timestamp()) / n) + fragments, times, count = temporal_time_split(self._inner, dt, st) from ..factory import _TemporalFactory - return [_TemporalFactory.create_temporal(tiles[i]) for i in range(new_count)] + return [_TemporalFactory.create_temporal(fragments[i]) \ + for i in range(count)] def stops(self, max_distance: Optional[float] = 0.0, - min_duration: Optional[timedelta] = timedelta()) -> TSS: + min_duration: Optional[timedelta] = timedelta()) -> TSS: """ - Return the subsequences where the objects stay within an area with a given maximum size for at least - the specified duration. + Return the subsequences where the objects stay within an area with a + given maximum size for at least the specified duration. Args: max_distance: A :class:`float` with the maximum distance of a stop. - min_duration: A :class:`timedelta` with the minimum duration of a stop. + min_duration: A :class:`timedelta` with the minimum duration of + a stop. Returns: A :class:`SequenceSet` of the same subtype as `self` with the stops. @@ -1168,7 +1233,8 @@ def stops(self, max_distance: Optional[float] = 0.0, MEOS Functions: temporal_stops """ - new_inner = temporal_stops(self._inner, max_distance, timedelta_to_interval(min_duration)) + new_inner = temporal_stops(self._inner, max_distance, + timedelta_to_interval(min_duration)) from ..factory import _TemporalFactory return _TemporalFactory.create_temporal(new_inner) @@ -1246,7 +1312,8 @@ def temporal_less_or_equal(self, other: Temporal) -> TBool: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal less or equal relation. + A :class:`TBool` with the result of the temporal less or equal + relation. MEOS Functions: tle_temporal_temporal @@ -1257,13 +1324,15 @@ def temporal_less_or_equal(self, other: Temporal) -> TBool: def temporal_greater_or_equal(self, other: Temporal) -> TBool: """ - Returns the temporal greater or equal relation between `self` and `other`. + Returns the temporal greater or equal relation between `self` and + `other`. Args: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal greater or equal relation. + A :class:`TBool` with the result of the temporal greater or equal + relation. MEOS Functions: tge_temporal_temporal @@ -1280,7 +1349,8 @@ def temporal_greater(self, other: Temporal) -> TBool: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal greater than relation. + A :class:`TBool` with the result of the temporal greater than + relation. MEOS Functions: tgt_temporal_temporal @@ -1373,10 +1443,10 @@ def __ge__(self, other): other: A temporal object to compare to `self`. Returns: - A :class:`bool` with the result of the greater or equal than relation. + A :class:`bool` with the result of the greater or equal than + relation. MEOS Functions: temporal_ge """ return temporal_ge(self._inner, other._inner) - diff --git a/pymeos/pymeos/temporal/tinstant.py b/pymeos/pymeos/temporal/tinstant.py index 78a5b265..f9e98c0f 100644 --- a/pymeos/pymeos/temporal/tinstant.py +++ b/pymeos/pymeos/temporal/tinstant.py @@ -18,16 +18,20 @@ class TInstant(Temporal[TBase, TG, TI, TS, TSS], ABC): """ - Base class for temporal instant types, i.e. temporal values that are defined at a single point in time. + Base class for temporal instant types, i.e. temporal values that are + defined at a single point in time. """ __slots__ = ['_inner'] _make_function = None _cast_function = None - def __init__(self, string: Optional[str] = None, *, value: Optional[Union[str, TBase]] = None, - timestamp: Optional[Union[str, datetime]] = None, _inner=None): - assert (_inner is not None) or ((string is not None) != (value is not None and timestamp is not None)), \ + def __init__(self, string: Optional[str] = None, *, + value: Optional[Union[str, TBase]] = None, + timestamp: Optional[Union[str, datetime]] = None, + _inner=None): + assert (_inner is not None) or ((string is not None) != \ + (value is not None and timestamp is not None)), \ "Either string must be not None or both point and timestamp must be not" if _inner is not None: self._inner = as_tinstant(_inner) diff --git a/pymeos/pymeos/temporal/tsequence.py b/pymeos/pymeos/temporal/tsequence.py index fd2932be..9d8bc300 100644 --- a/pymeos/pymeos/temporal/tsequence.py +++ b/pymeos/pymeos/temporal/tsequence.py @@ -18,16 +18,16 @@ class TSequence(Temporal[TBase, TG, TI, TS, TSS], ABC): """ - Base class for temporal sequence types, i.e. temporal values that are defined over a continuous period of time. + Base class for temporal sequence types, i.e. temporal values that are + defined over a continuous period of time. """ - def _expandable(self) -> bool: - return self._expandable_sequence - # ------------------------- Constructors ---------------------------------- - def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[Union[str, Any]]] = None, - lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, int] = False, - interpolation: TInterpolation = TInterpolation.LINEAR, normalize: bool = True, _inner=None): + def __init__(self, string: Optional[str] = None, *, + instant_list: Optional[List[Union[str, Any]]] = None, + lower_inc: bool = True, upper_inc: bool = False, + interpolation: TInterpolation = TInterpolation.LINEAR, + normalize: bool = True, _inner=None): assert (_inner is not None) or ((string is not None) != (instant_list is not None)), \ "Either string must be not None or instant_list must be not" if _inner is not None: @@ -35,22 +35,18 @@ def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[ elif string is not None: self._inner = as_tsequence(self.__class__._parse_function(string)) else: - self._instants = [x._inner if isinstance(x, self.ComponentClass) else self.__class__._parse_function(x) for - x in instant_list] + self._instants = [x._inner if isinstance(x, self.ComponentClass) \ + else self.__class__._parse_function(x) for x in instant_list] count = len(self._instants) - if not expandable: - self._inner = tsequence_make(self._instants, count, lower_inc, upper_inc, - interpolation.value, normalize) - else: - max_size = max(expandable, count) if isinstance(expandable, int) else 2 * count - self._inner = tsequence_make_exp(self._instants, count, max_size, lower_inc, upper_inc, - interpolation.value, normalize) - self._expandable_sequence = bool(expandable) or self._inner.maxcount > self._inner.count + self._inner = tsequence_make(self._instants, count, lower_inc, + upper_inc, interpolation.value, normalize) @classmethod - def from_instants(cls: Type[Self], instant_list: Optional[List[Union[str, Any]]], + def from_instants(cls: Type[Self], + instant_list: Optional[List[Union[str, Any]]], lower_inc: bool = True, upper_inc: bool = False, - interpolation: TInterpolation = TInterpolation.LINEAR, normalize: bool = True) -> Self: + interpolation: TInterpolation = TInterpolation.LINEAR, + normalize: bool = True) -> Self: """ Create a temporal sequence from a list of instants. @@ -64,7 +60,8 @@ def from_instants(cls: Type[Self], instant_list: Optional[List[Union[str, Any]]] Returns: A new temporal sequence. """ - return cls(instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, interpolation=interpolation, + return cls(instant_list=instant_list, lower_inc=lower_inc, + upper_inc=upper_inc, interpolation=interpolation, normalize=normalize) # ------------------------- Accessors ------------------------------------- diff --git a/pymeos/pymeos/temporal/tsequenceset.py b/pymeos/pymeos/temporal/tsequenceset.py index 96e46719..a59eb461 100644 --- a/pymeos/pymeos/temporal/tsequenceset.py +++ b/pymeos/pymeos/temporal/tsequenceset.py @@ -18,16 +18,13 @@ class TSequenceSet(Temporal[TBase, TG, TI, TS, TSS], ABC): """ - Base class for temporal sequence set types, i.e. temporal values that are defined by a set of temporal sequences. + Base class for temporal sequence set types, i.e. temporal values that are + defined by a set of temporal sequences. """ - def _expandable(self) -> bool: - return self._expandable_sequenceset - # ------------------------- Constructors ---------------------------------- def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List[Union[str, Any]]] = None, - expandable: Union[bool, int] = False, normalize: bool = True, _inner=None): assert (_inner is not None) or ((string is not None) != (sequence_list is not None)), \ "Either string must be not None or sequence_list must be not" @@ -36,20 +33,14 @@ def __init__(self, string: Optional[str] = None, *, elif string is not None: self._inner = as_tsequenceset(self.__class__._parse_function(string)) else: - sequences = [x._inner if isinstance(x, self.ComponentClass) else self.__class__._parse_function(x) - for x in sequence_list] + sequences = [x._inner if isinstance(x, self.ComponentClass) \ + else self.__class__._parse_function(x) for x in sequence_list] count = len(sequences) self._inner = tsequenceset_make(sequences, count, normalize) - if not expandable: - self._inner = tsequenceset_make(sequences, count, normalize) - else: - max_size = max(expandable, count) if isinstance(expandable, int) else 2 * count - self._inner = tsequenceset_make_exp(sequences, count, max_size, - normalize) - self._expandable_sequenceset = bool(expandable) or self._inner.maxcount > self._inner.count @classmethod - def from_sequences(cls: Type[Self], sequence_list: Optional[List[Union[str, Any]]] = None, + def from_sequences(cls: Type[Self], + sequence_list: Optional[List[Union[str, Any]]] = None, normalize: bool = True) -> Self: """ Create a temporal sequence set from a list of sequences. @@ -102,7 +93,8 @@ def to_dataframe(self) -> DataFrame: """ sequences = self.sequences() data = { - 'sequence': [i for i, seq in enumerate(sequences) for _ in range(seq.num_instants())], + 'sequence': [i for i, seq in enumerate(sequences) \ + for _ in range(seq.num_instants())], 'time': [t for seq in sequences for t in seq.timestamps()], 'value': [v for seq in sequences for v in seq.values()] } diff --git a/pymeos/pymeos/time/period.py b/pymeos/pymeos/time/period.py deleted file mode 100644 index ecbd64c7..00000000 --- a/pymeos/pymeos/time/period.py +++ /dev/null @@ -1,1017 +0,0 @@ -from __future__ import annotations - -from datetime import datetime, timedelta -from typing import Optional, Union, overload, TYPE_CHECKING, get_args - -from dateutil.parser import parse -from pymeos_cffi import * - -if TYPE_CHECKING: - from ..temporal import Temporal - from ..boxes import Box - from .periodset import PeriodSet - from .timestampset import TimestampSet - from .time import Time - - -class Period: - """ - Class for representing sets of contiguous timestamps between a lower and - an upper bound. The bounds may be inclusive or not. - - ``Period`` objects can be created with a single argument of type string - as in MobilityDB. - - >>> Period('(2019-09-08 00:00:00+01, 2019-09-10 00:00:00+01)') - - Another possibility is to provide the ``lower`` and ``upper`` named parameters (of type str or datetime), and - optionally indicate whether the bounds are inclusive or exclusive (by default, the lower bound is inclusive and the - upper is exclusive): - - >>> Period(lower='2019-09-08 00:00:00+01', upper='2019-09-10 00:00:00+01') - >>> Period(lower='2019-09-08 00:00:00+01', upper='2019-09-10 00:00:00+01', lower_inc=False, upper_inc=True) - >>> Period(lower=parse('2019-09-08 00:00:00+01'), upper=parse('2019-09-10 00:00:00+01'), upper_inc=True) - """ - - __slots__ = ['_inner'] - - # ------------------------- Constructors ---------------------------------- - def __init__(self, string: Optional[str] = None, *, - lower: Optional[Union[str, datetime]] = None, - upper: Optional[Union[str, datetime]] = None, - lower_inc: bool = True, - upper_inc: bool = False, - _inner=None): - super().__init__() - assert (_inner is not None) or ((string is not None) != (lower is not None and upper is not None)), \ - "Either string must be not None or both lower and upper must be not" - if _inner is not None: - self._inner = _inner - elif string is not None: - self._inner = period_in(string) - else: - lower_ts = pg_timestamptz_in(lower, -1) if isinstance(lower, str) else datetime_to_timestamptz(lower) - upper_ts = pg_timestamptz_in(upper, -1) if isinstance(upper, str) else datetime_to_timestamptz(upper) - self._inner = period_make(lower_ts, upper_ts, lower_inc, upper_inc) - - def __copy__(self): - """ - Return a copy of ``self``. - - Returns: - A new :class:`Period` instance - - MEOS Functions: - span_copy - """ - inner_copy = span_copy(self._inner) - return Period(_inner=inner_copy) - - @staticmethod - def from_wkb(wkb: bytes) -> Period: - """ - Returns a `Period` from its WKB representation. - - Args: - wkb: The WKB string. - - Returns: - A new :class:`Period` instance - - MEOS Functions: - span_from_wkb - """ - result = span_from_wkb(wkb) - return Period(_inner=result) - - @staticmethod - def from_hexwkb(hexwkb: str) -> Period: - """ - Returns a `Period` from its WKB representation in hex-encoded ASCII. - - Args: - hexwkb: WKB representation in hex-encoded ASCII - - Returns: - A new :class:`Period` instance - - MEOS Functions: - span_from_hexwkb - """ - result = span_from_hexwkb(hexwkb) - return Period(_inner=result) - - # ------------------------- Output ---------------------------------------- - def __str__(self): - """ - Return the string representation of the content of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - period_out - """ - return period_out(self._inner) - - def __repr__(self): - """ - Return the string representation of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - period_out - """ - return (f'{self.__class__.__name__}' - f'({self})') - - def as_wkb(self) -> bytes: - """ - Returns the WKB representation of ``self``. - - Returns: - A :class:`str` object with the WKB representation of ``self``. - - MEOS Functions: - span_as_wkb - """ - return span_as_wkb(self._inner, 4) - - def as_hexwkb(self) -> str: - """ - Returns the WKB representation of ``self`` in hex-encoded ASCII. - - Returns: - A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. - - MEOS Functions: - span_as_hexwkb - """ - return span_as_hexwkb(self._inner, -1)[0] - - # ------------------------- Conversions ----------------------------------- - def to_periodset(self) -> PeriodSet: - """ - Returns a period set containing ``self``. - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - span_to_spanset - """ - from .periodset import PeriodSet - return PeriodSet(_inner=span_to_spanset(self._inner)) - - # ------------------------- Accessors ------------------------------------- - def lower(self) -> datetime: - """ - Returns the lower bound of a period - - Returns: - The lower bound of the period as a :class:`datetime.datetime` - - MEOS Functions: - period_lower - """ - return timestamptz_to_datetime(period_lower(self._inner)) - - def upper(self) -> datetime: - """ - Returns the upper bound of a period - - Returns: - The upper bound of the period as a :class:`datetime.datetime` - - MEOS Functions: - period_upper - """ - return timestamptz_to_datetime(period_upper(self._inner)) - - def lower_inc(self) -> bool: - """ - Returns whether the lower bound of the period is inclusive or not - - Returns: - True if the lower bound of the period is inclusive and False otherwise - - MEOS Functions: - span_lower_inc - """ - return span_lower_inc(self._inner) - - def upper_inc(self) -> bool: - """ - Returns whether the upper bound of the period is inclusive or not - - Returns: - True if the upper bound of the period is inclusive and False otherwise - - MEOS Functions: - span_upper_inc - """ - return span_upper_inc(self._inner) - - def duration(self) -> timedelta: - """ - Returns the duration of the period. - - Returns: - A :class:`datetime.timedelta` instance representing the duration of the period - - MEOS Functions: - period_duration - """ - return interval_to_timedelta(period_duration(self._inner)) - - def duration_in_seconds(self) -> float: - """ - Returns the duration of the period. - - Returns: - Returns a `float` representing the duration of the period in seconds - - MEOS Functions: - span_width - """ - return span_width(self._inner) - - def __hash__(self) -> int: - """ - Return the hash representation of ``self``. - - Returns: - A new :class:`int` instance - - MEOS Functions: - span_hash - """ - return span_hash(self._inner) - - # ------------------------- Transformations ------------------------------- - def shift(self, delta: timedelta) -> Period: - """ - Returns a new period that is the result of shifting ``self`` by ``delta`` - - Examples: - >>> Period('[2000-01-01, 2000-01-10]').shift(timedelta(days=2)) - >>> 'Period([2000-01-03 00:00:00+01, 2000-01-12 00:00:00+01])' - - Args: - delta: :class:`datetime.timedelta` instance to shift - - Returns: - A new :class:`Period` instance - - MEOS Functions: - period_shift_tscale - """ - return self.shift_tscale(shift=delta) - - def tscale(self, duration: timedelta) -> Period: - """ - Returns a new period that starts as ``self`` but has duration ``duration`` - - Examples: - >>> Period('[2000-01-01, 2000-01-10]').tscale(timedelta(days=2)) - >>> 'Period([2000-01-01 00:00:00+01, 2000-01-03 00:00:00+01])' - - Args: - duration: :class:`datetime.timedelta` instance representing the duration of the new period - - Returns: - A new :class:`Period` instance - - MEOS Functions: - period_shift_tscale - """ - return self.shift_tscale(duration=duration) - - def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[timedelta] = None) -> Period: - """ - Returns a new period that starts at ``self`` shifted by ``shift`` and has duration ``duration`` - - Examples: - >>> Period('[2000-01-01, 2000-01-10]').shift_tscale(shift=timedelta(days=2), duration=timedelta(days=4)) - >>> 'Period([2000-01-03 00:00:00+01, 2000-01-07 00:00:00+01])' - - Args: - shift: :class:`datetime.timedelta` instance to shift - duration: :class:`datetime.timedelta` instance representing the duration of the new period - - Returns: - A new :class:`Period` instance - - MEOS Functions: - period_shift_tscale - """ - assert shift is not None or duration is not None, 'shift and scale deltas must not be both None' - modified = period_shift_tscale( - self._inner, - timedelta_to_interval(shift) if shift else None, - timedelta_to_interval(duration) if duration else None, - ) - return Period(_inner=modified) - - def expand(self, other: Period) -> Period: - """ - Returns a new period that includes both ``self`` and ``other`` - - Examples: - >>> Period('[2000-01-01, 2000-01-04)').expand(Period('[2000-01-05, 2000-01-10]')) - >>> 'Period([2000-01-01 00:00:00+01, 2000-01-10 00:00:00+01])' - - Args: - other: :class:`Period` instance to expand the period - - Returns: - A new :class:`Period` instance - - MEOS Functions: - span_expand - """ - copy = span_copy(self._inner) - span_expand(other._inner, copy) - return Period(_inner=copy) - - # ------------------------- Topological Operations ------------------------ - def is_adjacent(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is temporally adjacent to ``other``. That is, they share a bound but only one of them - contains it. - - Examples: - >>> Period('[2012-01-01, 2012-01-02)').is_adjacent(Period('[2012-01-02, 2012-01-03]')) - >>> True - >>> Period('[2012-01-01, 2012-01-02]').is_adjacent(Period('[2012-01-02, 2012-01-03]')) - >>> False # Both contain bound - >>> Period('[2012-01-01, 2012-01-02)').is_adjacent(Period('(2012-01-02, 2012-01-03]')) - >>> False # Neither contain bound - - Args: - other: temporal object to compare with - - Returns: - True if adjacent, False otherwise - - MEOS Functions: - adjacent_span_span, adjacent_span_spanset, adjacent_period_timestamp, - adjacent_period_timestampset, adjacent_period_temporal - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Period): - return adjacent_span_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return adjacent_spanset_span(other._inner, self._inner) - elif isinstance(other, datetime): - return adjacent_period_timestamp(self._inner, datetime_to_timestamptz(other)) - elif isinstance(other, TimestampSet): - return adjacent_span_span(self._inner, set_span(other._inner)) - elif isinstance(other, Temporal): - return adjacent_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return adjacent_span_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_contained_in(self, container: Union[Period, PeriodSet, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is temporally contained in ``container``. - - Examples: - >>> Period('[2012-01-02, 2012-01-03]').is_contained_in(Period('[2012-01-01, 2012-01-04]')) - >>> True - >>> Period('(2012-01-01, 2012-01-02)').is_contained_in(Period('[2012-01-01, 2012-01-02]')) - >>> True - >>> Period('[2012-01-01, 2012-01-02]').is_contained_in(Period('(2012-01-01, 2012-01-02)')) - >>> False - - Args: - container: temporal object to compare with - - Returns: - True if contained, False otherwise - - MEOS Functions: - contained_span_span, contained_span_spanset, contained_period_temporal - """ - from .periodset import PeriodSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(container, Period): - return contained_span_span(self._inner, container._inner) - elif isinstance(container, PeriodSet): - return contained_span_spanset(self._inner, container._inner) - elif isinstance(container, Temporal): - return contained_span_span(self._inner, temporal_to_period(container._inner)) - elif isinstance(container, Box): - return contained_span_span(self._inner, container.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {container.__class__}') - - def contains(self, content: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` temporally contains ``content``. - - Examples: - >>> Period('[2012-01-01, 2012-01-04]').contains(Period('[2012-01-02, 2012-01-03]')) - >>> True - >>> Period('[2012-01-01, 2012-01-02]').contains(Period('(2012-01-01, 2012-01-02)')) - >>> True - >>> Period('(2012-01-01, 2012-01-02)').contains(Period('[2012-01-01, 2012-01-02]')) - >>> False - - Args: - content: temporal object to compare with - - Returns: - True if contains, False otherwise - - MEOS Functions: - contains_span_span, contains_span_spanset, contains_period_timestamp, - contains_period_timestampset, contains_period_temporal - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(content, Period): - return contains_span_span(self._inner, content._inner) - elif isinstance(content, PeriodSet): - return contains_span_spanset(self._inner, content._inner) - elif isinstance(content, datetime): - return contains_period_timestamp(self._inner, datetime_to_timestamptz(content)) - elif isinstance(content, TimestampSet): - return contains_span_span(self._inner, set_span(content._inner)) - elif isinstance(content, Temporal): - return contains_span_span(self._inner, temporal_to_period(content._inner)) - elif isinstance(content, get_args(Box)): - return contains_span_span(self._inner, content.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {content.__class__}') - - def __contains__(self, item): - """ - Return whether ``self`` temporally contains ``item``. - - Examples: - >>> Period('[2012-01-02, 2012-01-03]') in Period('[2012-01-01, 2012-01-04]') - >>> True - >>> Period('(2012-01-01, 2012-01-02)') in Period('[2012-01-01, 2012-01-02]') - >>> True - >>> Period('[2012-01-01, 2012-01-02]') in Period('(2012-01-01, 2012-01-02)') - >>> False - - Args: - item: temporal object to compare with - - Returns: - True if contains, False otherwise - - MEOS Functions: - contains_span_span, contains_span_spanset, contains_period_timestamp, - contains_period_timestampset, contains_period_temporal - """ - return self.contains(item) - - def overlaps(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` temporally overlaps ``other``. That is, both share at least an instant - - Examples: - >>> Period('[2012-01-01, 2012-01-02]').overlaps(Period('[2012-01-02, 2012-01-03]')) - >>> True - >>> Period('[2012-01-01, 2012-01-02)').overlaps(Period('[2012-01-02, 2012-01-03]')) - >>> False - >>> Period('[2012-01-01, 2012-01-02)').overlaps(Period('(2012-01-02, 2012-01-03]')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if overlaps, False otherwise - - MEOS Functions: - overlaps_span_span, overlaps_span_spanset, overlaps_period_timestampset, - overlaps_period_temporal - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Period): - return overlaps_span_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return overlaps_spanset_span(other._inner, self._inner) - elif isinstance(other, datetime): - return overlaps_span_span(self._inner, timestamp_to_period(datetime_to_timestamptz(other))) - elif isinstance(other, TimestampSet): - return overlaps_span_span(self._inner, set_span(other._inner)) - elif isinstance(other, Temporal): - return overlaps_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return overlaps_span_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_same(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` and the bounding period of ``other`` is the same. - - Args: - other: temporal object to compare with - - Returns: - True if equal, False otherwise - - MEOS Functions: - same_period_temporal - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Temporal): - return span_eq(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return span_eq(self._inner, other.to_period()._inner) - elif isinstance(other, Period): - return span_eq(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return span_eq(self._inner, spanset_span(other._inner)) - elif isinstance(other, datetime): - return span_eq(self._inner, timestamp_to_period(datetime_to_timestamptz(other))) - elif isinstance(other, TimestampSet): - return span_eq(self._inner, set_span(other._inner)) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - # ------------------------- Position Operations --------------------------- - def is_before(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is strictly before ``other``. That is, ``self`` ends before ``other`` starts. - - Examples: - >>> Period('[2012-01-01, 2012-01-02)').is_before(Period('[2012-01-02, 2012-01-03]')) - >>> True - >>> Period('[2012-01-01, 2012-01-02)').is_before(Period('(2012-01-02, 2012-01-03]')) - >>> True - >>> Period('[2012-01-01, 2012-01-02]').is_before(Period('[2012-01-02, 2012-01-03]')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if before, False otherwise - - MEOS Functions: - left_span_span, left_span_spanset, before_period_timestamp, - before_period_timestampset, before_period_temporal - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Period): - return left_span_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return left_span_spanset(self._inner, other._inner) - elif isinstance(other, datetime): - return overafter_timestamp_period(datetime_to_timestamptz(other), self._inner) - if isinstance(other, TimestampSet): - return left_span_span(self._inner, set_span(other._inner)) - elif isinstance(other, Temporal): - return left_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return left_span_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_over_or_before(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is before ``other`` allowing overlap. That is, ``self`` ends before ``other`` ends (or - at the same time). - - Examples: - >>> Period('[2012-01-01, 2012-01-02)').is_over_or_before(Period('[2012-01-02, 2012-01-03]')) - >>> True - >>> Period('[2012-01-01, 2012-01-02]').is_over_or_before(Period('[2012-01-02, 2012-01-03]')) - >>> True - >>> Period('[2012-01-03, 2012-01-05]').is_over_or_before(Period('[2012-01-01, 2012-01-04]')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if before, False otherwise - - MEOS Functions: - overleft_span_span, overleft_span_spanset, overbefore_period_timestamp, - overbefore_period_timestampset, overbefore_period_temporal - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Period): - return overleft_span_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return overleft_span_spanset(self._inner, other._inner) - elif isinstance(other, datetime): - return overbefore_period_timestamp(self._inner, datetime_to_timestamptz(other)) - if isinstance(other, TimestampSet): - return overleft_span_span(self._inner, set_span(other._inner)) - elif isinstance(other, Temporal): - return overleft_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return overleft_span_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_after(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is strictly after ``other``. That is, ``self`` starts after ``other`` ends. - - Examples: - >>> Period('[2012-01-02, 2012-01-03]').is_after(Period('[2012-01-01, 2012-01-02)')) - >>> True - >>> Period('(2012-01-02, 2012-01-03]').is_after(Period('[2012-01-01, 2012-01-02)')) - >>> True - >>> Period('[2012-01-02, 2012-01-03]').is_after(Period('[2012-01-01, 2012-01-02]')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if after, False otherwise - - MEOS Functions: - right_span_span, right_span_spanset, after_period_timestamp, - after_period_timestampset, after_period_temporal - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Period): - return right_span_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return right_span_spanset(self._inner, other._inner) - elif isinstance(other, datetime): - return overbefore_timestamp_period(datetime_to_timestamptz(other), self._inner) - if isinstance(other, TimestampSet): - return right_span_span(self._inner, set_span(other._inner)) - elif isinstance(other, Temporal): - return right_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return right_span_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_over_or_after(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is after ``other`` allowing overlap. That is, ``self`` starts after ``other`` starts - (or at the same time). - - Examples: - >>> Period('[2012-01-02, 2012-01-03]').is_over_or_after(Period('[2012-01-01, 2012-01-02)')) - >>> True - >>> Period('[2012-01-02, 2012-01-03]').is_over_or_after(Period('[2012-01-01, 2012-01-02]')) - >>> True - >>> Period('[2012-01-02, 2012-01-03]').is_over_or_after(Period('[2012-01-01, 2012-01-03]')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if overlapping or after, False otherwise - - MEOS Functions: - overright_span_span, overright_span_spanset, overafter_period_timestamp, - overafter_period_timestampset, overafter_period_temporal - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Period): - return overright_span_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return overright_span_spanset(self._inner, other._inner) - elif isinstance(other, datetime): - return overafter_period_timestamp(self._inner, datetime_to_timestamptz(other)) - if isinstance(other, TimestampSet): - return overright_span_span(self._inner, set_span(other._inner)) - elif isinstance(other, Temporal): - return overright_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return overright_span_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - # ------------------------- Distance Operations --------------------------- - def distance(self, other: Union[Time, Box, Temporal]) -> timedelta: - """ - Returns the temporal distance between ``self`` and ``other``. - - Args: - other: temporal object to compare with - - Returns: - A :class:`datetime.timedelta` instance - - MEOS Functions: - distance_span_span, distance_spanset_span, distance_period_timestamp - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Temporal): - return timedelta(seconds=distance_span_span(self._inner, temporal_to_period(other._inner))) - elif isinstance(other, Period): - return timedelta(seconds=distance_span_span(self._inner, other._inner)) - elif isinstance(other, PeriodSet): - return timedelta(seconds=distance_spanset_span(other._inner, self._inner)) - elif isinstance(other, datetime): - return timedelta(seconds=distance_period_timestamp(self._inner, datetime_to_timestamptz(other))) - elif isinstance(other, TimestampSet): - return timedelta(seconds=distance_span_span(self._inner, set_span(other._inner))) - elif isinstance(other, get_args(Box)): - return timedelta(seconds=distance_span_span(self._inner, other.to_period()._inner)) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - # ------------------------- Set Operations -------------------------------- - @overload - def intersection(self, other: datetime) -> Optional[datetime]: - ... - - @overload - def intersection(self, other: Period) -> Optional[Period]: - ... - - @overload - def intersection(self, other: Union[TimestampSet, PeriodSet]) -> Optional[PeriodSet]: - ... - - def intersection(self, other: Time) -> Optional[Time]: - """ - Returns the temporal intersection of ``self`` and ``other``. - - Args: - other: temporal object to intersect with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - intersection_span_span, intersection_spanset_span, intersection_period_timestamp - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - if isinstance(other, datetime): - result = intersection_period_timestamp(self._inner, datetime_to_timestamptz(other)) - return timestamptz_to_datetime(result) if result is not None else None - elif isinstance(other, TimestampSet): - result = intersection_spanset_span(set_to_spanset(other._inner), self._inner) - return TimestampSet(_inner=result) if result is not None else None - elif isinstance(other, Period): - result = intersection_span_span(self._inner, other._inner) - return Period(_inner=result) if result is not None else None - elif isinstance(other, PeriodSet): - result = intersection_spanset_span(other._inner, self._inner) - return PeriodSet(_inner=result) if result is not None else None - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __mul__(self, other): - """ - Returns the temporal intersection of ``self`` and ``other``. - - Args: - other: temporal object to intersect with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - intersection_span_span, intersection_spanset_span, intersection_period_timestamp - """ - return self.intersection(other) - - def minus(self, other: Time) -> PeriodSet: - """ - Returns the temporal difference of ``self`` and ``other``. - - Args: - other: temporal object to diff with - - Returns: - A :class:`PeriodSet` instance. - - MEOS Functions: - minus_period_timestamp, minus_span_spanset, minus_span_span - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - if isinstance(other, datetime): - result = minus_period_timestamp(self._inner, datetime_to_timestamptz(other)) - return PeriodSet(_inner=result) if result is not None else None - elif isinstance(other, TimestampSet): - result = minus_span_spanset(self._inner, set_to_spanset(other._inner)) - return PeriodSet(_inner=result) if result is not None else None - elif isinstance(other, Period): - result = minus_span_span(self._inner, other._inner) - return PeriodSet(_inner=result) if result is not None else None - elif isinstance(other, PeriodSet): - result = minus_span_spanset(self._inner, other._inner) - return PeriodSet(_inner=result) if result is not None else None - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __sub__(self, other): - """ - Returns the temporal difference of ``self`` and ``other``. - - Args: - other: temporal object to diff with - - Returns: - A :class:`PeriodSet` instance. - - MEOS Functions: - minus_period_timestamp, minus_span_spanset, minus_span_span - """ - return self.minus(other) - - def union(self, other: Time) -> PeriodSet: - """ - Returns the temporal union of ``self`` and ``other``. - - Args: - other: temporal object to merge with - - Returns: - A :class:`PeriodSet` instance. - - MEOS Functions: - union_period_timestamp, union_spanset_span, union_span_span - """ - from .periodset import PeriodSet - from .timestampset import TimestampSet - if isinstance(other, datetime): - return PeriodSet(_inner=union_period_timestamp(self._inner, datetime_to_timestamptz(other))) - elif isinstance(other, TimestampSet): - return PeriodSet(_inner=union_spanset_span(set_to_spanset(other._inner), self._inner)) - if isinstance(other, Period): - return PeriodSet(_inner=union_span_span(self._inner, other._inner)) - elif isinstance(other, PeriodSet): - return PeriodSet(_inner=union_spanset_span(other._inner, self._inner)) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __add__(self, other): - """ - Returns the temporal union of ``self`` and ``other``. - - Args: - other: temporal object to merge with - - Returns: - A :class:`PeriodSet` instance. - - MEOS Functions: - union_period_timestamp, union_spanset_span, union_span_span - """ - return self.union(other) - - # ------------------------- Comparisons ----------------------------------- - def __eq__(self, other): - """ - Return whether ``self`` and ``other`` are equal. - - Args: - other: temporal object to compare with - - Returns: - True if equal, False otherwise - - MEOS Functions: - span_eq - """ - if isinstance(other, self.__class__): - return span_eq(self._inner, other._inner) - return False - - def __ne__(self, other): - """ - Return whether ``self`` and ``other`` are not equal. - - Args: - other: temporal object to compare with - - Returns: - True if not equal, False otherwise - - MEOS Functions: - span_neq - """ - if isinstance(other, self.__class__): - return span_ne(self._inner, other._inner) - return True - - def __lt__(self, other): - """ - Return whether ``self`` is less than ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if less than, False otherwise - - MEOS Functions: - span_lt - """ - if isinstance(other, self.__class__): - return span_lt(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __le__(self, other): - """ - Return whether ``self`` is less than or equal to ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if less than or equal, False otherwise - - MEOS Functions: - span_le - """ - if isinstance(other, self.__class__): - return span_le(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __gt__(self, other): - """ - Return whether ``self`` is greater than ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if greater than, False otherwise - - MEOS Functions: - span_gt - """ - if isinstance(other, self.__class__): - return span_gt(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __ge__(self, other): - """ - Return whether ``self`` is greater than or equal to ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if greater than or equal, False otherwise - - MEOS Functions: - span_ge - """ - if isinstance(other, self.__class__): - return span_ge(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - # ------------------------- Plot Operations ------------------------------- - def plot(self, *args, **kwargs): - from ..plotters import TimePlotter - return TimePlotter.plot_period(self, *args, **kwargs) - - # ------------------------- Database Operations --------------------------- - @staticmethod - def read_from_cursor(value, _=None): - """ - Reads a :class:`Period` from a database cursor. Used when automatically loading objects from the database. - Users should use the class constructor instead. - """ - if not value: - return None - return Period(string=value) - diff --git a/pymeos/pymeos/time/periodset.py b/pymeos/pymeos/time/periodset.py deleted file mode 100644 index 3101d6ff..00000000 --- a/pymeos/pymeos/time/periodset.py +++ /dev/null @@ -1,1039 +0,0 @@ -from __future__ import annotations - -from datetime import timedelta, datetime -from typing import Optional, Union, List, overload, get_args -from typing import TYPE_CHECKING - -from pymeos_cffi import * - -if TYPE_CHECKING: - from ..temporal import Temporal - from ..boxes import Box - from .period import Period - from .timestampset import TimestampSet - from .time import Time - - -class PeriodSet: - """ - Class for representing lists of disjoint periods. - - ``PeriodSet`` objects can be created with a single argument of type string - as in MobilityDB. - - >>> PeriodSet(string='{[2019-09-08 00:00:00+01, 2019-09-10 00:00:00+01], [2019-09-11 00:00:00+01, 2019-09-12 00:00:00+01]}') - - Another possibility is to give a list specifying the composing - periods, which can be instances of ``str`` or ``Period``. The composing - periods must be given in increasing order. - - >>> PeriodSet(period_list=['[2019-09-08 00:00:00+01, 2019-09-10 00:00:00+01]', '[2019-09-11 00:00:00+01, 2019-09-12 00:00:00+01]']) - >>> PeriodSet(period_list=[Period('[2019-09-08 00:00:00+01, 2019-09-10 00:00:00+01]'), Period('[2019-09-11 00:00:00+01, 2019-09-12 00:00:00+01]')]) - - """ - - __slots__ = ['_inner'] - - # ------------------------- Constructors ---------------------------------- - def __init__(self, string: Optional[str] = None, *, period_list: Optional[List[Union[str, Period]]] = None, - normalize: bool = True, _inner=None): - super().__init__() - assert (_inner is not None) or ((string is not None) != (period_list is not None)), \ - "Either string must be not None or period_list must be not" - if _inner is not None: - self._inner = _inner - elif string is not None: - self._inner = periodset_in(string) - else: - periods = [period_in(period)[0] if isinstance(period, str) else period._inner[0] for period in period_list] - self._inner = spanset_make(periods, normalize) - - def __copy__(self): - """ - Return a copy of ``self``. - - Returns: - A new :class:`Period` instance - - MEOS Functions: - spanset_copy - """ - inner_copy = spanset_copy(self._inner) - return PeriodSet(_inner=inner_copy) - - @staticmethod - def from_wkb(wkb: bytes) -> Period: - """ - Returns a `PeriodSet` from its WKB representation. - - Args: - wkb: The WKB string. - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - spanset_from_wkb - """ - result = spanset_from_wkb(wkb) - return PeriodSet(_inner=result) - - @staticmethod - def from_hexwkb(hexwkb: str) -> PeriodSet: - """ - Returns a `PeriodSet` from its WKB representation in hex-encoded ASCII. - Args: - hexwkb: WKB representation in hex-encoded ASCII - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - spanset_from_hexwkb - """ - result = spanset_from_hexwkb(hexwkb) - return PeriodSet(_inner=result) - - # ------------------------- Output ---------------------------------------- - def __str__(self): - """ - Return the string representation of the content of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - periodset_out - """ - return periodset_out(self._inner) - - def __repr__(self): - """ - Return the string representation of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - periodset_out - """ - return (f'{self.__class__.__name__}' - f'({self})') - - def as_wkb(self) -> bytes: - """ - Returns the WKB representation of ``self``. - - Returns: - A :class:`str` object with the WKB representation of ``self``. - - MEOS Functions: - spanset_as_wkb - """ - return spanset_as_wkb(self._inner, 4) - - def as_hexwkb(self) -> str: - """ - Returns the WKB representation of ``self`` in hex-encoded ASCII. - Returns: - A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. - - MEOS Functions: - spanset_as_hexwkb - """ - return spanset_as_hexwkb(self._inner, -1)[0] - - # ------------------------- Conversions ----------------------------------- - def to_period(self) -> Period: - """ - Returns a period that encompasses ``self``. - - Returns: - A new :class:`Period` instance - - MEOS Functions: - spanset_span - """ - from .period import Period - return Period(_inner=spanset_span(self._inner)) - - # ------------------------- Accessors ------------------------------------- - def duration(self, ignore_gaps: Optional[bool] = False) -> timedelta: - """ - Returns the duration of the periodset. By default, i.e., when the - second argument is False, the function takes into account the gaps within, - i.e., returns the sum of the durations of the periods within. - Otherwise, the function returns the duration of the periodset ignoring - any gap, i.e., the duration from the lower bound of the first period to - the upper bound of the last period. - - Parameters: - ignore_gaps: Whether to take into account potential time gaps in the periodset. - - Returns: - A :class:`datetime.timedelta` instance representing the duration of the periodset - - MEOS Functions: - periodset_duration - """ - return interval_to_timedelta(periodset_duration(self._inner, ignore_gaps)) - - def num_timestamps(self) -> int: - """ - Returns the number of timestamps in ``self``. - Returns: - An :class:`int` - - MEOS Functions: - periodset_num_timestamps - """ - return periodset_num_timestamps(self._inner) - - def start_timestamp(self) -> datetime: - """ - Returns the first timestamp in ``self``. - Returns: - A :class:`datetime` instance - - MEOS Functions: - periodset_start_timestamp - """ - return timestamptz_to_datetime(periodset_start_timestamp(self._inner)) - - def end_timestamp(self) -> datetime: - """ - Returns the last timestamp in ``self``. - Returns: - A :class:`datetime` instance - - MEOS Functions: - periodset_end_timestamp - """ - return timestamptz_to_datetime(periodset_end_timestamp(self._inner)) - - def timestamp_n(self, n: int) -> datetime: - """ - Returns the n-th timestamp in ``self``. - Returns: - A :class:`datetime` instance - - MEOS Functions: - periodset_timestamp_n - """ - result = periodset_timestamp_n(self._inner, n + 1) - if result is None: - raise IndexError(f"Index {n} out of range 0 - {self.num_timestamps() - 1}") - return timestamptz_to_datetime(result) - - def timestamps(self) -> List[datetime]: - """ - Returns the list of distinc timestamps in ``self``. - Returns: - A :class:`list[datetime]` instance - - MEOS Functions: - periodset_timestamps - """ - ts, count = periodset_timestamps(self._inner) - return [timestamptz_to_datetime(ts[i]) for i in range(count)] - - def num_periods(self) -> int: - """ - Returns the number of periods in ``self``. - Returns: - An :class:`int` - - MEOS Functions: - spanset_num_spans - """ - return spanset_num_spans(self._inner) - - def start_period(self) -> Period: - """ - Returns the first period in ``self``. - Returns: - A :class:`Period` instance - - MEOS Functions: - periodset_lower - """ - from .period import Period - return Period(_inner=spanset_start_span(self._inner)) - - def end_period(self) -> Period: - """ - Returns the last period in ``self``. - Returns: - A :class:`Period` instance - - MEOS Functions: - periodset_upper - """ - from .period import Period - return Period(_inner=spanset_end_span(self._inner)) - - def period_n(self, n: int) -> Period: - """ - Returns the n-th period in ``self``. - Returns: - A :class:`Period` instance - - MEOS Functions: - spanset_span_n - """ - from .period import Period - - result = spanset_span_n(self._inner, n + 1) - if result is None: - raise IndexError(f"Index {n} out of range 0 - {self.num_periods() - 1}") - return Period(_inner=result) - - def periods(self) -> List[Period]: - """ - Returns the list of periods in ``self``. - Returns: - A :class:`list[Period]` instance - - MEOS Functions: - spanset_spans - """ - from .period import Period - ps = spanset_spans(self._inner) - return [Period(_inner=ps[i]) for i in range(self.num_periods())] - - def __hash__(self) -> int: - """ - Return the hash representation of ``self``. - - Returns: - A new :class:`int` instance - - MEOS Functions: - spanset_hash - """ - return spanset_hash(self._inner) - - # ------------------------- Transformations ------------------------------- - def shift(self, delta: timedelta) -> PeriodSet: - """ - Returns a new periodset that is the result of shifting ``self`` by ``delta`` - - Examples: - >>> Period('[2000-01-01, 2000-01-10]').shift(timedelta(days=2)) - >>> 'Period([2000-01-03 00:00:00+01, 2000-01-12 00:00:00+01])' - - Args: - delta: :class:`datetime.timedelta` instance to shift - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - periodset_shift_tscale - """ - return self.shift_tscale(shift=delta) - - def tscale(self, duration: timedelta) -> PeriodSet: - """ - Returns a new periodset that starts as ``self`` but has duration ``duration`` - - Examples: - >>> Period('[2000-01-01, 2000-01-10]').tscale(timedelta(days=2)) - >>> 'Period([2000-01-01 00:00:00+01, 2000-01-03 00:00:00+01])' - - Args: - duration: :class:`datetime.timedelta` instance representing the duration of the new period - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - periodset_shift_tscale - """ - return self.shift_tscale(duration=duration) - - def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[timedelta] = None) -> PeriodSet: - """ - Returns a new periodset that starts at ``self`` shifted by ``shift`` and has duration ``duration`` - - Examples: - >>> Period('[2000-01-01, 2000-01-10]').shift_tscale(shift=timedelta(days=2), duration=timedelta(days=4)) - >>> 'Period([2000-01-03 00:00:00+01, 2000-01-07 00:00:00+01])' - - Args: - shift: :class:`datetime.timedelta` instance to shift - duration: :class:`datetime.timedelta` instance representing the duration of the new period - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - periodset_shift_tscale - """ - assert shift is not None or duration is not None, 'shift and scale deltas must not be both None' - ps = periodset_shift_tscale( - self._inner, - timedelta_to_interval(shift) if shift else None, - timedelta_to_interval(duration) if duration else None - ) - return PeriodSet(_inner=ps) - - # ------------------------- Topological Operations ------------------------ - def is_adjacent(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is temporally adjacent to ``other``. That is, they share a bound but only one of them - contains it. - - Examples: - >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_adjacent(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> True - >>> PeriodSet('{[2012-01-01, 2012-01-02]}').is_adjacent(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> False # Both contain bound - >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_adjacent(PeriodSet('{[(2012-01-02, 2012-01-03]]}')) - >>> False # Neither contain bound - - Args: - other: temporal object to compare with - - Returns: - True if adjacent, False otherwise - - MEOS Functions: - adjacent_spanset_span, adjacent_spanset_spanset, adjacent_periodset_timestamp, - adjacent_periodset_timestampset - """ - from .period import Period - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Period): - return adjacent_spanset_span(self._inner, other._inner) - if isinstance(other, PeriodSet): - return adjacent_spanset_spanset(self._inner, other._inner) - elif isinstance(other, datetime): - return adjacent_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) - elif isinstance(other, TimestampSet): - return adjacent_spanset_spanset(self._inner, set_to_spanset(other._inner)) - elif isinstance(other, Temporal): - return adjacent_spanset_spanset(self._inner, temporal_time(other._inner)) - elif isinstance(other, get_args(Box)): - return adjacent_spanset_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_contained_in(self, container: Union[Period, PeriodSet, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is temporally contained in ``container``. - - Examples: - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_contained_in(Period('{[2012-01-01, 2012-01-04]}')) - >>> True - >>> PeriodSet('{(2012-01-01, 2012-01-02)}').is_contained_in(Period('{[2012-01-01, 2012-01-02]}')) - >>> True - >>> PeriodSet('{[2012-01-01, 2012-01-02]}').is_contained_in(Period('{(2012-01-01, 2012-01-02)}')) - >>> False - - Args: - container: temporal object to compare with - - Returns: - True if contained, False otherwise - - MEOS Functions: - contained_spanset_span, contained_spanset_spanset, contained_periodset_temporal - """ - from .period import Period - from ..temporal import Temporal - from ..boxes import Box - if isinstance(container, Period): - return contained_spanset_span(self._inner, container._inner) - elif isinstance(container, PeriodSet): - return contained_spanset_spanset(self._inner, container._inner) - elif isinstance(container, Temporal): - return contained_spanset_spanset(self._inner, temporal_time(container._inner)) - elif isinstance(container, get_args(Box)): - return contained_spanset_span(self._inner, container.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {container.__class__}') - - def contains(self, content: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` temporally contains ``content``. - - Examples: - >>> PeriodSet('{[2012-01-01, 2012-01-04]}').contains(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> True - >>> PeriodSet('{[2012-01-01, 2012-01-02]}').contains(PeriodSet('{(2012-01-01, 2012-01-02)}')) - >>> True - >>> PeriodSet('{(2012-01-01, 2012-01-02)}').contains(PeriodSet('{[2012-01-01, 2012-01-02]}')) - >>> False - - Args: - content: temporal object to compare with - - Returns: - True if contains, False otherwise - - MEOS Functions: - contains_spanset_span, contains_spanset_spanset, contains_periodset_timestamp - """ - from .period import Period - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(content, Period): - return contains_spanset_span(self._inner, content._inner) - if isinstance(content, PeriodSet): - return contains_spanset_spanset(self._inner, content._inner) - elif isinstance(content, datetime): - return contains_periodset_timestamp(self._inner, datetime_to_timestamptz(content)) - elif isinstance(content, TimestampSet): - return contains_spanset_spanset(self._inner, set_to_spanset(content._inner)) - elif isinstance(content, Temporal): - return contains_spanset_spanset(self._inner, temporal_time(content._inner)) - elif isinstance(content, get_args(Box)): - return contains_spanset_span(self._inner, content.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {content.__class__}') - - def __contains__(self, item): - """ - Returns whether ``self`` temporally contains ``content``. - - Examples: - >>> PeriodSet('{[2012-01-01, 2012-01-04]}').contains(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> True - >>> PeriodSet('{[2012-01-01, 2012-01-02]}').contains(PeriodSet('{(2012-01-01, 2012-01-02)}')) - >>> True - >>> PeriodSet('{(2012-01-01, 2012-01-02)}').contains(PeriodSet('{[2012-01-01, 2012-01-02]}')) - >>> False - - Args: - item: temporal object to compare with - - Returns: - True if contains, False otherwise - - MEOS Functions: - contains_spanset_span, contains_spanset_spanset, contains_periodset_timestamp, - contains_periodset_timestampset, contains_periodset_temporal - """ - return self.contains(item) - - def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Box, Temporal]) -> bool: - """ - Returns whether ``self`` temporally overlaps ``other``. That is, both share at least an instant - - Examples: - >>> PeriodSet('{[2012-01-01, 2012-01-02]}').overlaps(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> True - >>> PeriodSet('{[2012-01-01, 2012-01-02)}').overlaps(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> False - >>> PeriodSet('{[2012-01-01, 2012-01-02)}').overlaps(PeriodSet('{(2012-01-02, 2012-01-03]}')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if overlaps, False otherwise - - MEOS Functions: - overlaps_spanset_span, overlaps_spanset_spanset - """ - from .period import Period - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Period): - return overlaps_spanset_span(self._inner, other._inner) - if isinstance(other, PeriodSet): - return overlaps_spanset_spanset(self._inner, other._inner) - elif isinstance(other, TimestampSet): - return overlaps_spanset_span(self._inner, set_span(other._inner)) - elif isinstance(other, Temporal): - return overlaps_spanset_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return overlaps_spanset_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_same(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether the bounding period of `self` is the same as the bounding period of `other`. - - Args: - other: A time or temporal object to compare to `self`. - - Returns: - True if same, False otherwise. - - See Also: - :meth:`Period.is_same` - """ - return self.to_period().is_same(other) - - # ------------------------- Position Operations --------------------------- - def is_before(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is strictly before ``other``. That is, ``self`` ends before ``other`` starts. - - Examples: - >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_before(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> True - >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_before(PeriodSet('{(2012-01-02, 2012-01-03]}')) - >>> True - >>> PeriodSet('{[2012-01-01, 2012-01-02]}').is_before(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if before, False otherwise - - MEOS Functions: - before_periodset_timestamp, left_spanset_span, left_spanset_spanset - """ - from .period import Period - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, datetime): - return before_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) - elif isinstance(other, TimestampSet): - return left_spanset_spanset(self._inner, set_to_spanset(other._inner)) - elif isinstance(other, Period): - return left_spanset_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return left_spanset_spanset(self._inner, other._inner) - elif isinstance(other, Temporal): - return left_spanset_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return left_spanset_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_over_or_before(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is before ``other`` allowing overlap. That is, ``self`` ends before ``other`` ends (or - at the same time). - - Examples: - >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_over_or_before(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> True - >>> PeriodSet('{[2012-01-01, 2012-01-02]}').is_over_or_before(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> True - >>> PeriodSet('{[2012-01-03, 2012-01-05]}').is_over_or_before(PeriodSet('{[2012-01-01, 2012-01-04]}')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if before, False otherwise - - MEOS Functions: - overleft_spanset_span, overleft_spanset_spanset, overbefore_periodset_timestamp, - overbefore_periodset_timestampset, overbefore_periodset_temporal - """ - from .period import Period - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Period): - return overleft_spanset_span(self._inner, other._inner) - if isinstance(other, PeriodSet): - return overleft_spanset_spanset(self._inner, other._inner) - elif isinstance(other, datetime): - return overbefore_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) - if isinstance(other, TimestampSet): - return overleft_spanset_span(self._inner, set_span(other._inner)) - elif isinstance(other, Temporal): - return overleft_spanset_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return overleft_spanset_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_after(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is strictly after ``other``.That is, ``self`` starts after ``other`` ends. - - Examples: - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_after(PeriodSet('{[2012-01-01, 2012-01-02)}')) - >>> True - >>> PeriodSet('{(2012-01-02, 2012-01-03]}').is_after(PeriodSet('{[2012-01-01, 2012-01-02)}')) - >>> True - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_after(PeriodSet('{[2012-01-01, 2012-01-02]}')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if after, False otherwise - - MEOS Functions: - right_spanset_span, right_spanset_spanset, overbefore_timestamp_periodset - """ - from .period import Period - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, datetime): - return overbefore_timestamp_periodset(datetime_to_timestamptz(other), self._inner) - elif isinstance(other, TimestampSet): - return right_spanset_span(self._inner, set_span(other._inner)) - elif isinstance(other, Period): - return right_spanset_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return right_spanset_spanset(self._inner, other._inner) - elif isinstance(other, Temporal): - return right_spanset_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return right_spanset_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_over_or_after(self, other: Union[Time, Box, Temporal]) -> bool: - """ - Returns whether ``self`` is after ``other`` allowing overlap. That is, ``self`` starts after ``other`` starts - (or at the same time). - - Examples: - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_after(PeriodSet('{[2012-01-01, 2012-01-02)}')) - >>> True - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_after(PeriodSet('{[2012-01-01, 2012-01-02]}')) - >>> True - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_after(PeriodSet('{[2012-01-01, 2012-01-03]}')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if overlapping or after, False otherwise - - MEOS Functions: - overright_spanset_span, overright_spanset_spanset, overafter_periodset_timestamp, - overafter_periodset_timestampset, - """ - from .period import Period - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, datetime): - return overafter_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) - elif isinstance(other, TimestampSet): - return overright_spanset_span(self._inner, set_span(other._inner)) - elif isinstance(other, Period): - return overright_spanset_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return overright_spanset_spanset(self._inner, other._inner) - elif isinstance(other, Temporal): - return overright_spanset_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return overright_spanset_span(self._inner, other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - # ------------------------- Distance Operations --------------------------- - def distance(self, other: Union[Time, Box, Temporal]) -> timedelta: - """ - Returns the temporal distance between ``self`` and ``other``. - - Args: - other: temporal object to compare with - - Returns: - A :class:`datetime.timedelta` instance - - MEOS Functions: - distance_periodset_period, distance_periodset_periodset, distance_periodset_timestamp, - distance_periodset_timestampset - """ - from .period import Period - from .timestampset import TimestampSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Temporal): - return timedelta(seconds=distance_spanset_span(self._inner, temporal_to_period(other._inner))) - elif isinstance(other, Period): - return timedelta(seconds=distance_spanset_span(self._inner, other._inner)) - elif isinstance(other, PeriodSet): - return timedelta(seconds=distance_spanset_spanset(self._inner, other._inner)) - elif isinstance(other, datetime): - return timedelta(seconds=distance_periodset_timestamp(self._inner, datetime_to_timestamptz(other))) - elif isinstance(other, TimestampSet): - return timedelta(seconds=distance_spanset_span(self._inner, set_span(other._inner))) - elif isinstance(other, get_args(Box)): - return timedelta(seconds=distance_spanset_span(self._inner, other.to_period()._inner)) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - # ------------------------- Set Operations -------------------------------- - @overload - def intersection(self, other: Period) -> PeriodSet: - ... - - @overload - def intersection(self, other: PeriodSet) -> PeriodSet: - ... - - @overload - def intersection(self, other: datetime) -> datetime: - ... - - @overload - def intersection(self, other: TimestampSet) -> TimestampSet: - ... - - def intersection(self, other: Time) -> Union[PeriodSet, datetime, TimestampSet]: - """ - Returns the temporal intersection of ``self`` and ``other``. - - Args: - other: temporal object to intersect with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - intersection_periodset_timestamp, intersection_spanset_spanset, intersection_spanset_span - """ - from .period import Period - from .timestampset import TimestampSet - if isinstance(other, datetime): - result = intersection_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) - return timestamptz_to_datetime(result) if result is not None else None - elif isinstance(other, TimestampSet): - result = intersection_spanset_spanset(self._inner, set_to_spanset(other._inner)) - return TimestampSet(_inner=result) if result is not None else None - elif isinstance(other, Period): - result = intersection_spanset_span(self._inner, other._inner) - return PeriodSet(_inner=result) if result is not None else None - elif isinstance(other, PeriodSet): - result = intersection_spanset_spanset(self._inner, other._inner) - return PeriodSet(_inner=result) if result is not None else None - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __mul__(self, other): - """ - Returns the temporal intersection of ``self`` and ``other``. - - Args: - other: temporal object to intersect with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - intersection_periodset_timestamp, intersection_spanset_spanset, intersection_spanset_span - """ - return self.intersection(other) - - def minus(self, other: Time) -> PeriodSet: - """ - Returns the temporal difference of ``self`` and ``other``. - - Args: - other: temporal object to diff with - - Returns: - A :class:`PeriodSet` instance. - - MEOS Functions: - minus_spanset_span, minus_spanset_spanset, minus_periodset_timestamp - """ - from .period import Period - from .timestampset import TimestampSet - if isinstance(other, datetime): - result = minus_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) - elif isinstance(other, TimestampSet): - result = minus_spanset_spanset(self._inner, set_to_spanset(other._inner)) - elif isinstance(other, Period): - result = minus_spanset_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - result = minus_spanset_spanset(self._inner, other._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - return PeriodSet(_inner=result) if result is not None else None - - def __sub__(self, other): - """ - Returns the temporal difference of ``self`` and ``other``. - - Args: - other: temporal object to diff with - - Returns: - A :class:`PeriodSet` instance. - - MEOS Functions: - minus_spanset_span, minus_spanset_spanset, minus_periodset_timestamp - """ - return self.minus(other) - - def union(self, other: Time) -> PeriodSet: - """ - Returns the temporal union of ``self`` and ``other``. - - Args: - other: temporal object to merge with - - Returns: - A :class:`PeriodSet` instance. - - MEOS Functions: - union_periodset_timestamp, union_spanset_spanset, union_spanset_span - """ - from .period import Period - from .timestampset import TimestampSet - if isinstance(other, datetime): - return PeriodSet(_inner=union_periodset_timestamp(self._inner, datetime_to_timestamptz(other))) - elif isinstance(other, TimestampSet): - return PeriodSet(_inner=union_spanset_spanset(self._inner, set_to_spanset(other._inner))) - elif isinstance(other, Period): - return PeriodSet(_inner=union_spanset_span(self._inner, other._inner)) - elif isinstance(other, PeriodSet): - return PeriodSet(_inner=union_spanset_spanset(self._inner, other._inner)) - - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __add__(self, other): - """ - Returns the temporal union of ``self`` and ``other``. - - Args: - other: temporal object to merge with - - Returns: - A :class:`PeriodSet` instance. - - MEOS Functions: - union_periodset_timestamp, union_spanset_spanset, union_spanset_span - """ - return self.union(other) - - # ------------------------- Comparisons ----------------------------------- - def __eq__(self, other): - """ - Return whether ``self`` and ``other`` are equal. - - Args: - other: temporal object to compare with - - Returns: - True if equal, False otherwise - - MEOS Functions: - spanset_eq - """ - if isinstance(other, self.__class__): - return spanset_eq(self._inner, other._inner) - return False - - def __ne__(self, other): - """ - Return whether ``self`` and ``other`` are not equal. - - Args: - other: temporal object to compare with - - Returns: - True if not equal, False otherwise - - MEOS Functions: - spanset_ne - """ - if isinstance(other, self.__class__): - return spanset_ne(self._inner, other._inner) - return True - - def __lt__(self, other): - """ - Return whether ``self`` is less than ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if less than, False otherwise - - MEOS Functions: - spanset_lt - """ - if isinstance(other, self.__class__): - return spanset_lt(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __le__(self, other): - """ - Return whether ``self`` is less than or equal to ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if less than or equal, False otherwise - - MEOS Functions: - spanset_le - """ - if isinstance(other, self.__class__): - return spanset_le(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __gt__(self, other): - """ - Return whether ``self`` is greater than ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if greater than, False otherwise - - MEOS Functions: - spanset_gt - """ - if isinstance(other, self.__class__): - return spanset_gt(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __ge__(self, other): - """ - Return whether ``self`` is greater than or equal to ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if greater than or equal, False otherwise - - MEOS Functions: - spanset_ge - """ - if isinstance(other, self.__class__): - return spanset_ge(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - # ------------------------- Plot Operations ------------------------------- - def plot(self, *args, **kwargs): - from ..plotters import TimePlotter - return TimePlotter.plot_periodset(self, *args, **kwargs) - - # ------------------------- Database Operations --------------------------- - @staticmethod - def read_from_cursor(value, _=None): - """ - Reads a :class:`PeriodSet` from a database cursor. Used when automatically loading objects from the database. - Users should use the class constructor instead. - """ - if not value: - return None - return PeriodSet(string=value) - diff --git a/pymeos/pymeos/time/timestampset.py b/pymeos/pymeos/time/timestampset.py deleted file mode 100644 index 3997035c..00000000 --- a/pymeos/pymeos/time/timestampset.py +++ /dev/null @@ -1,980 +0,0 @@ -from __future__ import annotations - -from datetime import datetime, timedelta -from typing import Optional, List, Union, TYPE_CHECKING, overload, get_args - -from dateutil.parser import parse -from pymeos_cffi import * - -if TYPE_CHECKING: - from ..temporal import Temporal - from .period import Period - from .periodset import PeriodSet - from .time import Time - from ..boxes import Box - - -class TimestampSet: - """ - Class for representing lists of distinct timestamp values. - - ``TimestampSet`` objects can be created with a single argument of type string - as in MobilityDB. - - >>> TimestampSet(string='{2019-09-08 00:00:00+01, 2019-09-10 00:00:00+01, 2019-09-11 00:00:00+01}') - - Another possibility is to give a tuple or list of composing timestamps, - which can be instances of ``str`` or ``datetime``. The composing timestamps - must be given in increasing order. - - >>> TimestampSet(timestamp_list=['2019-09-08 00:00:00+01', '2019-09-10 00:00:00+01', '2019-09-11 00:00:00+01']) - >>> TimestampSet(timestamp_list=[parse('2019-09-08 00:00:00+01'), parse('2019-09-10 00:00:00+01'), parse('2019-09-11 00:00:00+01')]) - - """ - - __slots__ = ['_inner'] - - # ------------------------- Constructors ---------------------------------- - def __init__(self, string: Optional[str] = None, *, timestamp_list: Optional[List[Union[str, datetime]]] = None, - _inner=None): - super().__init__() - assert (_inner is not None) or ((string is not None) != (timestamp_list is not None)), \ - "Either string must be not None or timestamp_list must be not" - if _inner is not None: - self._inner = _inner - elif string is not None: - self._inner = timestampset_in(string) - else: - times = [pg_timestamp_in(ts, -1) if isinstance(ts, str) else datetime_to_timestamptz(ts) - for ts in timestamp_list] - self._inner = timestampset_make(times, len(times)) - - def __copy__(self): - """ - Return a copy of ``self``. - - Returns: - A new :class:`Period` instance - - MEOS Functions: - set_copy - """ - inner_copy = set_copy(self._inner) - return TimestampSet(_inner=inner_copy) - - @staticmethod - def from_wkb(wkb: bytes) -> TimestampSet: - """ - Returns a `TimestampSet` from its WKB representation. - Args: - wkb: WKB representation - - Returns: - A new :class:`TimestampSet` instance - - MEOS Functions: - set_from_wkb - """ - return TimestampSet(_inner=(set_from_wkb(wkb))) - - @staticmethod - def from_hexwkb(hexwkb: str) -> TimestampSet: - """ - Returns a `TimestampSet` from its WKB representation in hex-encoded ASCII. - Args: - hexwkb: WKB representation in hex-encoded ASCII - - Returns: - A new :class:`TimestampSet` instance - - MEOS Functions: - set_from_hexwkb - """ - return TimestampSet(_inner=(set_from_hexwkb(hexwkb))) - - # ------------------------- Output ---------------------------------------- - def __str__(self): - """ - Return the string representation of the content of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - set_out - """ - return set_out(self._inner, 15) - - def __repr__(self): - """ - Return the string representation of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - set_out - """ - return (f'{self.__class__.__name__}' - f'({self})') - - def as_wkb(self) -> bytes: - """ - Returns the WKB representation of ``self``. - Returns: - A :class:`str` object with the WKB representation of ``self``. - - MEOS Functions: - set_as_wkb - """ - return set_as_wkb(self._inner, 4) - - def as_hexwkb(self) -> str: - """ - Returns the WKB representation of ``self`` in hex-encoded ASCII. - Returns: - A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. - - MEOS Functions: - set_as_hexwkb - """ - return set_as_hexwkb(self._inner, -1)[0] - - # ------------------------- Conversions ----------------------------------- - def to_periodset(self) -> PeriodSet: - """ - Returns a PeriodSet that contains a Period for each Timestamp in ``self``. - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - set_to_spanset - """ - from .periodset import PeriodSet - return PeriodSet(_inner=set_to_spanset(self._inner)) - - # ------------------------- Accessors ------------------------------------- - def duration(self) -> timedelta: - """ - Returns the duration of the time ignoring gaps, i.e. the duration from the - first timestamp to the last one. - - Returns: - A :class:`datetime.timedelta` instance representing the duration of the period - - MEOS Functions: - period_duration - """ - return interval_to_timedelta(period_duration(set_span(self._inner))) - - def period(self) -> Period: - """ - Returns a period that encompasses ``self``. - - Returns: - A new :class:`Period` instance - - MEOS Functions: - set_span - """ - from .period import Period - return Period(_inner=set_span(self._inner)) - - def num_timestamps(self) -> int: - """ - Returns the number of timestamps in ``self``. - Returns: - An :class:`int` - - MEOS Functions: - set_num_values - """ - return set_num_values(self._inner) - - def start_timestamp(self) -> datetime: - """ - Returns the first timestamp in ``self``. - Returns: - A :class:`datetime` instance - - MEOS Functions: - timestampset_start_timestamp - """ - return timestamptz_to_datetime(timestampset_start_timestamp(self._inner)) - - def end_timestamp(self) -> datetime: - """ - Returns the last timestamp in ``self``. - Returns: - A :class:`datetime` instance - - MEOS Functions: - timestampset_end_timestamp - """ - return timestamptz_to_datetime(timestampset_end_timestamp(self._inner)) - - def timestamp_n(self, n: int) -> datetime: - """ - Returns the n-th timestamp in ``self``. - Returns: - A :class:`datetime` instance - - MEOS Functions: - timestampset_timestamp_n - """ - result = timestampset_timestamp_n(self._inner, n + 1) - if result is None: - raise IndexError(f"Index {n} out of range 0 - {self.num_timestamps() - 1}") - return timestamptz_to_datetime(result) - - def timestamps(self) -> List[datetime]: - """ - Returns the list of distinct timestamps in ``self``. - Returns: - A :class:`list[datetime]` instance - - MEOS Functions: - timestampset_timestamps - """ - tss = timestampset_values(self._inner) - return [timestamptz_to_datetime(tss[i]) for i in range(self.num_timestamps())] - - def __hash__(self) -> int: - """ - Return the hash representation of ``self``. - - Returns: - A new :class:`int` instance - - MEOS Functions: - set_hash - """ - return set_hash(self._inner) - - # ------------------------- Transformations ------------------------------- - def shift(self, delta: timedelta) -> TimestampSet: - """ - Returns a new TimestampSet that is the result of shifting ``self`` by ``delta`` - - Examples: - >>> TimestampSet('{2000-01-01, 2000-01-10}').shift(timedelta(days=2)) - >>> 'TimestampSet({2000-01-03 00:00:00+01, 2000-01-12 00:00:00+01})' - - Args: - delta: :class:`datetime.timedelta` instance to shift - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - timestampset_shift_tscale - """ - return self.shift_tscale(shift=delta) - - def tscale(self, duration: timedelta) -> TimestampSet: - """ - Returns a new TimestampSet that with the scaled so that the span of ``self`` is ``duration``. - - Examples: - >>> TimestampSet('{2000-01-01, 2000-01-10}').tscale(timedelta(days=2)) - >>> 'TimestampSet({2000-01-01 00:00:00+01, 2000-01-03 00:00:00+01})' - - Args: - duration: :class:`datetime.timedelta` instance representing the span of the new set - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - timestampset_shift_tscale - """ - return self.shift_tscale(duration=duration) - - def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[timedelta] = None) -> TimestampSet: - """ - Returns a new TimestampSet that is the result of shifting and scaling ``self``. - - Examples: - >>> TimestampSet('{2000-01-01, 2000-01-10}').shift_tscale(shift=timedelta(days=2), duration=timedelta(days=4)) - >>> 'TimestampSet({2000-01-03 00:00:00+01, 2000-01-07 00:00:00+01})' - - Args: - shift: :class:`datetime.timedelta` instance to shift - duration: :class:`datetime.timedelta` instance representing the span of the new set - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - timestampset_shift_tscale - """ - assert shift is not None or duration is not None, 'shift and scale deltas must not be both None' - tss = timestampset_shift_tscale( - self._inner, - timedelta_to_interval(shift) if shift else None, - timedelta_to_interval(duration) if duration else None - ) - return TimestampSet(_inner=tss) - - # ------------------------- Topological Operations ------------------------ - def is_adjacent(self, other: Union[Period, PeriodSet, Temporal, Box]) -> bool: - """ - Returns whether ``self`` is temporally adjacent to ``other``. That is, they share a bound but only one of them - contains it. - - Examples: - >>> TimestampSet('{2012-01-01, 2012-01-02}').is_adjacent(Period('[2012-01-02, 2012-01-03]')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').is_adjacent(Period('[2012-01-02, 2012-01-03]')) - >>> False # Both contain bound - >>> TimestampSet('{2012-01-01, 2012-01-02}').is_adjacent(Period('(2012-01-02, 2012-01-03]')) - >>> False # Neither contain bound - - Args: - other: temporal object to compare with - - Returns: - True if adjacent, False otherwise - - MEOS Functions: - adjacent_span_span, adjacent_spanset_span - """ - from .period import Period - from .periodset import PeriodSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, Period): - return adjacent_span_span(set_span(self._inner), other._inner) - elif isinstance(other, PeriodSet): - return adjacent_spanset_spanset(other._inner, set_to_spanset(self._inner)) - elif isinstance(other, Temporal): - return adjacent_span_span(set_span(self._inner), temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return adjacent_span_span(set_span(self._inner), other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_contained_in(self, container: Union[Period, PeriodSet, TimestampSet, Temporal, Box]) -> bool: - """ - Returns whether ``self`` is temporally contained in ``container``. - - Examples: - >>> TimestampSet('{2012-01-02, 2012-01-03}').is_contained_in(Period('[2012-01-01, 2012-01-04]')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').is_contained_in(Period('[2012-01-01, 2012-01-02]')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').is_contained_in(Period('(2012-01-01, 2012-01-02)')) - >>> False - - Args: - container: temporal object to compare with - - Returns: - True if contained, False otherwise - - MEOS Functions: - contained_span_span, contained_span_spanset, contained_set_set, contained_spanset_spanset - """ - from .period import Period - from .periodset import PeriodSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(container, Period): - return contained_span_span(set_span(self._inner), container._inner) - elif isinstance(container, PeriodSet): - return contained_span_spanset(set_span(self._inner), container._inner) - elif isinstance(container, TimestampSet): - return contained_set_set(self._inner, container._inner) - elif isinstance(container, Temporal): - return contained_spanset_spanset(set_to_spanset(self._inner), temporal_time(container._inner)) - elif isinstance(container, Box): - return contained_span_span(set_span(self._inner), container.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {container.__class__}') - - def contains(self, content: Union[datetime, TimestampSet, Temporal]) -> bool: - """ - Returns whether ``self`` temporally contains ``content``. - - Examples: - >>> TimestampSet('{2012-01-01, 2012-01-04}').contains(parse('2012-01-01]')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01}')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01, 2012-01-03}')) - >>> False - - Args: - content: temporal object to compare with - - Returns: - True if contains, False otherwise - - MEOS Functions: - contains_timestampset_timestamp, contains_set_set, contains_spanset_spanset - """ - from ..temporal import Temporal - if isinstance(content, datetime): - return contains_timestampset_timestamp(self._inner, datetime_to_timestamptz(content)) - elif isinstance(content, TimestampSet): - return contains_set_set(self._inner, content._inner) - elif isinstance(content, Temporal): - return contains_spanset_spanset(set_to_spanset(self._inner), temporal_time(content._inner)) - else: - raise TypeError(f'Operation not supported with type {content.__class__}') - - def __contains__(self, item): - """ - Returns whether ``self`` temporally contains ``content``. - - Examples: - >>> TimestampSet('{2012-01-01, 2012-01-04}').contains(parse('2012-01-01]')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01}')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01, 2012-01-03}')) - >>> False - - Args: - item: temporal object to compare with - - Returns: - True if contains, False otherwise - - MEOS Functions: - contains_timestampset_timestamp, contains_set_set, contains_spanset_spanset - """ - return self.contains(item) - - def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Temporal, Box]) -> bool: - """ - Returns whether ``self`` temporally overlaps ``other``. That is, both share at least an instant - - Examples: - >>> TimestampSet('{2012-01-01, 2012-01-02}').overlaps(TimestampSet('{2012-01-02, 2012-01-03}')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').overlaps(Period('[2012-01-02, 2012-01-03]')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').overlaps(Period('(2012-01-02, 2012-01-03]')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if overlaps, False otherwise - - MEOS Functions: - overlaps_set_set, overlaps_span_span, overlaps_spanset_spanset - """ - from .period import Period - from .periodset import PeriodSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, datetime): - return contains_timestampset_timestamp(self._inner, datetime_to_timestamptz(other)) - elif isinstance(other, TimestampSet): - return overlaps_set_set(self._inner, other._inner) - elif isinstance(other, Period): - return overlaps_span_span(set_span(self._inner), other._inner) - elif isinstance(other, PeriodSet): - return overlaps_spanset_spanset(set_to_spanset(self._inner), other._inner) - elif isinstance(other, Temporal): - return overlaps_spanset_spanset(set_to_spanset(self._inner), temporal_time(other._inner)) - elif isinstance(other, Box): - return overlaps_span_span(set_span(self._inner), other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_same(self, other: Union[Time, Temporal, Box]) -> bool: - """ - Returns whether the bounding period of `self` is the same as the bounding period of `other`. - - Args: - other: A time or temporal object to compare to `self`. - - Returns: - True if same, False otherwise. - - See Also: - :meth:`Period.is_same` - """ - return self.to_period().is_same(other) - - # ------------------------- Position Operations --------------------------- - def is_after(self, other: Union[Time, Temporal, Box]) -> bool: - """ - Returns whether ``self`` is strictly after ``other``. That is, the first timestamp in ``self`` - is after ``other``. - - Examples: - >>> TimestampSet('{2012-01-02, 2012-01-03}').is_after(Period('[2012-01-01, 2012-01-02)')) - >>> True - >>> TimestampSet('{2012-01-02, 2012-01-03}').is_after(TimestampSet('{2012-01-01}')) - >>> True - >>> TimestampSet('{2012-01-02, 2012-01-03}').is_after(Period('[2012-01-01, 2012-01-02]')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if after, False otherwise - - MEOS Functions: - overbefore_timestamp_timestampset, right_set_set, right_span_span, right_span_spanset - """ - from .period import Period - from .periodset import PeriodSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, datetime): - return overbefore_timestamp_timestampset(datetime_to_timestamptz(other), self._inner) - elif isinstance(other, TimestampSet): - return right_set_set(self._inner, other._inner) - elif isinstance(other, Period): - return right_span_span(set_span(self._inner), other._inner) - elif isinstance(other, PeriodSet): - return right_span_spanset(set_span(self._inner), other._inner) - elif isinstance(other, Temporal): - return right_span_span(set_span(self._inner), temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return right_span_span(set_span(self._inner), other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_before(self, other: Union[Time, Temporal, Box]) -> bool: - """ - Returns whether ``self`` is strictly before ``other``. That is, ``self`` ends before ``other`` starts. - - Examples: - >>> TimestampSet('{2012-01-01, 2012-01-02}').is_before(TimestampSet('{2012-01-03}')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').is_before(Period('(2012-01-02, 2012-01-03]')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').is_before(Period('[2012-01-02, 2012-01-03]')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if before, False otherwise - - MEOS Functions: - overafter_timestamp_period, left_span_span, left_span_spanset - """ - from .period import Period - from .periodset import PeriodSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, datetime): - return overafter_timestamp_period(datetime_to_timestamptz(other), set_span(self._inner)) - elif isinstance(other, TimestampSet): - return left_set_set(self._inner, other._inner) - elif isinstance(other, Period): - return left_span_span(set_span(self._inner), other._inner) - elif isinstance(other, PeriodSet): - return left_span_spanset(set_span(self._inner), other._inner) - elif isinstance(other, Temporal): - return left_span_span(set_span(self._inner), temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return left_span_span(set_span(self._inner), other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_over_or_after(self, other: Union[Time, Temporal, Box]) -> bool: - """ - Returns whether ``self`` is after ``other`` allowing overlap. That is, ``self`` starts after ``other`` starts - (or at the same time). - - Examples: - >>> TimestampSet('{2012-01-02, 2012-01-03}').is_over_or_after(Period('[2012-01-01, 2012-01-02)')) - >>> True - >>> TimestampSet('{2012-01-02, 2012-01-03}').is_over_or_after(Period('[2012-01-01, 2012-01-02]')) - >>> True - >>> TimestampSet('{2012-01-02, 2012-01-03}').is_over_or_after(Period('[2012-01-01, 2012-01-03]')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if overlapping or after, False otherwise - - MEOS Functions: - overafter_period_timestamp, overright_span_span, overright_span_spanset - """ - from .period import Period - from .periodset import PeriodSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, datetime): - return overafter_period_timestamp(set_span(self._inner), datetime_to_timestamptz(other)) - elif isinstance(other, TimestampSet): - return overright_set_set(self._inner, other._inner) - elif isinstance(other, Period): - return overright_span_span(set_span(self._inner), other._inner) - elif isinstance(other, PeriodSet): - return overright_span_spanset(set_span(self._inner), other._inner) - elif isinstance(other, Temporal): - return overright_span_span(set_span(self._inner), temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return overright_span_span(set_span(self._inner), other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def is_over_or_before(self, other: Union[Time, Temporal, Box]) -> bool: - """ - Returns whether ``self`` is before ``other`` allowing overlap. That is, ``self`` ends before ``other`` ends (or - at the same time). - - Examples: - >>> TimestampSet('{2012-01-01, 2012-01-02}').is_over_or_before(Period('[2012-01-02, 2012-01-03]')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').is_over_or_before(Period('[2012-01-02, 2012-01-03]')) - >>> True - >>> TimestampSet('{2012-01-03, 2012-01-05}').is_over_or_before(Period('[2012-01-01, 2012-01-04]')) - >>> False - - Args: - other: temporal object to compare with - - Returns: - True if before, False otherwise - - MEOS Functions: - overbefore_period_timestamp, overleft_span_span, overleft_span_spanset - """ - from .period import Period - from .periodset import PeriodSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, datetime): - return overbefore_period_timestamp(set_span(self._inner), datetime_to_timestamptz(other)) - if isinstance(other, TimestampSet): - return overleft_set_set(self._inner, other._inner) - if isinstance(other, Period): - return overleft_span_span(set_span(self._inner), other._inner) - if isinstance(other, PeriodSet): - return overleft_span_spanset(set_span(self._inner), other._inner) - elif isinstance(other, Temporal): - return overleft_span_span(set_span(self._inner), temporal_to_period(other._inner)) - elif isinstance(other, get_args(Box)): - return overleft_span_span(set_span(self._inner), other.to_period()._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - # ------------------------- Distance Operations --------------------------- - def distance(self, other: Union[Time, Temporal, Box]) -> timedelta: - """ - Returns the temporal distance between ``self`` and ``other``. - - Args: - other: temporal object to compare with - - Returns: - A :class:`datetime.timedelta` instance - - MEOS Functions: - distance_timestampset_timestamp, distance_set_set, distance_span_span, distance_spanset_span - """ - from .period import Period - from .periodset import PeriodSet - from ..temporal import Temporal - from ..boxes import Box - if isinstance(other, datetime): - return timedelta(seconds=distance_timestampset_timestamp(self._inner, datetime_to_timestamptz(other))) - elif isinstance(other, TimestampSet): - return timedelta(seconds=distance_set_set(self._inner, other._inner)) - elif isinstance(other, Period): - return timedelta(seconds=distance_span_span(set_span(self._inner), other._inner)) - elif isinstance(other, PeriodSet): - return timedelta(seconds=distance_spanset_span(other._inner, set_span(self._inner))) - elif isinstance(other, Temporal): - return timedelta(seconds=distance_span_span(set_span(self._inner), temporal_to_period(other._inner))) - elif isinstance(other, get_args(Box)): - return timedelta(seconds=distance_span_span(set_span(self._inner), other.to_period()._inner)) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - # ------------------------- Set Operations -------------------------------- - @overload - def intersection(self, other: datetime) -> Optional[datetime]: - ... - - @overload - def intersection(self, other: TimestampSet) -> Optional[TimestampSet]: - ... - - @overload - def intersection(self, other: Union[Period, PeriodSet, Temporal]) -> Optional[PeriodSet]: - ... - - def intersection(self, other: Union[Time, Temporal]) -> Optional[Time]: - """ - Returns the temporal intersection of ``self`` and ``other``. - - Args: - other: temporal object to intersect with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - intersection_set_set, intersection_spanset_span, intersection_spanset_spanset - """ - from .period import Period - from .periodset import PeriodSet - if isinstance(other, datetime): - result = intersection_set_set(self._inner, timestamp_to_tstzset(datetime_to_timestamptz(other))) - return timestamptz_to_datetime(result) if result is not None else None - elif isinstance(other, TimestampSet): - result = intersection_set_set(self._inner, other._inner) - return TimestampSet(_inner=result) if result is not None else None - elif isinstance(other, Period): - result = intersection_spanset_span(set_to_spanset(self._inner), other._inner) - return PeriodSet(_inner=result) if result is not None else None - elif isinstance(other, PeriodSet): - result = intersection_spanset_spanset(set_to_spanset(self._inner), other._inner) - return PeriodSet(_inner=result) if result is not None else None - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __mul__(self, other): - """ - Returns the temporal intersection of ``self`` and ``other``. - - Args: - other: temporal object to intersect with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - intersection_set_set, intersection_spanset_span, intersection_spanset_spanset - """ - return self.intersection(other) - - @overload - def minus(self, other: Union[datetime, TimestampSet]) -> Optional[TimestampSet]: - ... - - @overload - def minus(self, other: Union[Period, PeriodSet]) -> Optional[PeriodSet]: - ... - - def minus(self, other: Time) -> Optional[Time]: - """ - Returns the temporal difference of ``self`` and ``other``. - - Args: - other: temporal object to diff with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - minus_timestampset_timestamp, minus_set_set, minus_spanset_span, minus_spanset_spanset - """ - from .period import Period - from .periodset import PeriodSet - if isinstance(other, datetime): - result = minus_timestampset_timestamp(self._inner, datetime_to_timestamptz(other)) - return TimestampSet(_inner=result) if result is not None else None - elif isinstance(other, TimestampSet): - result = minus_set_set(self._inner, other._inner) - return TimestampSet(_inner=result) if result is not None else None - elif isinstance(other, Period): - result = minus_spanset_span(set_to_spanset(self._inner), other._inner) - return PeriodSet(_inner=result) if result is not None else None - elif isinstance(other, PeriodSet): - result = minus_spanset_spanset(set_to_spanset(self._inner), other._inner) - return PeriodSet(_inner=result) if result is not None else None - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __sub__(self, other): - """ - Returns the temporal difference of ``self`` and ``other``. - - Args: - other: temporal object to diff with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - minus_timestampset_timestamp, minus_set_set, minus_spanset_span, minus_spanset_spanset - """ - return self.minus(other) - - @overload - def union(self, other: Union[datetime, TimestampSet]) -> TimestampSet: - ... - - @overload - def union(self, other: Union[Period, PeriodSet]) -> PeriodSet: - ... - - def union(self, other: Time) -> Union[PeriodSet, TimestampSet]: - """ - Returns the temporal union of ``self`` and ``other``. - - Args: - other: temporal object to merge with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - union_timestampset_timestamp, union_set_set, union_spanset_span, union_spanset_spanset - """ - from .period import Period - from .periodset import PeriodSet - if isinstance(other, datetime): - return TimestampSet(_inner=union_timestampset_timestamp(self._inner, datetime_to_timestamptz(other))) - elif isinstance(other, TimestampSet): - return TimestampSet(_inner=union_set_set(self._inner, other._inner)) - elif isinstance(other, Period): - return PeriodSet(_inner=union_spanset_span(set_to_spanset(self._inner), other._inner)) - elif isinstance(other, PeriodSet): - return PeriodSet(_inner=union_spanset_spanset(set_to_spanset(self._inner), other._inner)) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __add__(self, other): - """ - Returns the temporal union of ``self`` and ``other``. - - Args: - other: temporal object to merge with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - union_timestampset_timestamp, union_set_set, union_spanset_span, union_spanset_spanset - """ - return self.union(other) - - # ------------------------- Comparisons ----------------------------------- - def __eq__(self, other): - """ - Returns whether ``self`` and ``other`` are equal. - - Args: - other: temporal object to compare with - - Returns: - True if equal, False otherwise - - MEOS Functions: - set_eq - """ - if isinstance(other, self.__class__): - return set_eq(self._inner, other._inner) - return False - - def __ne__(self, other): - """ - Returns whether ``self`` and ``other`` are not equal. - - Args: - other: temporal object to compare with - - Returns: - True if not equal, False otherwise - - MEOS Functions: - set_ne - """ - if isinstance(other, self.__class__): - return set_ne(self._inner, other._inner) - return True - - def __lt__(self, other): - """ - Return whether ``self`` is less than ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if less than, False otherwise - - MEOS Functions: - set_lt - """ - if isinstance(other, self.__class__): - return set_lt(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __le__(self, other): - """ - Return whether ``self`` is less than or equal to ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if less than or equal, False otherwise - - MEOS Functions: - set_le - """ - if isinstance(other, self.__class__): - return set_le(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __gt__(self, other): - """ - Return whether ``self`` is greater than ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if greater than, False otherwise - - MEOS Functions: - set_gt - """ - if isinstance(other, self.__class__): - return set_gt(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - def __ge__(self, other): - """ - Return whether ``self`` is greater than or equal to ``other``. - - Args: - other: temporal object to compare with - - Returns: - True if greater than or equal, False otherwise - - MEOS Functions: - set_ge - """ - if isinstance(other, self.__class__): - return set_ge(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - - # ------------------------- Plot Operations ------------------------------- - def plot(self, *args, **kwargs): - from ..plotters import TimePlotter - return TimePlotter.plot_timestampset(self, *args, **kwargs) - - # ------------------------- Database Operations --------------------------- - @staticmethod - def read_from_cursor(value, _=None): - """ - Reads a :class:`TimestampSet` from a database cursor. Used when automatically loading objects from the database. - Users should use the class constructor instead. - """ - if not value: - return None - return TimestampSet(string=value) - diff --git a/pymeos/pyproject.toml b/pymeos/pyproject.toml index c815a1d7..e7f93e38 100644 --- a/pymeos/pyproject.toml +++ b/pymeos/pyproject.toml @@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta' [project] name = 'pymeos' -version = '1.1.3-alpha-2' +version = '1.1.3-alpha.6-1' authors = [ { name = 'Victor Divi', email = 'vdiviloper@gmail.com' }, { name = 'Zhicheng Luo', email = 'zhicheng.luo@ulb.be' }, @@ -34,12 +34,9 @@ license = {file = 'LICENSE'} requires-python = '>=3.7' dependencies = [ - 'pymeos-cffi >=1.1.0a4', + 'pymeos-cffi ==1.1.0a6', 'python-dateutil', - 'spans', - 'postgis', 'shapely', - 'geopandas' ] [project.optional-dependencies] @@ -59,6 +56,10 @@ plot = [ 'matplotlib' ] +pandas = [ + 'geopandas' +] + [project.urls] 'Homepage' = 'https://github.com/MobilityDB/PyMEOS/pymeos' 'Bug Tracker' = 'https://github.com/MobilityDB/PyMEOS/issues' diff --git a/pymeos/tests/time/__init__.py b/pymeos/tests/boxes/__init__.py similarity index 100% rename from pymeos/tests/time/__init__.py rename to pymeos/tests/boxes/__init__.py diff --git a/pymeos/tests/boxes/stbox_test.py b/pymeos/tests/boxes/stbox_test.py index 5bd83410..beef4d7a 100644 --- a/pymeos/tests/boxes/stbox_test.py +++ b/pymeos/tests/boxes/stbox_test.py @@ -172,7 +172,7 @@ def test_from_tpoint_expand_space_constructor(self, tpoint, expected): ], ids=['STBox X', 'STBox Z', 'STBox XT', 'STBox ZT'] ) - def test_from_geo_expand_space_constructor(self, stbox, expected): + def test_from_stbox_expand_space_constructor(self, stbox, expected): stb = STBox.from_expanding_bounding_box(stbox, 1) assert isinstance(stb, STBox) assert str(stb) == expected @@ -215,6 +215,7 @@ def test_from_quad_split_flat(self, stbox, expected): ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] ) def test_from_as_constructor(self, stbox): + assert stbox == STBox(str(stbox)) assert stbox == stbox.from_wkb(stbox.as_wkb()) assert stbox == stbox.from_hexwkb(stbox.as_hexwkb()) @@ -487,25 +488,23 @@ def test_tmin_tmax_inc(self, stbox, expected): [ (stbx, 0), (stbz, 0), - (stbt, 0), (stbxt, 0), (stbzt, 0), (gstbx, 4326), (gstbz, 4326), - (gstbt, 4326), (gstbxt, 4326), (gstbzt, 4326) ], - ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT', - 'Geodetic STBox X', 'Geodetic STBox Z', 'Geodetic STBox T', 'Geodetic STBox XT', 'Geodetic STBox ZT'] + ids=['STBox X', 'STBox Z', 'STBox XT', 'STBox ZT', + 'Geodetic STBox X', 'Geodetic STBox Z', 'Geodetic STBox XT', 'Geodetic STBox ZT'] ) def test_srid(self, stbox, expected): assert stbox.srid() == expected @pytest.mark.parametrize( 'stbox', - [stbx, stbz, stbt, stbxt, stbzt], - ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + [stbx, stbz, stbxt, stbzt], + ids=['STBox X', 'STBox Z', 'STBox XT', 'STBox ZT'] ) def test_set_srid(self, stbox): assert stbox.set_srid(5676).srid() == 5676 @@ -518,6 +517,21 @@ class TestSTBoxTransformations(TestSTBox): stbxt = STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') stbzt = STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])') + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, STBox('STBOX X((1,1),(2,2))')), + (stbz, STBox('STBOX Z((1,1,1),(2,2,2))')), + (stbxt, STBox('STBOX X((1,1),(2,2))')), + (stbzt, STBox('STBOX Z((1,1,1),(2,2,2))')), + ], + ids=['STBox X', 'STBox Z', 'STBox XT', 'STBox ZT'] + ) + def test_get_space(self, stbox, expected): + stb = stbox.get_space() + assert isinstance(stb, STBox) + assert stb == expected + @pytest.mark.parametrize( 'stbox, expected', [ @@ -547,46 +561,37 @@ def test_expand_time(self, stbox, expected): assert isinstance(stb, STBox) assert stb == expected - ###################################### - # THIS TEST DOES NOT WORK CORRECTLY - ###################################### @pytest.mark.parametrize( 'stbox, delta, expected', [(stbt, timedelta(days=4), - STBox('STBOX T([2019-09-01,2019-09-02])')), + STBox('STBOX T([2019-09-05,2019-09-06])')), (stbt, timedelta(days=-4), - STBox('STBOX T([2019-09-01,2019-09-02])')), + STBox('STBOX T([2019-08-28,2019-08-29])')), (stbt, timedelta(hours=2), - STBox('STBOX T([2019-09-01,2019-09-02])')), + STBox('STBOX T([2019-09-01 02:00:00,2019-09-02 02:00:00])')), (stbt, timedelta(hours=-2), - STBox('STBOX T([2019-09-01,2019-09-02])')), + STBox('STBOX T([2019-08-31 22:00:00,2019-09-01 22:00:00])')), ], ids=['positive days', 'negative days', 'positive hours', 'negative hours'] ) - def test_shift(self, stbox, delta, expected): - assert stbox.shift(delta) == expected + def test_shift_time(self, stbox, delta, expected): + assert stbox.shift_time(delta) == expected - ###################################### - # THIS TEST DOES NOT WORK CORRECTLY - ###################################### @pytest.mark.parametrize( 'stbox, delta, expected', [(stbt, timedelta(days=4), - STBox('STBOX T([2019-09-01,2019-09-02])')), + STBox('STBOX T([2019-09-01,2019-09-05])')), (stbt, timedelta(hours=2), - STBox('STBOX T([2019-09-01,2019-09-02])')), + STBox('STBOX T([2019-09-01,2019-09-01 02:00:00])')), ], ids=['positive days', 'positive hours'] ) - def test_tscale(self, stbox, delta, expected): - assert stbox.tscale(delta) == expected + def test_scale_time(self, stbox, delta, expected): + assert stbox.scale_time(delta) == expected - ###################################### - # THIS TEST DOES NOT WORK CORRECTLY - ###################################### - def test_shift_tscale(self): - assert self.stbt.shift_tscale(timedelta(days=4), timedelta(hours=4)) == \ - STBox('STBOX T([2019-09-01,2019-09-02])') + def test_shift_scale_time(self): + assert self.stbt.shift_scale_time(timedelta(days=4), timedelta(hours=4)) == \ + STBox('STBOX T([2019-09-05,2019-09-05 04:00:00])') @pytest.mark.parametrize( 'stbox, expected', @@ -604,7 +609,7 @@ def test_shift_tscale(self): ids=['STBox X', 'STBox Z', 'STBox XT', 'STBox ZT'] ) def test_round(self, stbox, expected): - assert stbox.round(maxdd=2) + assert stbox.round(max_decimals=2) class TestSTBoxTopologicalFunctions(TestSTBox): diff --git a/pymeos/tests/boxes/tbox_test.py b/pymeos/tests/boxes/tbox_test.py index 7c12bc1e..e208223e 100644 --- a/pymeos/tests/boxes/tbox_test.py +++ b/pymeos/tests/boxes/tbox_test.py @@ -1,10 +1,10 @@ from copy import copy from datetime import datetime, timezone, timedelta -from spans.types import intrange, floatrange import pytest from pymeos import TBox, TInterpolation, TimestampSet, Period, PeriodSet, \ + IntSpan, FloatSpan, \ TInt, TIntInst, TIntSeq, TIntSeqSet, TFloat, TFloatInst, TFloatSeq, TFloatSeqSet from tests.conftest import TestPyMEOS @@ -74,62 +74,64 @@ def test_hexwkb_constructor(self): [ (1, 'TBOXINT X([1, 2))'), (1.5, 'TBOXFLOAT X([1.5, 1.5])'), - (intrange(1, 2, True, True), 'TBOXINT X([1, 3))'), - (floatrange(1.5, 2.5, True, True), 'TBOXFLOAT X([1.5, 2.5])'), + (IntSpan(lower=1, upper=2, lower_inc=True, upper_inc=True), + 'TBOXINT X([1, 3))'), + (FloatSpan(lower=1.5, upper=2.5, lower_inc=True, upper_inc=True), + 'TBOXFLOAT X([1.5, 2.5])'), ], - ids=['int', 'float', 'intrange', 'floatrange'] + ids=['int', 'float', 'IntSpan', 'FloatSpan'] ) def test_from_value_constructor(self, value, expected): tb = TBox.from_value(value) assert isinstance(tb, TBox) assert str(tb) == expected - # @pytest.mark.parametrize( - # 'time, expected', - # [ - # (datetime(2019, 9, 1), - # 'TBOX T([2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), - # (TimestampSet('{2019-09-01, 2019-09-02}'), - # 'TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), - # (Period('[2019-09-01, 2019-09-02]'), - # 'TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), - # (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), - # 'TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), - # ], - # ids=['Timestamp', 'TimestampSet', 'Period', 'PeriodSet'] - # ) - # def test_from_time_constructor(self, time, expected): - # tb = TBox.from_time(time) - # assert isinstance(tb, TBox) - # assert str(tb) == expected - - # @pytest.mark.parametrize( - # 'value, time, expected', - # [ - # (1, datetime(2019, 9, 1), - # 'TBOXINT XT([1, 2),[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), - # (1.5, datetime(2019, 9, 1), - # 'TBOXFLOAT XT([1.5, 1.5],[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), - # (intrange(1, 2, True, True), datetime(2019, 9, 1), - # 'TBOXINT XT([1, 3),[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), - # (floatrange(1.5, 2.5, True, True), datetime(2019, 9, 1), - # 'TBOXFLOAT XT([1.5, 2.5],[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), - # (1, Period('[2019-09-01, 2019-09-02]'), - # 'TBOXINT XT([1, 3),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), - # (1.5, Period('[2019-09-01, 2019-09-02]'), - # 'TBOXFLOAT XT([1.5, 1.5],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), - # (intrange(1, 2, True, True), Period('[2019-09-01, 2019-09-02]'), - # 'TBOXINT XT([1, 3),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), - # (floatrange(1.5, 2.5, True, True), Period('[2019-09-01, 2019-09-02]'), - # 'TBOXFLOAT XT([1.5, 2.5],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), - # ], - # ids=['int-Timestamp', 'float-Timestamp', 'intrange-Timestamp', 'floatrange-Timestamp', - # 'int-Period', 'float-Period', 'intrange-Period', 'floatrange-Period',] - # ) - # def test_from_value_time_constructor(self, value, time, expected): - # tb = TBox.from_value_time(value, time) - # assert isinstance(tb, TBox) - # assert str(tb) == expected + @pytest.mark.parametrize( + 'time, expected', + [ + (datetime(2019, 9, 1), + 'TBOX T([2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (TimestampSet('{2019-09-01, 2019-09-02}'), + 'TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (Period('[2019-09-01, 2019-09-02]'), + 'TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), + 'TBOX T([2019-09-01 00:00:00+00, 2019-09-05 00:00:00+00])'), + ], + ids=['Timestamp', 'TimestampSet', 'Period', 'PeriodSet'] + ) + def test_from_time_constructor(self, time, expected): + tb = TBox.from_time(time) + assert isinstance(tb, TBox) + assert str(tb) == expected + + @pytest.mark.parametrize( + 'value, time, expected', + [ + (1, datetime(2019, 9, 1), + 'TBOXINT XT([1, 2),[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (1.5, datetime(2019, 9, 1), + 'TBOXFLOAT XT([1.5, 1.5],[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (IntSpan(lower=1, upper=2, lower_inc=True, upper_inc=True), datetime(2019, 9, 1), + 'TBOXINT XT([1, 3),[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (FloatSpan(lower=1.5, upper=2.5, lower_inc=True, upper_inc=True), datetime(2019, 9, 1), + 'TBOXFLOAT XT([1.5, 2.5],[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (1, Period('[2019-09-01, 2019-09-02]'), + 'TBOXINT XT([1, 2),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (1.5, Period('[2019-09-01, 2019-09-02]'), + 'TBOXFLOAT XT([1.5, 1.5],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (IntSpan(lower=1, upper=2, lower_inc=True, upper_inc=True), Period('[2019-09-01, 2019-09-02]'), + 'TBOXINT XT([1, 3),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (FloatSpan(lower=1.5, upper=2.5, lower_inc=True, upper_inc=True), Period('[2019-09-01, 2019-09-02]'), + 'TBOXFLOAT XT([1.5, 2.5],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + ], + ids=['int-Timestamp', 'float-Timestamp', 'IntSpan-Timestamp', 'FloatSpan-Timestamp', + 'int-Period', 'float-Period', 'IntSpan-Period', 'FloatSpan-Period',] + ) + def test_from_value_time_constructor(self, value, time, expected): + tb = TBox.from_value_time(value, time) + assert isinstance(tb, TBox) + assert str(tb) == expected @pytest.mark.parametrize( 'tnumber, expected', @@ -160,6 +162,7 @@ def test_from_tnumber_constructor(self, tnumber, expected): ids=['TBoxFloat X', 'TBox T', 'TBoxFloat XT'] ) def test_from_as_constructor(self, tbox): + assert tbox == TBox(str(tbox)) assert tbox == tbox.from_wkb(tbox.as_wkb()) assert tbox == tbox.from_hexwkb(tbox.as_hexwkb()) @@ -218,14 +221,14 @@ def test_as_hexwkb(self, tbox, expected): @pytest.mark.parametrize( 'tbox, expected', [ - (tbfx, floatrange(1.0, 2.0, True, True)), - (tbfxt, floatrange(1.0, 2.0, True, True)), + (tbfx, FloatSpan(lower=1.0, upper=2.0, lower_inc=True, upper_inc=True)), + (tbfxt, FloatSpan(lower=1.0, upper=2.0, lower_inc=True, upper_inc=True)), ], ids=['TBoxFloat X', 'TBoxFloat XT'] ) - def test_to_floatrange(self, tbox, expected): - tb = tbox.to_floatrange() - assert isinstance(tb, floatrange) + def test_to_floatspan(self, tbox, expected): + tb = tbox.to_floatspan() + assert isinstance(tb, FloatSpan) assert tb == expected @pytest.mark.parametrize( @@ -377,46 +380,75 @@ def test_expand_time(self, tbox, expected): assert isinstance(tb, TBox) assert tb == expected - ##################################### - ## THIS TEST DOES NOT WORK CORRECTLY - ##################################### + @pytest.mark.parametrize( + 'tbox, delta, expected', + [(tbfx, 2.0, TBox('TBOXFLOAT X([3,4])')), + (tbfx, -2.0, TBox('TBOXFLOAT X([-1,0])')), + (tbfxt, 2.0, TBox('TBOXFLOAT XT([3,4],[2019-09-01, 2019-09-02])')), + (tbfxt, -2.0, TBox('TBOXFLOAT XT([-1,0],[2019-09-01, 2019-09-02])')), + ], + ids=['TBox T positive', 'TBox T negative', + 'TBoxFloat XT positive', 'TBoxFloat XT negative' + ] + ) + def test_shift_value(self, tbox, delta, expected): + assert tbox.shift_value(delta) == expected + @pytest.mark.parametrize( 'tbox, delta, expected', [(tbt, timedelta(days=4), - TBox('TBOX T([2019-09-01,2019-09-02])')), + TBox('TBOX T([2019-09-05,2019-09-06])')), (tbt, timedelta(days=-4), - TBox('TBOX T([2019-09-01,2019-09-02])')), + TBox('TBOX T([2019-08-28,2019-08-29])')), (tbt, timedelta(hours=2), - TBox('TBOX T([2019-09-01,2019-09-02])')), + TBox('TBOX T([2019-09-01 02:00:00,2019-09-02 02:00:00])')), (tbt, timedelta(hours=-2), - TBox('TBOX T([2019-09-01,2019-09-02])')), + TBox('TBOX T([2019-08-31 22:00:00,2019-09-01 22:00:00])')), ], ids=['positive days', 'negative days', 'positive hours', 'negative hours'] ) - def test_shift(self, tbox, delta, expected): - assert tbox.shift(delta) == expected + def test_shift_time(self, tbox, delta, expected): + assert tbox.shift_time(delta) == expected + + @pytest.mark.parametrize( + 'tbox, delta, expected', + [(tbfx, 4.0, TBox('TBOXFLOAT X([1,5])')), + (tbfxt, 4.0, TBox('TBOXFLOAT XT([1,5],[2019-09-01, 2019-09-02])')), + ], + ids=['TBox T', 'TBoxFloat XT'] + ) + def test_scale_value(self, tbox, delta, expected): + assert tbox.scale_value(delta) == expected - ##################################### - ## THIS TEST DOES NOT WORK CORRECTLY - ##################################### @pytest.mark.parametrize( 'tbox, delta, expected', [(tbt, timedelta(days=4), - TBox('TBOX T([2019-09-01,2019-09-02])')), + TBox('TBOX T([2019-09-01,2019-09-05])')), (tbt, timedelta(hours=2), - TBox('TBOX T([2019-09-01,2019-09-02])')), + TBox('TBOX T([2019-09-01,2019-09-01 02:00:00])')), ], ids=['positive days', 'positive hours'] ) - def test_tscale(self, tbox, delta, expected): - assert tbox.tscale(delta) == expected + def test_scale_time(self, tbox, delta, expected): + assert tbox.scale_time(delta) == expected + + @pytest.mark.parametrize( + 'tbox, delta, width, expected', + [(tbfx, 2.0, 4.0, TBox('TBOXFLOAT X([3,7])')), + (tbfx, -2.0, 4.0, TBox('TBOXFLOAT X([-1,3])')), + (tbfxt, 2.0, 4.0, TBox('TBOXFLOAT XT([3,7],[2019-09-01, 2019-09-02])')), + (tbfxt, -2.0, 4.0, TBox('TBOXFLOAT XT([-1,3],[2019-09-01, 2019-09-02])')), + ], + ids=['TBox T positive', 'TBox T negative', + 'TBoxFloat XT positive', 'TBoxFloat XT negative' + ] + ) + def test_shift_scale_value(self, tbox, delta, width, expected): + assert tbox.shift_scale_value(delta, width) == expected - ##################################### - ## THIS TEST DOES NOT WORK CORRECTLY - ##################################### - def test_shift_tscale(self): - assert self.tbt.shift_tscale(timedelta(days=4), timedelta(hours=4)) == \ - TBox('TBOX T([2019-09-01,2019-09-02])') + def test_shift_scale_time(self): + assert self.tbt.shift_scale_time(timedelta(days=4), timedelta(hours=4)) == \ + TBox('TBOX T([2019-09-05,2019-09-05 04:00:00])') @pytest.mark.parametrize( 'tbox, expected', @@ -429,7 +461,7 @@ def test_shift_tscale(self): ids=['TBoxFloat X', 'TBoxFloat XT'] ) def test_round(self, tbox, expected): - assert tbox.round(maxdd=2) + assert tbox.round(max_decimals=2) class TestTBoxTopologicalFunctions(TestTBox): diff --git a/pymeos/tests/collections/__init__.py b/pymeos/tests/collections/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymeos/tests/collections/number/floatset_test.py b/pymeos/tests/collections/number/floatset_test.py new file mode 100644 index 00000000..ba05c43c --- /dev/null +++ b/pymeos/tests/collections/number/floatset_test.py @@ -0,0 +1,266 @@ +from copy import copy +from typing import List + +import pytest + +from pymeos import FloatSet, FloatSpan, FloatSpanSet +from tests.conftest import TestPyMEOS + + +class TestFloatSet(TestPyMEOS): + floatset = FloatSet('{1, 2, 3}') + + @staticmethod + def assert_intset_equality(floatset: FloatSet, values: List[int]): + assert floatset.num_elements() == len(values) + assert floatset.elements() == values + + +class TestFloatSetConstructors(TestFloatSet): + + def test_string_constructor(self): + self.assert_intset_equality(self.floatset, [1, 2, 3]) + + def test_list_constructor(self): + floatset = FloatSet(elements=[1, 2, 3]) + self.assert_intset_equality(floatset, [1, 2, 3]) + + def test_hexwkb_constructor(self): + floatset = FloatSet.from_hexwkb('0106000103000000000000000000F03F00000000000000400000000000000840') + self.assert_intset_equality(floatset, [1, 2, 3]) + + def test_from_as_constructor(self): + assert self.floatset == FloatSet(str(self.floatset)) + assert self.floatset == FloatSet.from_wkb(self.floatset.as_wkb()) + assert self.floatset == FloatSet.from_hexwkb(self.floatset.as_hexwkb()) + + def test_copy_constructor(self): + intset_copy = copy(self.floatset) + assert self.floatset == intset_copy + assert self.floatset is not intset_copy + + +class TestFloatSetOutputs(TestFloatSet): + + def test_str(self): + assert str(self.floatset) == '{1, 2, 3}' + + def test_repr(self): + assert repr(self.floatset) == 'FloatSet({1, 2, 3})' + + def test_as_hexwkb(self): + assert self.floatset.as_hexwkb() == '0106000103000000000000000000F03F00000000000000400000000000000840' + + +# class TestIntConversions(TestFloatSet): + + # def test_to_spanset(self): + # assert self.floatset.to_spanset() == FloatSpanSet( + # '{[1, 1], [2, 2], [3, 3]}') + + +class TestFloatSetAccessors(TestFloatSet): + + def test_to_span(self): + assert self.floatset.to_span() == FloatSpan('[1, 3]') + + def test_num_elements(self): + assert self.floatset.num_elements() == 3 + + def test_start_element(self): + assert self.floatset.start_element() == 1 + + def test_end_element(self): + assert self.floatset.end_element() == 3 + + def test_element_n(self): + assert self.floatset.element_n(1) == 2 + + def test_element_n_out_of_range(self): + with pytest.raises(IndexError): + self.floatset.element_n(3) + + def test_elements(self): + assert self.floatset.elements() == [1, 2, 3] + + def test_hash(self): + assert hash(self.floatset) == 2419122126 + + +class TestFloatSetTopologicalFunctions(TestFloatSet): + value = 5.0 + other = FloatSet('{5, 10}') + + @pytest.mark.parametrize( + 'arg, result', + [ + (other, False), + ], + ids=['other'] + ) + def test_is_contained_in(self, arg, result): + assert self.floatset.is_contained_in(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, False), + (other, False), + ], + ids=['value', 'other'] + ) + def test_contains(self, arg, result): + assert self.floatset.contains(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (other, False), + ], + ids=['other'] + ) + def test_overlaps(self, arg, result): + assert self.floatset.overlaps(arg) == result + +class TestFloatSetPositionFunctions(TestFloatSet): + value = 5.0 + other = FloatSet('{5, 10}') + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, True), + (other, True), + ], + ids=['value', 'other'] + ) + def test_is_left(self, arg, result): + assert self.floatset.is_left(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, True), + (other, True), + ], + ids=['value', 'other'] + ) + def test_is_over_or_left(self, arg, result): + assert self.floatset.is_over_or_left(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, False), + (other, False), + ], + ids=['value', 'other'] + ) + def test_is_right(self, arg, result): + assert self.floatset.is_right(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, False), + (other, False), + ], + ids=['value', 'other'] + ) + def test_is_over_or_right(self, arg, result): + assert self.floatset.is_over_or_right(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, 2.0), + (other, 2.0), + ], + ids=['value', 'other'] + ) + def test_distance(self, arg, result): + assert self.floatset.distance(arg) == result + + +class TestFloatSetSetFunctions(TestFloatSet): + value = 5.0 + floatset = FloatSet('{1, 10}') + + @pytest.mark.parametrize( + 'other', + [value, floatset], + ids=['value', 'floatset'] + ) + def test_intersection(self, other): + self.floatset.intersection(other) + self.floatset * other + + @pytest.mark.parametrize( + 'other', + [value, floatset], + ids=['value', 'floatset'] + ) + def test_union(self, other): + self.floatset.union(other) + self.floatset + other + + @pytest.mark.parametrize( + 'other', + [value, floatset], + ids=['value', 'floatset'] + ) + def test_minus(self, other): + self.floatset.minus(other) + self.floatset - other + + +class TestFloatSetComparisons(TestFloatSet): + floatset = FloatSet('{1, 10}') + other = FloatSet('{2, 10}') + + def test_eq(self): + _ = self.floatset == self.other + + def test_ne(self): + _ = self.floatset != self.other + + def test_lt(self): + _ = self.floatset < self.other + + def test_le(self): + _ = self.floatset <= self.other + + def test_gt(self): + _ = self.floatset > self.other + + def test_ge(self): + _ = self.floatset >= self.other + + +# class TestFloatSetTransformationFunctions(TestFloatSet): + + # @pytest.mark.parametrize( + # 'delta,result', + # [(4, [5, 6, 8]), + # (-4, [-3, -1, 0]), + # ], + # ids=['positive delta', 'negative delta'] + # ) + # def test_shift(self, delta, result): + # shifted = self.floatset.shift(delta) + # self.assert_intset_equality(shifted, result) + + # @pytest.mark.parametrize( + # 'delta,result', + # [(6, [1, 4, 7])], + # ids=['positive'] + # ) + # def test_scale(self, delta, result): + # scaled = self.floatset.scale(delta) + # self.assert_intset_equality(scaled, result) + + # def test_shift_scale(self): + # shifted_scaled = self.floatset.shift_scale(4, 4) + # self.assert_intset_equality(shifted_scaled, [5, 7, 9]) + + diff --git a/pymeos/tests/collections/number/intset_test.py b/pymeos/tests/collections/number/intset_test.py new file mode 100644 index 00000000..1464917c --- /dev/null +++ b/pymeos/tests/collections/number/intset_test.py @@ -0,0 +1,267 @@ +from copy import copy +from typing import List + +import pytest + +from pymeos import IntSet, IntSpan, IntSpanSet +from tests.conftest import TestPyMEOS + + +class TestIntSet(TestPyMEOS): + intset = IntSet('{1, 2, 3}') + + @staticmethod + def assert_intset_equality(intset: IntSet, values: List[int]): + assert intset.num_elements() == len(values) + assert intset.elements() == values + + +class TestIntSetConstructors(TestIntSet): + + def test_string_constructor(self): + self.assert_intset_equality(self.intset, [1, 2, 3]) + + def test_list_constructor(self): + intset = IntSet(elements=[1, 2, 3]) + self.assert_intset_equality(intset, [1, 2, 3]) + + def test_hexwkb_constructor(self): + intset = IntSet.from_hexwkb('010C000103000000010000000200000003000000') + self.assert_intset_equality(intset, [1, 2, 3]) + + def test_from_as_constructor(self): + assert self.intset == IntSet(str(self.intset)) + assert self.intset == IntSet.from_wkb(self.intset.as_wkb()) + assert self.intset == IntSet.from_hexwkb(self.intset.as_hexwkb()) + + def test_copy_constructor(self): + intset_copy = copy(self.intset) + assert self.intset == intset_copy + assert self.intset is not intset_copy + + +class TestIntSetOutputs(TestIntSet): + + def test_str(self): + assert str(self.intset) == '{1, 2, 3}' + + def test_repr(self): + assert repr( + self.intset) == 'IntSet({1, 2, 3})' + + def test_as_hexwkb(self): + assert self.intset.as_hexwkb() == '010C000103000000010000000200000003000000' + + +# class TestIntConversions(TestIntSet): + + # def test_to_spanset(self): + # assert self.intset.to_spanset() == IntSpanSet( + # '{[1, 1], [2, 2], [3, 3]}') + + +class TestIntSetAccessors(TestIntSet): + + def test_to_span(self): + assert self.intset.to_span() == IntSpan('[1, 3]') + + def test_num_elements(self): + assert self.intset.num_elements() == 3 + + def test_start_element(self): + assert self.intset.start_element() == 1 + + def test_end_element(self): + assert self.intset.end_element() == 3 + + def test_element_n(self): + assert self.intset.element_n(1) == 2 + + def test_element_n_out_of_range(self): + with pytest.raises(IndexError): + self.intset.element_n(3) + + def test_elements(self): + assert self.intset.elements() == [1, 2, 3] + + def test_hash(self): + assert hash(self.intset) == 3969573766 + + +class TestIntSetTopologicalFunctions(TestIntSet): + value = 5 + other = IntSet('{5, 10}') + + @pytest.mark.parametrize( + 'arg, result', + [ + (other, False), + ], + ids=['other'] + ) + def test_is_contained_in(self, arg, result): + assert self.intset.is_contained_in(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, False), + (other, False), + ], + ids=['value', 'other'] + ) + def test_contains(self, arg, result): + assert self.intset.contains(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (other, False), + ], + ids=['other'] + ) + def test_overlaps(self, arg, result): + assert self.intset.overlaps(arg) == result + +class TestIntSetPositionFunctions(TestIntSet): + value = 5 + other = IntSet('{5, 10}') + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, True), + (other, True), + ], + ids=['value', 'other'] + ) + def test_is_left(self, arg, result): + assert self.intset.is_left(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, True), + (other, True), + ], + ids=['value', 'other'] + ) + def test_is_over_or_left(self, arg, result): + assert self.intset.is_over_or_left(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, False), + (other, False), + ], + ids=['value', 'other'] + ) + def test_is_right(self, arg, result): + assert self.intset.is_right(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, False), + (other, False), + ], + ids=['value', 'other'] + ) + def test_is_over_or_right(self, arg, result): + assert self.intset.is_over_or_right(arg) == result + + @pytest.mark.parametrize( + 'arg, result', + [ + (value, 2), + (other, 2), + ], + ids=['value', 'other'] + ) + def test_distance(self, arg, result): + assert self.intset.distance(arg) == result + + +class TestIntSetSetFunctions(TestIntSet): + value = 1 + intset = IntSet('{1, 10}') + + @pytest.mark.parametrize( + 'other', + [value, intset], + ids=['value', 'intset'] + ) + def test_intersection(self, other): + self.intset.intersection(other) + self.intset * other + + @pytest.mark.parametrize( + 'other', + [value, intset], + ids=['value', 'intset'] + ) + def test_union(self, other): + self.intset.union(other) + self.intset + other + + @pytest.mark.parametrize( + 'other', + [value, intset], + ids=['value', 'intset'] + ) + def test_minus(self, other): + self.intset.minus(other) + self.intset - other + + +class TestIntSetComparisons(TestIntSet): + intset = IntSet('{1, 10}') + other = IntSet('{2, 10}') + + def test_eq(self): + _ = self.intset == self.other + + def test_ne(self): + _ = self.intset != self.other + + def test_lt(self): + _ = self.intset < self.other + + def test_le(self): + _ = self.intset <= self.other + + def test_gt(self): + _ = self.intset > self.other + + def test_ge(self): + _ = self.intset >= self.other + + +# class TestIntSetTransformationFunctions(TestIntSet): + + # @pytest.mark.parametrize( + # 'delta,result', + # [(4, [5, 6, 8]), + # (-4, [-3, -1, 0]), + # ], + # ids=['positive delta', 'negative delta'] + # ) + # def test_shift(self, delta, result): + # shifted = self.intset.shift(delta) + # self.assert_intset_equality(shifted, result) + + # @pytest.mark.parametrize( + # 'delta,result', + # [(6, [1, 4, 7])], + # ids=['positive'] + # ) + # def test_scale(self, delta, result): + # scaled = self.intset.scale(delta) + # self.assert_intset_equality(scaled, result) + + # def test_shift_scale(self): + # shifted_scaled = self.intset.shift_scale(4, 4) + # self.assert_intset_equality(shifted_scaled, [5, 7, 9]) + + diff --git a/pymeos/tests/collections/number/intspan_test.py b/pymeos/tests/collections/number/intspan_test.py new file mode 100644 index 00000000..aa25e794 --- /dev/null +++ b/pymeos/tests/collections/number/intspan_test.py @@ -0,0 +1,305 @@ +from copy import copy + +import pytest + +from pymeos import IntSpan, IntSpanSet + +from tests.conftest import TestPyMEOS + + +class TestIntSpan(TestPyMEOS): + intspan = IntSpan('[7, 10)') + + @staticmethod + def assert_intspan_equality(intspan: IntSpan, + lower: int = None, + upper: int = None, + lower_inc: bool = None, + upper_inc: bool = None): + if lower is not None: + assert intspan.lower() == lower + if upper is not None: + assert intspan.upper() == upper + if lower_inc is not None: + assert intspan.lower_inc() == lower_inc + if upper_inc is not None: + assert intspan.upper_inc() == upper_inc + + +class TestIntSpanConstructors(TestIntSpan): + + @pytest.mark.parametrize( + 'source, params', + [ + ('(7, 10)', (8, 10, True, False)), + ('[7, 10]', (7, 11, True, False)), + ] + ) + def test_string_constructor(self, source, params): + intspan = IntSpan(source) + self.assert_intspan_equality(intspan, *params) + + @pytest.mark.parametrize( + 'input_lower,input_upper,lower,upper', + [ + ('7', '10', 7, 10), + (7, 10, 7, 10), + (7, '10', 7, 10), + ], + ids=['string', 'int', 'mixed'] + ) + def test_constructor_bounds(self, input_lower, input_upper, lower, upper): + intspan = IntSpan(lower=lower, upper=upper) + self.assert_intspan_equality(intspan, lower, upper) + + def test_constructor_bound_inclusivity_defaults(self): + intspan = IntSpan(lower='7', upper='10') + self.assert_intspan_equality(intspan, lower_inc=True, upper_inc=False) + + @pytest.mark.parametrize( + 'lower,upper', + [ + (True, True), + (True, False), + (False, True), + (False, False), + ] + ) + def test_constructor_bound_inclusivity(self, lower, upper): + intspan = IntSpan(lower='7', upper='10', lower_inc=lower, upper_inc=upper) + self.assert_intspan_equality(intspan, lower_inc=True, upper_inc=False) + + def test_hexwkb_constructor(self): + source = '010D0001070000000A000000' + intspan = IntSpan.from_hexwkb(source) + self.assert_intspan_equality(intspan, 7, 10, True, False) + + def test_from_as_constructor(self): + assert self.intspan == IntSpan(str(self.intspan)) + assert self.intspan == IntSpan.from_wkb(self.intspan.as_wkb()) + assert self.intspan == IntSpan.from_hexwkb(self.intspan.as_hexwkb()) + + def test_copy_constructor(self): + other = copy(self.intspan) + assert self.intspan == other + assert self.intspan is not other + + +class TestIntSpanOutputs(TestIntSpan): + + def test_str(self): + assert str(self.intspan) == '[7, 10)' + + def test_repr(self): + assert repr(self.intspan) == 'IntSpan([7, 10))' + + def test_hexwkb(self): + assert self.intspan.as_hexwkb() == '010D0001070000000A000000' + + +class TestIntSpanConversions(TestIntSpan): + + def test_to_intspanset(self): + intspanset = self.intspan.to_spanset() + assert isinstance(intspanset, IntSpanSet) + assert intspanset.num_spans() == 1 + assert intspanset.start_span() == self.intspan + + +class TestIntSpanAccessors(TestIntSpan): + intspan2 = IntSpan('[8, 11]') + + def test_lower(self): + assert self.intspan.lower() == 7 + assert self.intspan2.lower() == 8 + + def test_upper(self): + assert self.intspan.upper() == 10 + assert self.intspan2.upper() == 12 + + def test_lower_inc(self): + assert self.intspan.lower_inc() + assert self.intspan2.lower_inc() + + def test_upper_inc(self): + assert not self.intspan.upper_inc() + assert not self.intspan2.upper_inc() + + def test_width(self): + assert self.intspan.width() == 3 + assert self.intspan2.width() == 4 + + def test_hash(self): + assert hash(self.intspan) == 1519224342 + + +class TestIntSpanTransformations(TestIntSpan): + + @pytest.mark.parametrize( + 'delta,result', + [(4, (11, 14, True, False)), + (-4, (3, 6, True, False)), + ], + ids=['positive delta', 'negative delta'] + ) + def test_shift(self, delta, result): + shifted = self.intspan.shift(delta) + self.assert_intspan_equality(shifted, *result) + + @pytest.mark.parametrize( + 'delta,result', + [(4, (7, 12, True, False)), + ], + ids=['positive'] + ) + def test_scale(self, delta, result): + scaled = self.intspan.scale(delta) + self.assert_intspan_equality(scaled, *result) + + def test_shift_scale(self): + shifted_scaled = self.intspan.shift_scale(4, 2) + self.assert_intspan_equality(shifted_scaled, 11, 14, True, False) + + +class TestIntSpanTopologicalPositionFunctions(TestIntSpan): + value = 5 + intspan = IntSpan('(1, 20)') + intspanset = IntSpanSet('{(1, 20), (31, 41)}') + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_is_adjacent(self, other): + self.intspan.is_adjacent(other) + + @pytest.mark.parametrize( + 'other', + [intspan, intspanset], + ids=['intspan', 'intspanset'] + ) + def test_is_contained_in(self, other): + self.intspan.is_contained_in(other) + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_contains(self, other): + self.intspan.contains(other) + _ = other in self.intspan + + @pytest.mark.parametrize( + 'other', + [intspan, intspanset], + ids=['intspan', 'intspanset'] + ) + def test_overlaps(self, other): + self.intspan.overlaps(other) + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_is_same(self, other): + self.intspan.is_same(other) + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_is_left(self, other): + self.intspan.is_left(other) + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_is_over_or_left(self, other): + self.intspan.is_over_or_left(other) + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_is_right(self, other): + self.intspan.is_right(other) + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_is_over_or_right(self, other): + self.intspan.is_over_or_right(other) + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_distance(self, other): + self.intspan.distance(other) + + +class TestIntSpanSetFunctions(TestIntSpan): + value = 1 + intspan = IntSpan('(1, 20)') + intspanset = IntSpanSet('{(1, 20), (31, 41)}') + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_intersection(self, other): + self.intspan.intersection(other) + self.intspan * other + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_union(self, other): + self.intspan.union(other) + self.intspan + other + + @pytest.mark.parametrize( + 'other', + [value, intspan, intspanset], + ids=['value', 'intspan', 'intspanset'] + ) + def test_minus(self, other): + self.intspan.minus(other) + self.intspan - other + + +class TestIntSpanComparisons(TestIntSpan): + intspan = IntSpan('(1, 20)') + other = IntSpan('[5, 10)') + + def test_eq(self): + _ = self.intspan == self.other + + def test_ne(self): + _ = self.intspan != self.other + + def test_lt(self): + _ = self.intspan < self.other + + def test_le(self): + _ = self.intspan <= self.other + + def test_gt(self): + _ = self.intspan > self.other + + def test_ge(self): + _ = self.intspan >= self.other + diff --git a/pymeos/tests/collections/text/__init__.py b/pymeos/tests/collections/text/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymeos/tests/collections/text/textset_test.py b/pymeos/tests/collections/text/textset_test.py new file mode 100644 index 00000000..a0f5ccad --- /dev/null +++ b/pymeos/tests/collections/text/textset_test.py @@ -0,0 +1,156 @@ +from copy import copy +from datetime import datetime, timezone, timedelta +from typing import List + +import pytest + +from pymeos import TextSet +from tests.conftest import TestPyMEOS + + +class TestTextSet(TestPyMEOS): + tset = TextSet('{A, BB, ccc}') + + @staticmethod + def assert_textset_equality(tset: TextSet, + elements: List[str]): + assert tset.num_elements() == len(elements) + assert tset.elements() == elements + + +class TestTextSetConstructors(TestTextSet): + + def test_string_constructor(self): + self.assert_textset_equality(self.tset, ['A', 'BB', 'ccc']) + + def test_list_constructor(self): + ts_set = TextSet(elements=['A', 'BB', 'ccc']) + self.assert_textset_equality(ts_set, ['A', 'BB', 'ccc']) + + def test_hexwkb_constructor(self): + ts_set = TextSet.from_hexwkb( + '011A000103000000020000000000000041000300000000000000424200040000000000000063636300') + self.assert_textset_equality(ts_set, ['A', 'BB', 'ccc']) + + def test_from_as_constructor(self): + assert self.tset == TextSet(str(self.tset)) + assert self.tset == TextSet.from_wkb(self.tset.as_wkb()) + assert self.tset == TextSet.from_hexwkb(self.tset.as_hexwkb()) + + def test_copy_constructor(self): + ts_set_copy = copy(self.tset) + assert self.tset == ts_set_copy + assert self.tset is not ts_set_copy + + +class TestTextSetOutputs(TestTextSet): + + def test_str(self): + assert str(self.tset) == '{"A", "BB", "ccc"}' + + def test_repr(self): + assert repr(self.tset) == 'TextSet({"A", "BB", "ccc"})' + + def test_as_hexwkb(self): + assert self.tset.as_hexwkb() == ('011A00010300000002000000000000004' + '1000300000000000000424200040000000000000063636300') + + +class TestTimestampConversions(TestTextSet): + + def test_to_spanset(self): + with pytest.raises(NotImplementedError): + self.tset.to_spanset() + + def test_to_span(self): + with pytest.raises(NotImplementedError): + self.tset.to_span() + + +class TestTextSetAccessors(TestTextSet): + + def test_num_elements(self): + assert self.tset.num_elements() == 3 + assert len(self.tset) == 3 + + def test_start_element(self): + assert self.tset.start_element() == 'A' + + def test_end_element(self): + assert self.tset.end_element() == 'ccc' + + def test_element_n(self): + assert self.tset.element_n(1) == 'BB' + + def test_element_n_out_of_range(self): + with pytest.raises(IndexError): + self.tset.element_n(3) + + def test_elements(self): + assert self.tset.elements() == ['A', 'BB', 'ccc', ] + + def test_hash(self): + assert hash(self.tset) == 3145376687 + + +class TestTextSetSetFunctions(TestTextSet): + string = 'A' + other = TextSet('{a, BB, ccc}') + + @pytest.mark.parametrize( + 'other, expected', + [(string, 'A'), + (other, TextSet('{BB, ccc}'))], + ids=['string', 'TextSet'] + ) + def test_intersection(self, other, expected): + assert self.tset.intersection(other) == expected + assert self.tset * other == expected + + @pytest.mark.parametrize( + 'other, expected', + [(string, TextSet('{A, BB, ccc}')), + (other, TextSet('{A, a, BB, ccc}'))], + ids=['string', 'TextSet'] + ) + def test_union(self, other, expected): + assert self.tset.union(other) == expected + assert self.tset + other == expected + + @pytest.mark.parametrize( + 'other, expected', + [(string, TextSet('{BB, ccc}')), + (other, TextSet('{A}'))], + ids=['string', 'TextSet'] + ) + def test_minus(self, other, expected): + assert self.tset.minus(other) == expected + assert self.tset - other == expected + + +class TestTextSetComparisons(TestTextSet): + other = TextSet('{2020-01-02 00:00:00+0, 2020-03-31 00:00:00+0}') + + def test_eq(self): + _ = self.tset == self.other + + def test_ne(self): + _ = self.tset != self.other + + def test_lt(self): + _ = self.tset < self.other + + def test_le(self): + _ = self.tset <= self.other + + def test_gt(self): + _ = self.tset > self.other + + def test_ge(self): + _ = self.tset >= self.other + + +class TestTextSetMiscFunctions(TestTextSet): + + def test_hash(self): + hash(self.tset) diff --git a/pymeos/tests/collections/time/__init__.py b/pymeos/tests/collections/time/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymeos/tests/time/period_test.py b/pymeos/tests/collections/time/period_test.py similarity index 79% rename from pymeos/tests/time/period_test.py rename to pymeos/tests/collections/time/period_test.py index cd8a3506..5c7525a4 100644 --- a/pymeos/tests/time/period_test.py +++ b/pymeos/tests/collections/time/period_test.py @@ -82,6 +82,7 @@ def test_hexwkb_constructor(self): datetime(2019, 9, 10, tzinfo=timezone.utc), False, False) def test_from_as_constructor(self): + assert self.period == Period(str(self.period)) assert self.period == Period.from_wkb(self.period.as_wkb()) assert self.period == Period.from_hexwkb(self.period.as_hexwkb()) @@ -143,7 +144,7 @@ def test_hash(self): assert hash(self.period) == 1164402929 -class TestPeriodTransformationFunctions(TestPeriod): +class TestPeriodTransformations(TestPeriod): @pytest.mark.parametrize( 'delta,result', @@ -172,27 +173,21 @@ def test_shift(self, delta, result): ], ids=['days', 'hours'] ) - def test_tscale(self, delta, result): - scaled = self.period.tscale(delta) + def test_scale(self, delta, result): + scaled = self.period.scale(delta) self.assert_period_equality(scaled, *result) - def test_shift_tscale(self): - shifted_scaled = self.period.shift_tscale(timedelta(days=4), timedelta(hours=4)) + def test_shift_scale(self): + shifted_scaled = self.period.shift_scale(timedelta(days=4), timedelta(hours=4)) self.assert_period_equality(shifted_scaled, datetime(2019, 9, 12, 0, tzinfo=timezone.utc), datetime(2019, 9, 12, 4, tzinfo=timezone.utc), False, False) - def test_expand(self): - expanded = self.period.expand(Period('(2021-01-01 00:00:00+0, 2021-02-01 00:00:00+0)')) - self.assert_period_equality(expanded, datetime(2019, 9, 8, tzinfo=timezone.utc), - datetime(2021, 2, 1, tzinfo=timezone.utc), False, False) - class TestPeriodTopologicalPositionFunctions(TestPeriod): period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') periodset = PeriodSet( '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') timestamp = datetime(year=2020, month=1, day=1) - timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') instant = TFloatInst('1.0@2020-01-01') discrete_sequence = TFloatSeq('{1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31}') stepwise_sequence = TFloatSeq('Interp=Step;(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') @@ -204,9 +199,9 @@ class TestPeriodTopologicalPositionFunctions(TestPeriod): @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] ) def test_is_adjacent(self, other): @@ -224,9 +219,9 @@ def test_is_contained_in(self, other): @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] ) def test_contains(self, other): @@ -235,9 +230,9 @@ def test_contains(self, other): @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] ) def test_overlaps(self, other): @@ -245,9 +240,9 @@ def test_overlaps(self, other): @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] ) def test_is_same(self, other): @@ -255,49 +250,53 @@ def test_is_same(self, other): @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] ) def test_is_before(self, other): self.period.is_before(other) + self.period.is_left(other) @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] ) def test_is_over_or_before(self, other): self.period.is_over_or_before(other) + self.period.is_over_or_left(other) @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] ) def test_is_after(self, other): self.period.is_after(other) + self.period.is_right(other) @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] ) def test_is_over_or_after(self, other): self.period.is_over_or_after(other) + self.period.is_over_or_right(other) @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] ) def test_distance(self, other): @@ -309,12 +308,11 @@ class TestPeriodSetFunctions(TestPeriod): periodset = PeriodSet( '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') timestamp = datetime(year=2020, month=1, day=1) - timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset], - ids=['period', 'periodset', 'timestamp', 'timestampset'] + [period, periodset, timestamp], + ids=['period', 'periodset', 'timestamp'] ) def test_intersection(self, other): self.period.intersection(other) @@ -322,8 +320,8 @@ def test_intersection(self, other): @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset], - ids=['period', 'periodset', 'timestamp', 'timestampset'] + [period, periodset, timestamp], + ids=['period', 'periodset', 'timestamp'] ) def test_union(self, other): self.period.union(other) @@ -331,8 +329,8 @@ def test_union(self, other): @pytest.mark.parametrize( 'other', - [period, periodset, timestamp, timestampset], - ids=['period', 'periodset', 'timestamp', 'timestampset'] + [period, periodset, timestamp], + ids=['period', 'periodset', 'timestamp'] ) def test_minus(self, other): self.period.minus(other) diff --git a/pymeos/tests/time/periodset_test.py b/pymeos/tests/collections/time/periodset_test.py similarity index 76% rename from pymeos/tests/time/periodset_test.py rename to pymeos/tests/collections/time/periodset_test.py index e85fc4b2..1383a8a1 100644 --- a/pymeos/tests/time/periodset_test.py +++ b/pymeos/tests/collections/time/periodset_test.py @@ -1,342 +1,339 @@ -from copy import copy -from datetime import datetime, timezone, timedelta -from typing import List - -import pytest - -from pymeos import Period, PeriodSet, TimestampSet, TFloatInst, TFloatSeq, STBox, TFloatSeqSet, TBox -from tests.conftest import TestPyMEOS - - -class TestPeriodSet(TestPyMEOS): - periodset = PeriodSet('{[2019-09-01, 2019-09-02], [2019-09-03, 2019-09-04]}') - - @staticmethod - def assert_periodset_equality(periodset: PeriodSet, periods: List[Period]): - assert periodset.num_periods() == len(periods) - assert periodset.periods() == periods - - -class TestPeriodSetConstructors(TestPeriodSet): - - def test_string_constructor(self): - self.assert_periodset_equality(self.periodset, [ - Period('[2019-09-01, 2019-09-02]'), - Period('[2019-09-03, 2019-09-04]') - ]) - - def test_period_list_constructor(self): - periodset = PeriodSet(period_list=[ - Period('[2019-09-01, 2019-09-02]'), - Period('[2019-09-03, 2019-09-04]') - ]) - self.assert_periodset_equality(periodset, [ - Period('[2019-09-01, 2019-09-02]'), - Period('[2019-09-03, 2019-09-04]') - ]) - - def test_from_as_constructor(self): - assert self.periodset == PeriodSet.from_wkb(self.periodset.as_wkb()) - assert self.periodset == PeriodSet.from_hexwkb(self.periodset.as_hexwkb()) - - def test_copy_constructor(self): - copied = copy(self.periodset) - assert self.periodset == copied - assert self.periodset is not copied - - -class TestPeriodSetOutputs(TestPeriodSet): - - def test_str(self): - assert str(self.periodset) == '{[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00], ' \ - '[2019-09-03 00:00:00+00, 2019-09-04 00:00:00+00]}' - - def test_repr(self): - assert repr(self.periodset) == 'PeriodSet({[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00], ' \ - '[2019-09-03 00:00:00+00, 2019-09-04 00:00:00+00]})' - - def test_hexwkb(self): - assert self.periodset.as_hexwkb() == '012200020000000300A01E4E713402000000F66B85340200030060CD899934020000C0A4A7AD340200' - -class TestPeriodSetConversions(TestPeriodSet): - - def test_to_period(self): - assert self.periodset.to_period() == Period('[2019-09-01, 2019-09-04]') - - -class TestPeriodSetAccessors(TestPeriodSet): - periodset2 = PeriodSet('{[2019-09-01, 2019-09-02), (2019-09-02, 2019-09-04]}') - - def test_duration(self): - assert self.periodset.duration() == timedelta(days=2) - assert self.periodset.duration(True) == timedelta(days=3) - - def test_num_timestamps(self): - assert self.periodset.num_timestamps() == 4 - assert self.periodset2.num_timestamps() == 3 - - def test_start_timestamp(self): - assert self.periodset.start_timestamp() == datetime(2019, 9, 1, tzinfo=timezone.utc) - - def test_end_timestamp(self): - assert self.periodset.end_timestamp() == datetime(2019, 9, 4, tzinfo=timezone.utc) - - def test_timestamp_n(self): - assert self.periodset.timestamp_n(0) == datetime(2019, 9, 1, tzinfo=timezone.utc) - assert self.periodset.timestamp_n(1) == datetime(2019, 9, 2, tzinfo=timezone.utc) - assert self.periodset.timestamp_n(2) == datetime(2019, 9, 3, tzinfo=timezone.utc) - assert self.periodset.timestamp_n(3) == datetime(2019, 9, 4, tzinfo=timezone.utc) - - def test_timestamps(self): - assert self.periodset.timestamps() == [ - datetime(2019, 9, 1, tzinfo=timezone.utc), - datetime(2019, 9, 2, tzinfo=timezone.utc), - datetime(2019, 9, 3, tzinfo=timezone.utc), - datetime(2019, 9, 4, tzinfo=timezone.utc), - ] - - def test_num_periods(self): - assert self.periodset.num_periods() == 2 - - def test_start_period(self): - assert self.periodset.start_period() == Period('[2019-09-01, 2019-09-02]') - - def test_end_period(self): - assert self.periodset.end_period() == Period('[2019-09-03, 2019-09-04]') - - def test_period_n(self): - assert self.periodset.period_n(0) == Period('[2019-09-01, 2019-09-02]') - assert self.periodset.period_n(1) == Period('[2019-09-03, 2019-09-04]') - - def test_periods(self): - assert self.periodset.periods() == [ - Period('[2019-09-01, 2019-09-02]'), - Period('[2019-09-03, 2019-09-04]') - ] - - def test_hash(self): - assert hash(self.periodset) == 552347465 - - -class TestPeriodTransformationFunctions(TestPeriodSet): - - @pytest.mark.parametrize( - 'delta,result', - [(timedelta(days=4), - [Period('[2019-09-05 00:00:00+0, 2019-09-06 00:00:00+0]'), - Period('[2019-09-07 00:00:00+0, 2019-09-08 00:00:00+0]')]), - (timedelta(days=-4), - [Period('[2019-08-28 00:00:00+0, 2019-08-29 00:00:00+0]'), - Period('[2019-08-30 00:00:00+00, 2019-08-31 00:00:00+00]')]), - (timedelta(hours=2), - [Period('[2019-09-01 02:00:00+0, 2019-09-02 02:00:00+0]'), - Period('[2019-09-03 02:00:00+0, 2019-09-04 02:00:00+0]')]), - (timedelta(hours=-2), - [Period('[2019-08-31 22:00:00+0, 2019-09-01 22:00:00+0]'), - Period('[2019-09-02 22:00:00+0, 2019-09-03 22:00:00+0]')]), - ], - ids=['positive days', 'negative days', 'positive hours', 'negative hours'] - ) - def test_shift(self, delta, result): - shifted = self.periodset.shift(delta) - self.assert_periodset_equality(shifted, result) - - @pytest.mark.parametrize( - 'delta,result', - [(timedelta(days=3), - [Period('[2019-09-01 00:00:00+0, 2019-09-02 00:00:00+0]'), - Period('[2019-09-03 00:00:00+0, 2019-09-04 00:00:00+0]')]), - (timedelta(hours=6), - [Period('[2019-09-01 00:00:00+0, 2019-09-01 02:00:00+0]'), - Period('[2019-09-01 04:00:00+0, 2019-09-01 06:00:00+0]'), ]), - ], - ids=['days', 'hours'] - ) - def test_tscale(self, delta, result): - scaled = self.periodset.tscale(delta) - self.assert_periodset_equality(scaled, result) - - def test_shift_tscale(self): - shifted_scaled = self.periodset.shift_tscale(timedelta(days=4), timedelta(hours=6)) - self.assert_periodset_equality(shifted_scaled, [Period('[2019-09-05 00:00:00+0, 2019-09-05 02:00:00+0]'), - Period('[2019-09-05 04:00:00+0, 2019-09-05 06:00:00+0]')]) - - -class TestPeriodSetTopologicalPositionFunctions(TestPeriodSet): - period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') - periodset = PeriodSet( - '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') - timestamp = datetime(year=2020, month=1, day=1) - timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') - instant = TFloatInst('1.0@2020-01-01') - discrete_sequence = TFloatSeq('{1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31}') - stepwise_sequence = TFloatSeq('Interp=Step;(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') - continuous_sequence = TFloatSeq('(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') - sequence_set = TFloatSeqSet('{(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31], ' - '(1.0@2021-01-01, 3.0@2021-01-10, 10.0@2021-01-20, 0.0@2021-01-31]}') - tbox = TBox('TBox XT([0, 10),[2020-01-01, 2020-01-31])') - stbox = STBox('STBOX ZT(((1.0,2.0,3.0),(4.0,5.0,6.0)),[2001-01-01, 2001-01-02])') - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_adjacent(self, other): - self.periodset.is_adjacent(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, instant, discrete_sequence, stepwise_sequence, continuous_sequence, sequence_set, tbox, - stbox], - ids=['period', 'periodset', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', - 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_contained_in(self, other): - self.periodset.is_contained_in(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_contains(self, other): - self.periodset.contains(other) - _ = other in self.periodset - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_overlaps(self, other): - self.periodset.overlaps(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_same(self, other): - self.periodset.is_same(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_before(self, other): - self.periodset.is_before(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_over_or_before(self, other): - self.periodset.is_over_or_before(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_after(self, other): - self.periodset.is_after(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_over_or_after(self, other): - self.periodset.is_over_or_after(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_distance(self, other): - self.periodset.distance(other) - - -class TestPeriodSetSetFunctions(TestPeriodSet): - period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') - periodset = PeriodSet( - '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') - timestamp = datetime(year=2020, month=1, day=1) - timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset], - ids=['period', 'periodset', 'timestamp', 'timestampset'] - ) - def test_intersection(self, other): - self.periodset.intersection(other) - self.periodset * other - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset], - ids=['period', 'periodset', 'timestamp', 'timestampset'] - ) - def test_union(self, other): - self.periodset.union(other) - self.periodset + other - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset], - ids=['period', 'periodset', 'timestamp', 'timestampset'] - ) - def test_minus(self, other): - self.periodset.minus(other) - self.periodset - other - - -class TestPeriodComparisons(TestPeriodSet): - periodset = PeriodSet( - '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') - other = PeriodSet( - '{(2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0), (2022-01-01 00:00:00+0, 2022-01-31 00:00:00+0)}') - - def test_eq(self): - _ = self.periodset == self.other - - def test_ne(self): - _ = self.periodset != self.other - - def test_lt(self): - _ = self.periodset < self.other - - def test_le(self): - _ = self.periodset <= self.other - - def test_gt(self): - _ = self.periodset > self.other - - def test_ge(self): - _ = self.periodset >= self.other - - - +from copy import copy +from datetime import datetime, timezone, timedelta +from typing import List + +import pytest + +from pymeos import Period, PeriodSet, TFloatInst, TFloatSeq, STBox, TFloatSeqSet, TBox +from tests.conftest import TestPyMEOS + + +class TestPeriodSet(TestPyMEOS): + periodset = PeriodSet('{[2019-09-01, 2019-09-02], [2019-09-03, 2019-09-04]}') + + @staticmethod + def assert_periodset_equality(periodset: PeriodSet, periods: List[Period]): + assert periodset.num_periods() == len(periods) + assert periodset.periods() == periods + + +class TestPeriodSetConstructors(TestPeriodSet): + + def test_string_constructor(self): + self.assert_periodset_equality(self.periodset, [ + Period('[2019-09-01, 2019-09-02]'), + Period('[2019-09-03, 2019-09-04]') + ]) + + def test_span_list_constructor(self): + periodset = PeriodSet(span_list=[ + Period('[2019-09-01, 2019-09-02]'), + Period('[2019-09-03, 2019-09-04]') + ]) + self.assert_periodset_equality(periodset, [ + Period('[2019-09-01, 2019-09-02]'), + Period('[2019-09-03, 2019-09-04]') + ]) + + def test_from_as_constructor(self): + assert self.periodset == PeriodSet(str(self.periodset)) + assert self.periodset == PeriodSet.from_wkb(self.periodset.as_wkb()) + assert self.periodset == PeriodSet.from_hexwkb(self.periodset.as_hexwkb()) + + def test_copy_constructor(self): + copied = copy(self.periodset) + assert self.periodset == copied + assert self.periodset is not copied + + +class TestPeriodSetOutputs(TestPeriodSet): + + def test_str(self): + assert str(self.periodset) == '{[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00], ' \ + '[2019-09-03 00:00:00+00, 2019-09-04 00:00:00+00]}' + + def test_repr(self): + assert repr(self.periodset) == 'PeriodSet({[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00], ' \ + '[2019-09-03 00:00:00+00, 2019-09-04 00:00:00+00]})' + + def test_hexwkb(self): + assert self.periodset.as_hexwkb() == '012200020000000300A01E4E713402000000F66B85340200030060CD899934020000C0A4A7AD340200' + + +class TestPeriodSetConversions(TestPeriodSet): + + def test_to_period(self): + assert self.periodset.to_period() == Period('[2019-09-01, 2019-09-04]') + + +class TestPeriodSetAccessors(TestPeriodSet): + periodset2 = PeriodSet('{[2019-09-01, 2019-09-02), (2019-09-02, 2019-09-04]}') + + def test_duration(self): + assert self.periodset.duration() == timedelta(days=2) + assert self.periodset.duration(True) == timedelta(days=3) + + def test_num_timestamps(self): + assert self.periodset.num_timestamps() == 4 + assert self.periodset2.num_timestamps() == 3 + + def test_start_timestamp(self): + assert self.periodset.start_timestamp() == datetime(2019, 9, 1, tzinfo=timezone.utc) + + def test_end_timestamp(self): + assert self.periodset.end_timestamp() == datetime(2019, 9, 4, tzinfo=timezone.utc) + + def test_timestamp_n(self): + assert self.periodset.timestamp_n(0) == datetime(2019, 9, 1, tzinfo=timezone.utc) + assert self.periodset.timestamp_n(1) == datetime(2019, 9, 2, tzinfo=timezone.utc) + assert self.periodset.timestamp_n(2) == datetime(2019, 9, 3, tzinfo=timezone.utc) + assert self.periodset.timestamp_n(3) == datetime(2019, 9, 4, tzinfo=timezone.utc) + + def test_timestamps(self): + assert self.periodset.timestamps() == [ + datetime(2019, 9, 1, tzinfo=timezone.utc), + datetime(2019, 9, 2, tzinfo=timezone.utc), + datetime(2019, 9, 3, tzinfo=timezone.utc), + datetime(2019, 9, 4, tzinfo=timezone.utc), + ] + + def test_num_periods(self): + assert self.periodset.num_periods() == 2 + + def test_start_period(self): + assert self.periodset.start_period() == Period('[2019-09-01, 2019-09-02]') + + def test_end_period(self): + assert self.periodset.end_period() == Period('[2019-09-03, 2019-09-04]') + + def test_period_n(self): + assert self.periodset.period_n(0) == Period('[2019-09-01, 2019-09-02]') + assert self.periodset.period_n(1) == Period('[2019-09-03, 2019-09-04]') + + def test_periods(self): + assert self.periodset.periods() == [ + Period('[2019-09-01, 2019-09-02]'), + Period('[2019-09-03, 2019-09-04]') + ] + + def test_hash(self): + assert hash(self.periodset) == 552347465 + + +class TestPeriodSetTransformations(TestPeriodSet): + + @pytest.mark.parametrize( + 'delta,result', + [(timedelta(days=4), + [Period('[2019-09-05 00:00:00+0, 2019-09-06 00:00:00+0]'), + Period('[2019-09-07 00:00:00+0, 2019-09-08 00:00:00+0]')]), + (timedelta(days=-4), + [Period('[2019-08-28 00:00:00+0, 2019-08-29 00:00:00+0]'), + Period('[2019-08-30 00:00:00+00, 2019-08-31 00:00:00+00]')]), + (timedelta(hours=2), + [Period('[2019-09-01 02:00:00+0, 2019-09-02 02:00:00+0]'), + Period('[2019-09-03 02:00:00+0, 2019-09-04 02:00:00+0]')]), + (timedelta(hours=-2), + [Period('[2019-08-31 22:00:00+0, 2019-09-01 22:00:00+0]'), + Period('[2019-09-02 22:00:00+0, 2019-09-03 22:00:00+0]')]), + ], + ids=['positive days', 'negative days', 'positive hours', 'negative hours'] + ) + def test_shift(self, delta, result): + shifted = self.periodset.shift(delta) + self.assert_periodset_equality(shifted, result) + + @pytest.mark.parametrize( + 'delta,result', + [(timedelta(days=3), + [Period('[2019-09-01 00:00:00+0, 2019-09-02 00:00:00+0]'), + Period('[2019-09-03 00:00:00+0, 2019-09-04 00:00:00+0]')]), + (timedelta(hours=6), + [Period('[2019-09-01 00:00:00+0, 2019-09-01 02:00:00+0]'), + Period('[2019-09-01 04:00:00+0, 2019-09-01 06:00:00+0]'), ]), + ], + ids=['days', 'hours'] + ) + def test_scale(self, delta, result): + scaled = self.periodset.scale(delta) + self.assert_periodset_equality(scaled, result) + + def test_shift_scale(self): + shifted_scaled = self.periodset.shift_scale(timedelta(days=4), timedelta(hours=6)) + self.assert_periodset_equality(shifted_scaled, [Period('[2019-09-05 00:00:00+0, 2019-09-05 02:00:00+0]'), + Period('[2019-09-05 04:00:00+0, 2019-09-05 06:00:00+0]')]) + + +class TestPeriodSetTopologicalPositionFunctions(TestPeriodSet): + period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + timestamp = datetime(year=2020, month=1, day=1) + instant = TFloatInst('1.0@2020-01-01') + discrete_sequence = TFloatSeq('{1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31}') + stepwise_sequence = TFloatSeq('Interp=Step;(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') + continuous_sequence = TFloatSeq('(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') + sequence_set = TFloatSeqSet('{(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31], ' + '(1.0@2021-01-01, 3.0@2021-01-10, 10.0@2021-01-20, 0.0@2021-01-31]}') + tbox = TBox('TBox XT([0, 10),[2020-01-01, 2020-01-31])') + stbox = STBox('STBOX ZT(((1.0,2.0,3.0),(4.0,5.0,6.0)),[2001-01-01, 2001-01-02])') + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_adjacent(self, other): + self.periodset.is_adjacent(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, instant, discrete_sequence, stepwise_sequence, continuous_sequence, sequence_set, tbox, + stbox], + ids=['period', 'periodset', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', + 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_contained_in(self, other): + self.periodset.is_contained_in(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_contains(self, other): + self.periodset.contains(other) + _ = other in self.periodset + + @pytest.mark.parametrize( + 'other', + [period, periodset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_overlaps(self, other): + self.periodset.overlaps(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_same(self, other): + self.periodset.is_same(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_before(self, other): + self.periodset.is_before(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_over_or_before(self, other): + self.periodset.is_over_or_before(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_after(self, other): + self.periodset.is_after(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_over_or_after(self, other): + self.periodset.is_over_or_after(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_distance(self, other): + self.periodset.distance(other) + + +class TestPeriodSetSetFunctions(TestPeriodSet): + period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + timestamp = datetime(year=2020, month=1, day=1) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp], + ids=['period', 'periodset', 'timestamp'] + ) + def test_intersection(self, other): + self.periodset.intersection(other) + self.periodset * other + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp], + ids=['period', 'periodset', 'timestamp'] + ) + def test_union(self, other): + self.periodset.union(other) + self.periodset + other + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp], + ids=['period', 'periodset', 'timestamp'] + ) + def test_minus(self, other): + self.periodset.minus(other) + self.periodset - other + + +class TestPeriodSetComparisons(TestPeriodSet): + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + other = PeriodSet( + '{(2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0), (2022-01-01 00:00:00+0, 2022-01-31 00:00:00+0)}') + + def test_eq(self): + _ = self.periodset == self.other + + def test_ne(self): + _ = self.periodset != self.other + + def test_lt(self): + _ = self.periodset < self.other + + def test_le(self): + _ = self.periodset <= self.other + + def test_gt(self): + _ = self.periodset > self.other + + def test_ge(self): + _ = self.periodset >= self.other diff --git a/pymeos/tests/time/timestampset_test.py b/pymeos/tests/collections/time/timestampset_test.py similarity index 53% rename from pymeos/tests/time/timestampset_test.py rename to pymeos/tests/collections/time/timestampset_test.py index e91e0abd..3d7be503 100644 --- a/pymeos/tests/time/timestampset_test.py +++ b/pymeos/tests/collections/time/timestampset_test.py @@ -1,334 +1,288 @@ -from copy import copy -from datetime import datetime, timezone, timedelta -from typing import List - -import pytest - -from pymeos import Period, PeriodSet, TimestampSet, TFloatInst, TFloatSeq, STBox, TFloatSeqSet, TBox -from tests.conftest import TestPyMEOS - - -class TestTimestampSet(TestPyMEOS): - ts_set = TimestampSet('{2019-09-01 00:00:00+0, 2019-09-02 00:00:00+0, 2019-09-03 00:00:00+0}') - - @staticmethod - def assert_timestampset_equality(ts_set: TimestampSet, - timestamps: List[datetime]): - assert ts_set.num_timestamps() == len(timestamps) - assert ts_set.timestamps() == timestamps - - -class TestTimestampSetConstructors(TestTimestampSet): - - def test_string_constructor(self): - self.assert_timestampset_equality(self.ts_set, [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), - datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), - datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) - - def test_list_constructor(self): - ts_set = TimestampSet(timestamp_list=[datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), - datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), - datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) - self.assert_timestampset_equality(ts_set, [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), - datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), - datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) - - def test_hexwkb_constructor(self): - ts_set = TimestampSet.from_hexwkb('012000010300000000A01E4E713402000000F66B853402000060CD8999340200') - self.assert_timestampset_equality(ts_set, [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), - datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), - datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) - - def test_from_as_constructor(self): - assert self.ts_set == TimestampSet.from_wkb(self.ts_set.as_wkb()) - assert self.ts_set == TimestampSet.from_hexwkb(self.ts_set.as_hexwkb()) - - def test_copy_constructor(self): - ts_set_copy = copy(self.ts_set) - assert self.ts_set == ts_set_copy - assert self.ts_set is not ts_set_copy - - -class TestTimestampSetOutputs(TestTimestampSet): - - def test_str(self): - assert str(self.ts_set) == '{"2019-09-01 00:00:00+00", "2019-09-02 00:00:00+00", "2019-09-03 00:00:00+00"}' - - def test_repr(self): - assert repr(self.ts_set) == 'TimestampSet({"2019-09-01 00:00:00+00", "2019-09-02 00:00:00+00", "2019-09-03 00:00:00+00"})' - - def test_as_hexwkb(self): - assert self.ts_set.as_hexwkb() == '012000010300000000A01E4E713402000000F66B853402000060CD8999340200' - - -class TestTimestampConversions(TestTimestampSet): - - def test_to_periodset(self): - assert self.ts_set.to_periodset() == PeriodSet( - '{[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00], ' - '[2019-09-02 00:00:00+00, 2019-09-02 00:00:00+00], ' - '[2019-09-03 00:00:00+00, 2019-09-03 00:00:00+00]}') - - -class TestTimestampSetAccessors(TestTimestampSet): - - def test_duration(self): - assert self.ts_set.duration() == timedelta(days=2) - - def test_period(self): - assert self.ts_set.period() == Period('[2019-09-01 00:00:00+00, 2019-09-03 00:00:00+00]') - - def test_num_timestamps(self): - assert self.ts_set.num_timestamps() == 3 - - def test_start_timestamp(self): - assert self.ts_set.start_timestamp() == datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc) - - def test_end_timestamp(self): - assert self.ts_set.end_timestamp() == datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc) - - def test_timestamp_n(self): - assert self.ts_set.timestamp_n(1) == datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc) - - def test_timestamp_n_out_of_range(self): - with pytest.raises(IndexError): - self.ts_set.timestamp_n(3) - - def test_timestamps(self): - assert self.ts_set.timestamps() == [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), - datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), - datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc), - ] - - def test_hash(self): - assert hash(self.ts_set) == 527267058 - - -class TestTimestampSetPositionFunctions(TestTimestampSet): - period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') - periodset = PeriodSet( - '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') - timestamp = datetime(year=2020, month=1, day=1) - timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') - instant = TFloatInst('1.0@2020-01-01') - discrete_sequence = TFloatSeq('{1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31}') - stepwise_sequence = TFloatSeq('Interp=Step;(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') - continuous_sequence = TFloatSeq('(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') - sequence_set = TFloatSeqSet('{(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31], ' - '(1.0@2021-01-01, 3.0@2021-01-10, 10.0@2021-01-20, 0.0@2021-01-31]}') - tbox = TBox('TBox XT([0, 10),[2020-01-01, 2020-01-31])') - stbox = STBox('STBOX ZT(((1.0,2.0,3.0),(4.0,5.0,6.0)),[2001-01-01, 2001-01-02])') - - @pytest.mark.parametrize( - 'other', - [period, periodset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_adjacent(self, other): - self.timestampset.is_adjacent(other) - - @pytest.mark.parametrize( - 'other', - [timestampset, period, periodset, instant, discrete_sequence, stepwise_sequence, continuous_sequence, sequence_set, tbox, - stbox], - ids=['timestampset', 'period', 'periodset', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', - 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_contained_in(self, other): - self.timestampset.is_contained_in(other) - - @pytest.mark.parametrize( - 'other', - [timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence], - ids=['timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set'] - ) - def test_contains(self, other): - self.timestampset.contains(other) - _ = other in self.timestampset - - # - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_overlaps(self, other): - self.timestampset.overlaps(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_same(self, other): - self.periodset.is_same(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_before(self, other): - self.timestampset.is_before(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_over_or_before(self, other): - self.timestampset.is_over_or_before(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_after(self, other): - self.timestampset.is_after(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_is_over_or_after(self, other): - self.timestampset.is_over_or_after(other) - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, - continuous_sequence, tbox, stbox], - ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', - 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] - ) - def test_distance(self, other): - self.timestampset.distance(other) - - -class TestTimestampSetSetFunctions(TestTimestampSet): - timestamp = datetime(year=2020, month=1, day=1) - timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') - period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') - periodset = PeriodSet( - '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset], - ids=['period', 'periodset', 'timestamp', 'timestampset'] - ) - def test_intersection(self, other): - self.timestampset.intersection(other) - self.timestampset * other - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset], - ids=['period', 'periodset', 'timestamp', 'timestampset'] - ) - def test_union(self, other): - self.timestampset.union(other) - self.timestampset + other - - @pytest.mark.parametrize( - 'other', - [period, periodset, timestamp, timestampset], - ids=['period', 'periodset', 'timestamp', 'timestampset'] - ) - def test_minus(self, other): - self.timestampset.minus(other) - self.timestampset - other - - -class TestTimestampSetComparisons(TestTimestampSet): - timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') - other = TimestampSet('{2020-01-02 00:00:00+0, 2020-03-31 00:00:00+0}') - - def test_eq(self): - _ = self.timestampset == self.other - - def test_ne(self): - _ = self.timestampset != self.other - - def test_lt(self): - _ = self.timestampset < self.other - - def test_le(self): - _ = self.timestampset <= self.other - - def test_gt(self): - _ = self.timestampset > self.other - - def test_ge(self): - _ = self.timestampset >= self.other - - -class TestTimestampSetManipulationFunctions(TestTimestampSet): - timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-02 00:00:00+0, 2020-01-04 00:00:00+0}') - - @pytest.mark.parametrize( - 'delta,result', - [(timedelta(days=4), - [datetime(2020, 1, 5, tzinfo=timezone.utc), datetime(2020, 1, 6, tzinfo=timezone.utc), - datetime(2020, 1, 8, tzinfo=timezone.utc)]), - (timedelta(days=-4), - [datetime(2019, 12, 28, tzinfo=timezone.utc), datetime(2019, 12, 29, tzinfo=timezone.utc), - datetime(2019, 12, 31, tzinfo=timezone.utc)]), - (timedelta(hours=2), - [datetime(2020, 1, 1, 2, tzinfo=timezone.utc), datetime(2020, 1, 2, 2, tzinfo=timezone.utc), - datetime(2020, 1, 4, 2, tzinfo=timezone.utc)]), - (timedelta(hours=-2), - [datetime(2019, 12, 31, 22, tzinfo=timezone.utc), datetime(2020, 1, 1, 22, tzinfo=timezone.utc), - datetime(2020, 1, 3, 22, tzinfo=timezone.utc)]), - ], - ids=['positive days', 'negative days', 'positive hours', 'negative hours'] - ) - def test_shift(self, delta, result): - shifted = self.timestampset.shift(delta) - self.assert_timestampset_equality(shifted, result) - - @pytest.mark.parametrize( - 'delta,result', - [(timedelta(days=6), - [datetime(2020, 1, 1, tzinfo=timezone.utc), datetime(2020, 1, 3, tzinfo=timezone.utc), - datetime(2020, 1, 7, tzinfo=timezone.utc)]), - (timedelta(hours=3), - [datetime(2020, 1, 1, tzinfo=timezone.utc), datetime(2020, 1, 1, 1, tzinfo=timezone.utc), - datetime(2020, 1, 1, 3, tzinfo=timezone.utc)]), - ], - ids=['days', 'hours'] - ) - def test_tscale(self, delta, result): - scaled = self.timestampset.tscale(delta) - self.assert_timestampset_equality(scaled, result) - - def test_shift_tscale(self): - shifted_scaled = self.timestampset.shift_tscale(timedelta(days=4), timedelta(hours=3)) - self.assert_timestampset_equality(shifted_scaled, - [datetime(2020, 1, 5, tzinfo=timezone.utc), - datetime(2020, 1, 5, 1, tzinfo=timezone.utc), - datetime(2020, 1, 5, 3, tzinfo=timezone.utc)]) - - -class TestTimestampSetMiscFunctions(TestTimestampSet): - timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-02 00:00:00+0, 2020-01-04 00:00:00+0}') - - def test_hash(self): - hash(self.timestampset) +from copy import copy +from datetime import datetime, timezone, timedelta +from typing import List + +import pytest + +from pymeos import Period, PeriodSet, TimestampSet, TFloatInst, TFloatSeq, STBox, TFloatSeqSet, TBox +from tests.conftest import TestPyMEOS + + +class TestTimestampSet(TestPyMEOS): + ts_set = TimestampSet('{2019-09-01 00:00:00+0, 2019-09-02 00:00:00+0, 2019-09-03 00:00:00+0}') + + @staticmethod + def assert_timestampset_equality(ts_set: TimestampSet, + timestamps: List[datetime]): + assert ts_set.num_elements() == len(timestamps) + assert ts_set.elements() == timestamps + + +class TestTimestampSetConstructors(TestTimestampSet): + + def test_string_constructor(self): + self.assert_timestampset_equality(self.ts_set, [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) + + def test_list_constructor(self): + ts_set = TimestampSet(elements=[datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) + self.assert_timestampset_equality(ts_set, [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) + + def test_hexwkb_constructor(self): + ts_set = TimestampSet.from_hexwkb('012000010300000000A01E4E713402000000F66B853402000060CD8999340200') + self.assert_timestampset_equality(ts_set, [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) + + def test_from_as_constructor(self): + assert self.ts_set == TimestampSet(str(self.ts_set)) + assert self.ts_set == TimestampSet.from_wkb(self.ts_set.as_wkb()) + assert self.ts_set == TimestampSet.from_hexwkb(self.ts_set.as_hexwkb()) + + def test_copy_constructor(self): + ts_set_copy = copy(self.ts_set) + assert self.ts_set == ts_set_copy + assert self.ts_set is not ts_set_copy + + +class TestTimestampSetOutputs(TestTimestampSet): + + def test_str(self): + assert str(self.ts_set) == '{"2019-09-01 00:00:00+00", "2019-09-02 00:00:00+00", "2019-09-03 00:00:00+00"}' + + def test_repr(self): + assert repr(self.ts_set) == 'TimestampSet({"2019-09-01 00:00:00+00", "2019-09-02 00:00:00+00", "2019-09-03 00:00:00+00"})' + + def test_as_hexwkb(self): + assert self.ts_set.as_hexwkb() == '012000010300000000A01E4E713402000000F66B853402000060CD8999340200' + + +class TestTimestampConversions(TestTimestampSet): + + def test_to_periodset(self): + assert self.ts_set.to_periodset() == PeriodSet( + '{[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00], ' + '[2019-09-02 00:00:00+00, 2019-09-02 00:00:00+00], ' + '[2019-09-03 00:00:00+00, 2019-09-03 00:00:00+00]}') + + +class TestTimestampSetAccessors(TestTimestampSet): + + def test_duration(self): + assert self.ts_set.duration() == timedelta(days=2) + + def test_period(self): + assert self.ts_set.to_period() == Period('[2019-09-01 00:00:00+00, 2019-09-03 00:00:00+00]') + + def test_num_timestamps(self): + assert self.ts_set.num_elements() == 3 + assert len(self.ts_set) == 3 + + def test_start_timestamp(self): + assert self.ts_set.start_element() == datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc) + + def test_end_timestamp(self): + assert self.ts_set.end_element() == datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc) + + def test_timestamp_n(self): + assert self.ts_set.element_n(1) == datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc) + + def test_timestamp_n_out_of_range(self): + with pytest.raises(IndexError): + self.ts_set.element_n(3) + + def test_timestamps(self): + assert self.ts_set.elements() == [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc), + ] + + def test_hash(self): + assert hash(self.ts_set) == 527267058 + + +class TestTimestampSetPositionFunctions(TestTimestampSet): + timestamp = datetime(year=2020, month=1, day=1) + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') + + @pytest.mark.parametrize( + 'other, expected', + [(timestampset, False)], + ids=['timestampset'] + ) + def test_is_contained_in(self, other, expected): + assert self.ts_set.is_contained_in(other) == expected + + @pytest.mark.parametrize( + 'other', + [timestamp, timestampset], + ids=['timestamp', 'timestampset'] + ) + def test_contains(self, other): + self.ts_set.contains(other) + _ = other in self.timestampset + + @pytest.mark.parametrize( + 'other', + [timestampset], + ids=['timestampset'] + ) + def test_overlaps(self, other): + self.ts_set.overlaps(other) + + @pytest.mark.parametrize( + 'other', + [timestamp, timestampset], + ids=['timestamp', 'timestampset'] + ) + def test_is_before(self, other): + self.ts_set.is_before(other) + + @pytest.mark.parametrize( + 'other', + [timestamp, timestampset], + ids=['timestamp', 'timestampset'] + ) + def test_is_over_or_before(self, other): + self.ts_set.is_over_or_before(other) + + @pytest.mark.parametrize( + 'other', + [timestamp, timestampset], + ids=['timestamp', 'timestampset'] + ) + def test_is_after(self, other): + self.ts_set.is_after(other) + + @pytest.mark.parametrize( + 'other', + [timestamp, timestampset], + ids=['timestamp', 'timestampset'] + ) + def test_is_over_or_after(self, other): + self.ts_set.is_over_or_after(other) + + @pytest.mark.parametrize( + 'other', + [timestamp, timestampset], + ids=['timestamp', 'timestampset'] + ) + def test_distance(self, other): + self.ts_set.distance(other) + + +class TestTimestampSetSetFunctions(TestTimestampSet): + timestamp = datetime(year=2020, month=1, day=1) + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') + period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_intersection(self, other): + self.timestampset.intersection(other) + self.timestampset * other + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_union(self, other): + self.timestampset.union(other) + self.timestampset + other + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_minus(self, other): + self.timestampset.minus(other) + self.timestampset - other + + +class TestTimestampSetComparisons(TestTimestampSet): + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') + other = TimestampSet('{2020-01-02 00:00:00+0, 2020-03-31 00:00:00+0}') + + def test_eq(self): + _ = self.timestampset == self.other + + def test_ne(self): + _ = self.timestampset != self.other + + def test_lt(self): + _ = self.timestampset < self.other + + def test_le(self): + _ = self.timestampset <= self.other + + def test_gt(self): + _ = self.timestampset > self.other + + def test_ge(self): + _ = self.timestampset >= self.other + + +class TestTimestampSetFunctionsFunctions(TestTimestampSet): + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-02 00:00:00+0, 2020-01-04 00:00:00+0}') + + @pytest.mark.parametrize( + 'delta,result', + [(timedelta(days=4), + [datetime(2020, 1, 5, tzinfo=timezone.utc), datetime(2020, 1, 6, tzinfo=timezone.utc), + datetime(2020, 1, 8, tzinfo=timezone.utc)]), + (timedelta(days=-4), + [datetime(2019, 12, 28, tzinfo=timezone.utc), datetime(2019, 12, 29, tzinfo=timezone.utc), + datetime(2019, 12, 31, tzinfo=timezone.utc)]), + (timedelta(hours=2), + [datetime(2020, 1, 1, 2, tzinfo=timezone.utc), datetime(2020, 1, 2, 2, tzinfo=timezone.utc), + datetime(2020, 1, 4, 2, tzinfo=timezone.utc)]), + (timedelta(hours=-2), + [datetime(2019, 12, 31, 22, tzinfo=timezone.utc), datetime(2020, 1, 1, 22, tzinfo=timezone.utc), + datetime(2020, 1, 3, 22, tzinfo=timezone.utc)]), + ], + ids=['positive days', 'negative days', 'positive hours', 'negative hours'] + ) + def test_shift(self, delta, result): + shifted = self.timestampset.shift(delta) + self.assert_timestampset_equality(shifted, result) + + @pytest.mark.parametrize( + 'delta,result', + [(timedelta(days=6), + [datetime(2020, 1, 1, tzinfo=timezone.utc), datetime(2020, 1, 3, tzinfo=timezone.utc), + datetime(2020, 1, 7, tzinfo=timezone.utc)]), + (timedelta(hours=3), + [datetime(2020, 1, 1, tzinfo=timezone.utc), datetime(2020, 1, 1, 1, tzinfo=timezone.utc), + datetime(2020, 1, 1, 3, tzinfo=timezone.utc)]), + ], + ids=['days', 'hours'] + ) + def test_scale(self, delta, result): + scaled = self.timestampset.scale(delta) + self.assert_timestampset_equality(scaled, result) + + def test_shift_scale(self): + shifted_scaled = self.timestampset.shift_scale(timedelta(days=4), timedelta(hours=3)) + self.assert_timestampset_equality(shifted_scaled, + [datetime(2020, 1, 5, tzinfo=timezone.utc), + datetime(2020, 1, 5, 1, tzinfo=timezone.utc), + datetime(2020, 1, 5, 3, tzinfo=timezone.utc)]) + + +class TestTimestampSetMiscFunctions(TestTimestampSet): + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-02 00:00:00+0, 2020-01-04 00:00:00+0}') + + def test_hash(self): + hash(self.timestampset) diff --git a/pymeos/tests/main/tbool_test.py b/pymeos/tests/main/tbool_test.py index dbff67da..d1418127 100644 --- a/pymeos/tests/main/tbool_test.py +++ b/pymeos/tests/main/tbool_test.py @@ -144,7 +144,7 @@ def test_instant_list_sequence_constructor(self, list, interpolation, normalize, ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_from_as_constructor(self, temporal): - # assert temporal == temporal.from_wkt(temporal.as_wkt()) + assert temporal == temporal.__class__(str(temporal)) assert temporal == temporal.from_wkb(temporal.as_wkb()) assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) assert temporal == temporal.from_mfjson(temporal.as_mfjson()) @@ -752,40 +752,43 @@ def test_to_instant(self, temporal, expected): assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TBoolInst('True@2019-09-01'), + (TBoolInst('True@2019-09-01'), TInterpolation.STEPWISE, TBoolSeq('[True@2019-09-01]')), - (TBoolSeq('{True@2019-09-01, False@2019-09-02}'), + (TBoolSeq('{True@2019-09-01, False@2019-09-02}'), TInterpolation.DISCRETE, TBoolSeq('{True@2019-09-01, False@2019-09-02}')), - (TBoolSeq('[True@2019-09-01, False@2019-09-02]'), + (TBoolSeq('[True@2019-09-01, False@2019-09-02]'), TInterpolation.STEPWISE, TBoolSeq('[True@2019-09-01, False@2019-09-02]')), - (TBoolSeqSet('{[True@2019-09-01, False@2019-09-02]}'), + (TBoolSeqSet('{[True@2019-09-01, False@2019-09-02]}'), TInterpolation.STEPWISE, TBoolSeq('[True@2019-09-01, False@2019-09-02]')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequence(self, temporal, expected): - temp = temporal.to_sequence() + def test_to_sequence(self, temporal, interpolation, expected): + temp = temporal.to_sequence(interpolation) assert isinstance(temp, TBoolSeq) assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TBoolInst('True@2019-09-01'), + (TBoolInst('True@2019-09-01'), TInterpolation.STEPWISE, TBoolSeqSet('{[True@2019-09-01]}')), (TBoolSeq('{True@2019-09-01, False@2019-09-02}'), + TInterpolation.STEPWISE, TBoolSeqSet('{[True@2019-09-01], [False@2019-09-02]}')), (TBoolSeq('[True@2019-09-01, False@2019-09-02]'), + TInterpolation.STEPWISE, TBoolSeqSet('{[True@2019-09-01, False@2019-09-02]}')), (TBoolSeqSet('{[True@2019-09-01, False@2019-09-02]}'), + TInterpolation.STEPWISE, TBoolSeqSet('{[True@2019-09-01, False@2019-09-02]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequenceset(self, temporal, expected): - temp = temporal.to_sequenceset() + def test_to_sequenceset(self, temporal, interpolation, expected): + temp = temporal.to_sequenceset(interpolation) assert isinstance(temp, TBoolSeqSet) assert temp == expected @@ -823,8 +826,8 @@ def test_to_sequenceset(self, temporal, expected): 'Sequence Set positive days', 'Sequence Set negative days', 'Sequence Set positive hours', 'Sequence Set negative hours'] ) - def test_shift(self, tbool, delta, expected): - assert tbool.shift(delta) == expected + def test_shift_time(self, tbool, delta, expected): + assert tbool.shift_time(delta) == expected @pytest.mark.parametrize( 'tbool, delta, expected', @@ -845,11 +848,11 @@ def test_shift(self, tbool, delta, expected): 'Sequence positive days', 'Sequence positive hours', 'Sequence Set positive days', 'Sequence Set positive hours'] ) - def test_scale(self, tbool, delta, expected): - assert tbool.tscale(delta) == expected + def test_scale_time(self, tbool, delta, expected): + assert tbool.scale_time(delta) == expected - def test_shift_tscale(self): - assert self.tbss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ + def test_shift_scale_time(self): + assert self.tbss.shift_scale_time(timedelta(days=4), timedelta(hours=2)) == \ TBoolSeqSet('{[True@2019-09-05 00:00:00, False@2019-09-05 00:30:00],' '[True@2019-09-05 01:00:00, True@2019-09-05 02:00:00]}') @@ -874,7 +877,7 @@ def test_shift_tscale(self): 'Sequence Set days', 'Sequence Set hours'] ) def test_temporal_sample(self, tint, delta, expected): - assert tint.temporal_sample(delta) == expected + assert tint.temporal_sample(delta, '2019-09-01') == expected class TestTBoolModifications(TestTBool): @@ -980,8 +983,8 @@ class TestTBoolManipulationFunctions(TestTBool): ids=['Instant positive', 'Discrete Sequence positive', 'Sequence positive', 'SequenceSet positive', 'Instant negative', 'Discrete Sequence negative', 'Sequence negative', 'SequenceSet negative'], ) - def test_shift(self, temporal, shift, expected): - assert temporal.shift(shift) == expected + def test_shift_time(self, temporal, shift, expected): + assert temporal.shift_time(shift) == expected @pytest.mark.parametrize( 'temporal, scale, expected', @@ -994,8 +997,8 @@ def test_shift(self, temporal, shift, expected): ], ids=['Instant positive', 'Discrete Sequence positive', 'Sequence positive', 'SequenceSet positive'], ) - def test_tscale(self, temporal, scale, expected): - assert temporal.tscale(scale) == expected + def test_scale_time(self, temporal, scale, expected): + assert temporal.scale_time(scale) == expected @pytest.mark.parametrize( 'temporal, shift, scale, expected', @@ -1014,8 +1017,8 @@ def test_tscale(self, temporal, scale, expected): ids=['Instant positive', 'Discrete Sequence positive', 'Sequence positive', 'SequenceSet positive', 'Instant negative', 'Discrete Sequence negative', 'Sequence negative', 'SequenceSet negative'], ) - def test_shift_tscale(self, temporal, shift, scale, expected): - assert temporal.shift_tscale(shift, scale) == expected + def test_shift_scale_time(self, temporal, shift, scale, expected): + assert temporal.shift_scale_time(shift, scale) == expected class TestTBoolRestrictors(TestTBool): diff --git a/pymeos/tests/main/tfloat_test.py b/pymeos/tests/main/tfloat_test.py index b1417bb3..16dd1c7c 100644 --- a/pymeos/tests/main/tfloat_test.py +++ b/pymeos/tests/main/tfloat_test.py @@ -1,15 +1,14 @@ from copy import copy -from operator import not_ from datetime import datetime, timezone, timedelta -from spans.types import intrange, floatrange +from operator import not_ import pytest -from pymeos import TBool, TBoolInst, TBoolSeq, TBoolSeqSet, \ +from pymeos import TBoolInst, TBoolSeq, TBoolSeqSet, \ TFloat, TFloatInst, TFloatSeq, TFloatSeqSet, \ TInt, TIntInst, TIntSeq, TIntSeqSet, \ - TInterpolation, TBox, TimestampSet, Period, PeriodSet - + TInterpolation, TBox, TimestampSet, Period, PeriodSet, \ + FloatSpan, FloatSpanSet, FloatSet from tests.conftest import TestPyMEOS @@ -66,7 +65,7 @@ def test_from_base_time_constructor(self, source, type, interpolation): '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00]'), ('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}', TFloatSeqSet, TInterpolation.LINEAR, '{[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00], ' - '[1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]}'), + '[1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]}'), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -89,7 +88,7 @@ def test_string_constructor(self, source, type, interpolation, expected): ids=['Sequence', 'SequenceSet'] ) def test_string_constructor_normalization(self, source, type, expected): - tf = type(source, normalize=1) + tf = type(source, normalize=True) assert isinstance(tf, type) assert str(tf) == expected @@ -151,6 +150,7 @@ def test_instant_list_sequence_constructor(self, list, interpolation, normalize, 'Stepwise Sequence', 'Stepwise SequenceSet'] ) def test_from_as_constructor(self, temporal): + assert temporal == temporal.__class__(str(temporal)) assert temporal == temporal.from_wkb(temporal.as_wkb()) assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) assert temporal == temporal.from_mfjson(temporal.as_mfjson()) @@ -185,7 +185,7 @@ class TestTFloatOutputs(TestTFloat): '[1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]}'), (tfsts, 'Interp=Step;[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00]'), (tfstss, 'Interp=Step;{[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00], ' - '[1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]}') + '[1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]}') ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet', 'Stepwise Sequence', 'Stepwise SequenceSet'] @@ -228,7 +228,7 @@ def test_as_wkt(self, temporal, expected): (tfds, '011B00060200000003000000000000F83F00A01E4E7134020000000000000004400000F66B85340200'), (tfs, '011B000E0200000003000000000000F83F00A01E4E7134020000000000000004400000F66B85340200'), (tfss, '011B000F020000000200000003000000000000F83F00A01E4E7134020000000000000004400000F66B85340200' - '0200000003000000000000F83F0060CD8999340200000000000000F83F00207CC5C1340200') + '0200000003000000000000F83F0060CD8999340200000000000000F83F00207CC5C1340200') ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -440,28 +440,28 @@ def test_values(self, temporal, expected): @pytest.mark.parametrize( 'temporal, expected', [ - (tfi, floatrange(1.5, 1.5, True, True)), - (tfds, floatrange(1.5, 2.5, True, True)), - (tfs, floatrange(1.5, 2.5, True, True)), - (tfss, floatrange(1.5, 2.5, True, True)), + (tfi, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True)), + (tfds, FloatSpan(lower=1.5, upper=2.5, lower_inc=True, upper_inc=True)), + (tfs, FloatSpan(lower=1.5, upper=2.5, lower_inc=True, upper_inc=True)), + (tfss, FloatSpan(lower=1.5, upper=2.5, lower_inc=True, upper_inc=True)), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_value_range(self, temporal, expected): - assert temporal.value_range() == expected + def test_value_span(self, temporal, expected): + assert temporal.value_span() == expected - @pytest.mark.parametrize( - 'temporal, expected', - [ - (tfi, [floatrange(1.5, 1.5, True, True)]), - (tfds, [floatrange(1.5, 1.5, True, True),floatrange(2.5, 2.5, True, True)]), - (tfs, [floatrange(1.5, 2.5, True, True)]), - (tfss, [floatrange(1.5, 2.5, True, True)]), - ], - ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] - ) - def test_value_ranges(self, temporal, expected): - assert temporal.value_ranges() == expected + # @pytest.mark.parametrize( + # 'temporal, expected', + # [ + # (tfi, FloatSpanSet('{[1.5, 1.5]}')), + # (tfds, FloatSpanSet('{[1.5, 1.5], [2.5, 2.5]}')), + # (tfs, FloatSpanSet('{[1.5, 1.5]}')), + # (tfss, FloatSpanSet('{[1.5, 1.5]}')), + # ], + # ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + # ) + # def test_value_spans(self, temporal, expected): + # assert temporal.value_spans() == expected @pytest.mark.parametrize( 'temporal, expected', @@ -760,12 +760,12 @@ def test_timestamps(self, temporal, expected): (tfds, [TFloatSeq('[1.5@2019-09-01]'), TFloatSeq('[2.5@2019-09-02]')]), (tfs, [TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]')]), (tfss, [TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]'), - TFloatSeq('[1.5@2019-09-03, 1.5@2019-09-05]')]), + TFloatSeq('[1.5@2019-09-03, 1.5@2019-09-05]')]), (tfsts, [TFloatSeq('Interp=Step;[1.5@2019-09-01, 1.5@2019-09-02)'), - TFloatSeq('Interp=Step;[2.5@2019-09-02]')]), + TFloatSeq('Interp=Step;[2.5@2019-09-02]')]), (tfstss, [TFloatSeq('Interp=Step;[1.5@2019-09-01, 1.5@2019-09-02)'), - TFloatSeq('Interp=Step;[2.5@2019-09-02]'), - TFloatSeq('Interp=Step;[1.5@2019-09-03, 1.5@2019-09-05]')]), + TFloatSeq('Interp=Step;[2.5@2019-09-02]'), + TFloatSeq('Interp=Step;[1.5@2019-09-03, 1.5@2019-09-05]')]), ], ids=['Discrete Sequence', 'Sequence', 'SequenceSet', 'Stepwise Sequence', 'Stepwise SequenceSet'] ) @@ -806,15 +806,15 @@ def test_lower_upper_inc(self, temporal, expected): assert temporal.upper_inc() == expected def test_sequenceset_sequence_functions(self): - tfss1 =TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02],' - '[1@2019-09-03, 1@2019-09-05], [3@2019-09-06]}') + tfss1 = TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02],' + '[1@2019-09-03, 1@2019-09-05], [3@2019-09-06]}') assert tfss1.num_sequences() == 3 assert tfss1.start_sequence() == TFloatSeq('[1@2019-09-01, 2@2019-09-02]') assert tfss1.end_sequence() == TFloatSeq('[3@2019-09-06]') assert tfss1.sequence_n(1) == TFloatSeq('[1@2019-09-03, 1@2019-09-05]') assert tfss1.sequences() == [TFloatSeq('[1@2019-09-01, 2@2019-09-02]'), - TFloatSeq('[1@2019-09-03, 1@2019-09-05]'), - TFloatSeq('[3@2019-09-06]')] + TFloatSeq('[1@2019-09-03, 1@2019-09-05]'), + TFloatSeq('[3@2019-09-06]')] @pytest.mark.parametrize( 'temporal, expected', @@ -855,10 +855,10 @@ class TestTFloatTransformations(TestTFloat): tfss_d = TFloatSeqSet('{[1.5@2019-09-01],[2.5@2019-09-03]}') tfs_s = TFloatSeq('[1.5@2019-09-01, 1.5@2019-09-02]') tfss_s = TFloatSeqSet('{[1.5@2019-09-01, 1.5@2019-09-02],' - '[2.5@2019-09-03, 2.5@2019-09-05]}') + '[2.5@2019-09-03, 2.5@2019-09-05]}') tfs_l = TFloatSeq('Interp=Step;[1.5@2019-09-01, 2.5@2019-09-02]') tfss_l = TFloatSeqSet('Interp=Step;{[1.5@2019-09-01, 2.5@2019-09-02],' - '[1.5@2019-09-03, 1.5@2019-09-05]}') + '[1.5@2019-09-03, 1.5@2019-09-05]}') @pytest.mark.parametrize( 'temporal, expected', @@ -876,40 +876,46 @@ def test_to_instant(self, temporal, expected): assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TFloatInst('1.5@2019-09-01'), - TFloatSeq('[1.5@2019-09-01]')), + (TFloatInst('1.5@2019-09-01'), TInterpolation.LINEAR, + TFloatSeq('[1.5@2019-09-01]')), (TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}'), - TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}')), + TInterpolation.DISCRETE, + TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}')), (TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]'), - TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]')), + TInterpolation.LINEAR, + TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]')), (TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}'), - TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]')), + TInterpolation.LINEAR, + TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequence(self, temporal, expected): - temp = temporal.to_sequence() + def test_to_sequence(self, temporal, interpolation, expected): + temp = temporal.to_sequence(interpolation) assert isinstance(temp, TFloatSeq) assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TFloatInst('1.5@2019-09-01'), - TFloatSeqSet('{[1.5@2019-09-01]}')), + (TFloatInst('1.5@2019-09-01'), TInterpolation.LINEAR, + TFloatSeqSet('{[1.5@2019-09-01]}')), (TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}'), - TFloatSeqSet('{[1.5@2019-09-01], [2.5@2019-09-02]}')), + TInterpolation.LINEAR, + TFloatSeqSet('{[1.5@2019-09-01], [2.5@2019-09-02]}')), (TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]'), - TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}')), + TInterpolation.LINEAR, + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}')), (TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}'), - TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}')), + TInterpolation.LINEAR, + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequenceset(self, temporal, expected): - temp = temporal.to_sequenceset() + def test_to_sequenceset(self, temporal, interpolation, expected): + temp = temporal.to_sequenceset(interpolation) assert isinstance(temp, TFloatSeqSet) assert temp == expected @@ -917,32 +923,32 @@ def test_to_sequenceset(self, temporal, expected): 'temporal, interpolation, expected', [ (tfi, TInterpolation.DISCRETE, - TFloatSeq('{1.5@2019-09-01}')), + TFloatSeq('{1.5@2019-09-01}')), (tfds, TInterpolation.DISCRETE, tfds), (tfs_d, TInterpolation.DISCRETE, - TFloatSeq('{1.5@2019-09-01}')), + TFloatSeq('{1.5@2019-09-01}')), (tfss_d, TInterpolation.DISCRETE, - TFloatSeq('{1.5@2019-09-01,2.5@2019-09-03}')), + TFloatSeq('{1.5@2019-09-01,2.5@2019-09-03}')), - (tfi, TInterpolation.STEPWISE, - TFloatSeq('Interp=Step;[1.5@2019-09-01]')), - (tfds, TInterpolation.STEPWISE, - TFloatSeqSet('Interp=Step;{[1.5@2019-09-01], [2.5@2019-09-02]}')), + (tfi, TInterpolation.STEPWISE, + TFloatSeq('Interp=Step;[1.5@2019-09-01]')), + (tfds, TInterpolation.STEPWISE, + TFloatSeqSet('Interp=Step;{[1.5@2019-09-01], [2.5@2019-09-02]}')), (tfs_s, TInterpolation.STEPWISE, - TFloatSeq('Interp=Step;[1.5@2019-09-01, 1.5@2019-09-02]')), + TFloatSeq('Interp=Step;[1.5@2019-09-01, 1.5@2019-09-02]')), (tfss_s, TInterpolation.STEPWISE, - TFloatSeqSet('Interp=Step;{[1.5@2019-09-01, 1.5@2019-09-02],' - '[2.5@2019-09-03, 2.5@2019-09-05]}')), + TFloatSeqSet('Interp=Step;{[1.5@2019-09-01, 1.5@2019-09-02],' + '[2.5@2019-09-03, 2.5@2019-09-05]}')), - (tfi, TInterpolation.LINEAR, - TFloatSeq('[1.5@2019-09-01]')), - (tfds, TInterpolation.LINEAR, - TFloatSeqSet('{[1.5@2019-09-01], [2.5@2019-09-02]}')), + (tfi, TInterpolation.LINEAR, + TFloatSeq('[1.5@2019-09-01]')), + (tfds, TInterpolation.LINEAR, + TFloatSeqSet('{[1.5@2019-09-01], [2.5@2019-09-02]}')), (tfs_l, TInterpolation.LINEAR, - TFloatSeqSet('{[1.5@2019-09-01, 1.5@2019-09-02), [2.5@2019-09-02]}')), + TFloatSeqSet('{[1.5@2019-09-01, 1.5@2019-09-02), [2.5@2019-09-02]}')), (tfss_l, TInterpolation.LINEAR, - TFloatSeqSet('{[1.5@2019-09-01, 1.5@2019-09-02),' - '[2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + TFloatSeqSet('{[1.5@2019-09-01, 1.5@2019-09-02),' + '[2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), ], ids=['Instant to discrete', 'Discrete Sequence to discrete', 'Sequence to discrete', 'SequenceSet to discrete', 'Instant to step', 'Discrete Sequence to step', 'Sequence to step', 'SequenceSet to step', @@ -951,12 +957,69 @@ def test_to_sequenceset(self, temporal, expected): def test_set_interpolation(self, temporal, interpolation, expected): assert temporal.set_interpolation(interpolation) == expected + @pytest.mark.parametrize( + 'tfloat, delta, expected', + [(tfi, 2, TFloatInst('3.5@2019-09-01')), + (tfi, -2, TFloatInst('-0.5@2019-09-01')), + (tfds, 2, TFloatSeq('{3.5@2019-09-01, 4.5@2019-09-02}')), + (tfds, -2, TFloatSeq('{-0.5@2019-09-01, 0.5@2019-09-02}')), + (tfs, 2, TFloatSeq('[3.5@2019-09-01, 4.5@2019-09-02]')), + (tfs, -2, TFloatSeq('[-0.5@2019-09-01, 0.5@2019-09-02]')), + (tfss, 2, TFloatSeqSet('{[3.5@2019-09-01, 4.5@2019-09-02],' + '[3.5@2019-09-03, 3.5@2019-09-05]}')), + (tfss, -2, TFloatSeqSet('{[-0.5@2019-09-01, 0.5@2019-09-02],' + '[-0.5@2019-09-03, -0.5@2019-09-05]}')), + ], + ids=['Instant positive', 'Instant negative', + 'Discrete Sequence positive', 'Discrete Sequence negative', + 'Sequence positive', 'Sequence negative', + 'Sequence Set positive', 'Sequence Set negative', + ] + ) + def test_shift_value(self, tfloat, delta, expected): + assert tfloat.shift_value(delta) == expected + + @pytest.mark.parametrize( + 'tfloat, width, expected', + [(tfi, 4, TFloatInst('1.5@2019-09-01')), + (tfds, 4, TFloatSeq('{1.5@2019-09-01, 5.5@2019-09-02}')), + (tfs, 4, TFloatSeq('[1.5@2019-09-01, 5.5@2019-09-02]')), + (tfss, 4, TFloatSeqSet('{[1.5@2019-09-01, 5.5@2019-09-02],' + '[1.5@2019-09-03, 1.5@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set', ] + ) + def test_scale_value(self, tfloat, width, expected): + assert tfloat.scale_value(width) == expected + + @pytest.mark.parametrize( + 'tfloat, delta, width, expected', + [(tfi, 2, 3, TFloatInst('3.5@2019-09-01')), + (tfi, -2, 3, TFloatInst('-0.5@2019-09-01')), + (tfds, 2, 3, TFloatSeq('{3.5@2019-09-01, 6.5@2019-09-02}')), + (tfds, -2, 3, TFloatSeq('{-0.5@2019-09-01, 2.5@2019-09-02}')), + (tfs, 2, 3, TFloatSeq('[3.5@2019-09-01, 6.5@2019-09-02]')), + (tfs, -2, 3, TFloatSeq('[-0.5@2019-09-01, 2.5@2019-09-02]')), + (tfss, 2, 3, TFloatSeqSet('{[3.5@2019-09-01, 6.5@2019-09-02],' + '[3.5@2019-09-03, 3.5@2019-09-05]}')), + (tfss, -2, 3, TFloatSeqSet('{[-0.5@2019-09-01, 2.5@2019-09-02],' + '[-0.5@2019-09-03, -0.5@2019-09-05]}')), + ], + ids=['Instant positive', 'Instant negative', + 'Discrete Sequence positive', 'Discrete Sequence negative', + 'Sequence positive', 'Sequence negative', + 'Sequence Set positive', 'Sequence Set negative', + ] + ) + def test_shift_scale_value(self, tfloat, delta, width, expected): + assert tfloat.shift_scale_value(delta, width) == expected + @pytest.mark.parametrize( 'tfloat, delta, expected', [(tfi, timedelta(days=4), TFloatInst('1.5@2019-09-05')), (tfi, timedelta(days=-4), TFloatInst('1.5@2019-08-28')), (tfi, timedelta(hours=2), TFloatInst('1.5@2019-09-01 02:00:00')), - (tfi, timedelta(hours=-2), TFloatInst('1.5@2019-08-31 22:00:00')), + (tfi, timedelta(hours=-2), TFloatInst('1.5@2019-08-31 22:00:00')), (tfds, timedelta(days=4), TFloatSeq('{1.5@2019-09-05, 2.5@2019-09-06}')), (tfds, timedelta(days=-4), TFloatSeq('{1.5@2019-08-28, 2.5@2019-08-29}')), (tfds, timedelta(hours=2), TFloatSeq('{1.5@2019-09-01 02:00:00, 2.5@2019-09-02 02:00:00}')), @@ -966,27 +1029,27 @@ def test_set_interpolation(self, temporal, interpolation, expected): (tfs, timedelta(hours=2), TFloatSeq('[1.5@2019-09-01 02:00:00, 2.5@2019-09-02 02:00:00]')), (tfs, timedelta(hours=-2), TFloatSeq('[1.5@2019-08-31 22:00:00, 2.5@2019-09-01 22:00:00]')), (tfss, timedelta(days=4), - TFloatSeqSet('{[1.5@2019-09-05, 2.5@2019-09-06],[1.5@2019-09-07, 1.5@2019-09-09]}')), + TFloatSeqSet('{[1.5@2019-09-05, 2.5@2019-09-06],[1.5@2019-09-07, 1.5@2019-09-09]}')), (tfss, timedelta(days=-4), - TFloatSeqSet('{[1.5@2019-08-28, 2.5@2019-08-29],[1.5@2019-08-30, 1.5@2019-09-01]}')), + TFloatSeqSet('{[1.5@2019-08-28, 2.5@2019-08-29],[1.5@2019-08-30, 1.5@2019-09-01]}')), (tfss, timedelta(hours=2), - TFloatSeqSet('{[1.5@2019-09-01 02:00:00, 2.5@2019-09-02 02:00:00],' - '[1.5@2019-09-03 02:00:00, 1.5@2019-09-05 02:00:00]}')), + TFloatSeqSet('{[1.5@2019-09-01 02:00:00, 2.5@2019-09-02 02:00:00],' + '[1.5@2019-09-03 02:00:00, 1.5@2019-09-05 02:00:00]}')), (tfss, timedelta(hours=-2), - TFloatSeqSet('{[1.5@2019-08-31 22:00:00, 2.5@2019-09-01 22:00:00],' - '[1.5@2019-09-02 22:00:00, 1.5@2019-09-04 22:00:00]}')), + TFloatSeqSet('{[1.5@2019-08-31 22:00:00, 2.5@2019-09-01 22:00:00],' + '[1.5@2019-09-02 22:00:00, 1.5@2019-09-04 22:00:00]}')), ], ids=['Instant positive days', 'Instant negative days', 'Instant positive hours', 'Instant negative hours', - 'Discrete Sequence positive days', 'Discrete Sequence negative days', + 'Discrete Sequence positive days', 'Discrete Sequence negative days', 'Discrete Sequence positive hours', 'Discrete Sequence negative hours', - 'Sequence positive days', 'Sequence negative days', + 'Sequence positive days', 'Sequence negative days', 'Sequence positive hours', 'Sequence negative hours', - 'Sequence Set positive days', 'Sequence Set negative days', + 'Sequence Set positive days', 'Sequence Set negative days', 'Sequence Set positive hours', 'Sequence Set negative hours'] ) - def test_shift(self, tfloat, delta, expected): - assert tfloat.shift(delta) == expected + def test_shift_time(self, tfloat, delta, expected): + assert tfloat.shift_time(delta) == expected @pytest.mark.parametrize( 'tfloat, delta, expected', @@ -997,23 +1060,23 @@ def test_shift(self, tfloat, delta, expected): (tfs, timedelta(days=4), TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-05]')), (tfs, timedelta(hours=2), TFloatSeq('[1.5@2019-09-01 00:00:00, 2.5@2019-09-01 02:00:00]')), (tfss, timedelta(days=4), - TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), (tfss, timedelta(hours=2), - TFloatSeqSet('{[1.5@2019-09-01 00:00:00, 2.5@2019-09-01 00:30:00],' - '[1.5@2019-09-01 01:00:00, 1.5@2019-09-01 02:00:00]}')), - ], + TFloatSeqSet('{[1.5@2019-09-01 00:00:00, 2.5@2019-09-01 00:30:00],' + '[1.5@2019-09-01 01:00:00, 1.5@2019-09-01 02:00:00]}')), + ], ids=['Instant positive days', 'Instant positive hours', 'Discrete Sequence positive days', 'Discrete Sequence positive hours', 'Sequence positive days', 'Sequence positive hours', 'Sequence Set positive days', 'Sequence Set positive hours'] ) - def test_scale(self, tfloat, delta, expected): - assert tfloat.tscale(delta) == expected + def test_scale_time(self, tfloat, delta, expected): + assert tfloat.scale_time(delta) == expected - def test_shift_tscale(self): - assert self.tfss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ - TFloatSeqSet('{[1.5@2019-09-05 00:00:00, 2.5@2019-09-05 00:30:00],' - '[1.5@2019-09-05 01:00:00, 1.5@2019-09-05 02:00:00]}') + def test_shift_scale_time(self): + assert self.tfss.shift_scale_time(timedelta(days=4), timedelta(hours=2)) == \ + TFloatSeqSet('{[1.5@2019-09-05 00:00:00, 2.5@2019-09-05 00:30:00],' + '[1.5@2019-09-05 01:00:00, 1.5@2019-09-05 02:00:00]}') @pytest.mark.parametrize( 'tfloat, delta, expected', @@ -1024,11 +1087,11 @@ def test_shift_tscale(self): (tfs, timedelta(days=4), TFloatSeq('{1.5@2019-09-01}')), (tfs, timedelta(hours=12), TFloatSeq('{1.5@2019-09-01, 2@2019-09-01 12:00:00, 2.5@2019-09-02}')), (tfss, timedelta(days=4), - TFloatSeq('{1.5@2019-09-01,1.5@2019-09-05}')), + TFloatSeq('{1.5@2019-09-01,1.5@2019-09-05}')), (tfss, timedelta(hours=12), - TFloatSeq('{1.5@2019-09-01, 2@2019-09-01 12:00:00, 2.5@2019-09-02,' - '1.5@2019-09-03, 1.5@2019-09-03 12:00:00, 1.5@2019-09-04, ' - '1.5@2019-09-04 12:00:00, 1.5@2019-09-05}')), + TFloatSeq('{1.5@2019-09-01, 2@2019-09-01 12:00:00, 2.5@2019-09-02,' + '1.5@2019-09-03, 1.5@2019-09-03 12:00:00, 1.5@2019-09-04, ' + '1.5@2019-09-04 12:00:00, 1.5@2019-09-05}')), ], ids=['Instant days', 'Instant hours', 'Discrete Sequence days', 'Discrete Sequence hours', @@ -1036,31 +1099,29 @@ def test_shift_tscale(self): 'Sequence Set days', 'Sequence Set hours'] ) def test_temporal_sample(self, tfloat, delta, expected): - assert tfloat.temporal_sample(delta) == expected + assert tfloat.temporal_sample(delta, '2019-09-01') == expected - # This function should be corrected in MEOS - # @pytest.mark.parametrize( - # 'tfloat, delta, expected', - # [(tfi, timedelta(days=4), TFloatInst('1.5@2019-09-01')), - # (tfi, timedelta(hours=12), TFloatInst('1.5@2019-09-01')), - # (tfds, timedelta(days=4), TFloatSeq('{2@2019-09-01}')), - # (tfds, timedelta(hours=12), TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}')), - # (tfs, timedelta(days=4), TFloatSeq('{2@2019-09-01}')), - # (tfs, timedelta(hours=12), TFloatSeq('{1.5@2019-09-01, 2@2019-09-01 12:00:00, 2.5@2019-09-02}')), - # (tfss, timedelta(days=4), - # TFloatSeq('{1.5@2019-09-01,1.5@2019-09-05}')), - # (tfss, timedelta(hours=12), - # TFloatSeq('{1.5@2019-09-01, 2@2019-09-01 12:00:00, 2.5@2019-09-02,' - # '1.5@2019-09-03, 1.5@2019-09-03 12:00:00, 1.5@2019-09-04, ' - # '1.5@2019-09-04 12:00:00, 1.5@2019-09-05}')), - # ], - # ids=['Instant days', 'Instant hours', - # 'Discrete Sequence days', 'Discrete Sequence hours', - # 'Sequence days', 'Sequence hours', - # 'Sequence Set days', 'Sequence Set hours'] - # ) - # def test_temporal_precision(self, tfloat, delta, expected): - # assert tfloat.temporal_precision(delta) == expected + @pytest.mark.parametrize( + 'tfloat, delta, expected', + [(tfi, timedelta(days=4), TFloatInst('1.5@2019-08-31')), + (tfi, timedelta(hours=12), TFloatInst('1.5@2019-09-01')), + (tfds, timedelta(days=4), TFloatSeq('{2@2019-08-31}')), + (tfds, timedelta(hours=12), TFloatSeq('{1.5@2019-09-01, 2@2019-09-01 12:00:00+00, 2@2019-09-02}')), + (tfs, timedelta(days=4), TFloatSeq('{[2@2019-08-31]}')), + (tfs, timedelta(hours=12), TFloatSeq('{[1.75@2019-09-01, 2.25@2019-09-01 12:00:00+00, 2.5@2019-09-02]}')), + (tfss, timedelta(days=4), + TFloatSeq('{[1.75@2019-08-31, 1.5@2019-09-04]}')), + (tfss, timedelta(hours=12), + TFloatSeq('{[1.75@2019-09-01, 2.25@2019-09-01 12:00:00, 2.5@2019-09-02],' + '[1.5@2019-09-03, 1.5@2019-09-05]}')), + ], + ids=['Instant days', 'Instant hours', + 'Discrete Sequence days', 'Discrete Sequence hours', + 'Sequence days', 'Sequence hours', + 'Sequence Set days', 'Sequence Set hours'] + ) + def test_temporal_precision(self, tfloat, delta, expected): + assert tfloat.temporal_precision(delta) == expected @pytest.mark.parametrize( 'tfloat, delta, expected', @@ -1068,33 +1129,33 @@ def test_temporal_sample(self, tfloat, delta, expected): (tfs, timedelta(hours=12), None), (tfss, timedelta(days=4), None), (tfss, timedelta(hours=12), - TFloatSeq('[1.5@2019-09-03, 1.5@2019-09-05]')), + TFloatSeq('[1.5@2019-09-03, 1.5@2019-09-05]')), ], ids=['Sequence days', 'Sequence hours', 'Sequence Set days', 'Sequence Set hours'] ) def test_stops(self, tfloat, delta, expected): - assert tfloat.stops(0.0, delta) == expected + assert tfloat.stops(0.1, delta) == expected @pytest.mark.parametrize( 'temporal, expected', [ - (TFloatInst('1.123456789@2019-09-01'), - TFloatInst('1.12@2019-09-01')), + (TFloatInst('1.123456789@2019-09-01'), + TFloatInst('1.12@2019-09-01')), (TFloatSeq('{1.123456789@2019-09-01,' - '2.123456789@2019-09-02}'), - TFloatSeq('{1.12@2019-09-01, 2.12@2019-09-02}')), - (TFloatSeq('[1.123456789@2019-09-01, 2.123456789@2019-09-02]'), - TFloatSeq('[1.12@2019-09-01, 2.12@2019-09-02]')), - (TFloatSeqSet('{[1.123456789@2019-09-01, 2.123456789@2019-09-02],' - '[1.123456789@2019-09-03, 1.123456789@2019-09-05]}'), - TFloatSeq('{[1.12@2019-09-01, 2.12@2019-09-02],' - '[1.12@2019-09-03, 1.12@2019-09-05]}')), + '2.123456789@2019-09-02}'), + TFloatSeq('{1.12@2019-09-01, 2.12@2019-09-02}')), + (TFloatSeq('[1.123456789@2019-09-01, 2.123456789@2019-09-02]'), + TFloatSeq('[1.12@2019-09-01, 2.12@2019-09-02]')), + (TFloatSeqSet('{[1.123456789@2019-09-01, 2.123456789@2019-09-02],' + '[1.123456789@2019-09-03, 1.123456789@2019-09-05]}'), + TFloatSeq('{[1.12@2019-09-01, 2.12@2019-09-02],' + '[1.12@2019-09-03, 1.12@2019-09-05]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_round(self, temporal, expected): - assert temporal.round(maxdd=2) + assert temporal.round(max_decimals=2) class TestTFloatModifications(TestTFloat): @@ -1110,7 +1171,7 @@ class TestTFloatModifications(TestTFloat): (tfds, TFloatSeq('{1.5@2019-09-03}'), TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02, 1.5@2019-09-03}')), (tfs, TFloatSeq('[1.5@2019-09-03]'), TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02, 1.5@2019-09-03]}')), (tfss, TFloatSeq('[1.5@2019-09-06]'), - TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05],[1.5@2019-09-06]}')), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05],[1.5@2019-09-06]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1122,10 +1183,10 @@ def test_insert(self, temporal, sequence, expected): [ (tfi, TFloatInst('2.5@2019-09-01'), TFloatInst('2.5@2019-09-01')), (tfds, TFloatInst('2.5@2019-09-01'), TFloatSeq('{2.5@2019-09-01, 2.5@2019-09-02}')), - (tfs, TFloatInst('2.5@2019-09-01'), - TFloatSeqSet('{[2.5@2019-09-01], (1.5@2019-09-01, 2.5@2019-09-02]}')), + (tfs, TFloatInst('2.5@2019-09-01'), + TFloatSeqSet('{[2.5@2019-09-01], (1.5@2019-09-01, 2.5@2019-09-02]}')), (tfss, TFloatInst('2.5@2019-09-01'), - TFloatSeqSet('{[2.5@2019-09-01], (1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + TFloatSeqSet('{[2.5@2019-09-01], (1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1139,9 +1200,9 @@ def test_update(self, temporal, instant, expected): (tfi, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), tfi), (tfds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), TFloatSeq('{2.5@2019-09-02}')), (tfs, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), - TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), + TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), (tfss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), - TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), ], ids=['Instant intersection', 'Instant disjoint', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1155,7 +1216,7 @@ def test_delete(self, temporal, time, expected): (tfds, TFloatInst('1.5@2019-09-03'), TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02, 1.5@2019-09-03}')), (tfs, TFloatInst('1.5@2019-09-03'), TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02, 1.5@2019-09-03]')), (tfss, TFloatInst('1.5@2019-09-06'), - TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-06]}')), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-06]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1168,7 +1229,7 @@ def test_append_instant(self, temporal, instant, expected): (tfds, TFloatSeq('{1.5@2019-09-03}'), TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02, 1.5@2019-09-03}')), (tfs, TFloatSeq('[1.5@2019-09-03]'), TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02], [1.5@2019-09-03]}')), (tfss, TFloatSeq('[1.5@2019-09-06]'), - TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05],[1.5@2019-09-06]}')), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05],[1.5@2019-09-06]}')), ], ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1249,7 +1310,8 @@ def test_temporal_sub_int_float(self, temporal, argument, expected): (tfi, tfloatarg, TFloatInst('3.75@2019-09-01')), (tfds, tfloatarg, TFloatSeq('{3.75@2019-09-01, 3.75@2019-09-02}')), (tfs, tfloatarg, TFloatSeqSet('{[3.75@2019-09-01, 4@2019-09-01 12:00:00+00, 3.75@2019-09-02]}')), - (tfss, tfloatarg, TFloatSeqSet('{[3.75@2019-09-01, 4@2019-09-01 12:00:00+00, 3.75@2019-09-02], [2.25@2019-09-03]}')), + (tfss, tfloatarg, + TFloatSeqSet('{[3.75@2019-09-01, 4@2019-09-01 12:00:00+00, 3.75@2019-09-02], [2.25@2019-09-03]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1264,12 +1326,12 @@ def test_temporal_mul_temporal(self, temporal, argument, expected): (tfds, 0, TFloat.from_base_temporal(0, tfds)), (tfs, 0, TFloat.from_base_temporal(0, tfs)), (tfss, 0, TFloat.from_base_temporal(0, tfss)), - + (tfi, 1, tfi), (tfds, 1, tfds), (tfs, 1, tfs), (tfss, 1, tfss), - + (tfi, 2, TFloatInst('3@2019-09-01')), (tfds, 2, TFloatSeq('{3@2019-09-01, 5@2019-09-02}')), (tfs, 2, TFloatSeq('[3@2019-09-01, 5@2019-09-02]')), @@ -1291,7 +1353,8 @@ def test_temporal_mul_int_float(self, temporal, argument, expected): (tfi, tfloatarg, TFloatInst('0.6@2019-09-01')), (tfds, tfloatarg, TFloatSeq('{0.6@2019-09-01, 1.667@2019-09-02}')), (tfs, tfloatarg, TFloatSeqSet('{[0.6@2019-09-01, 1@2019-09-01 12:00:00+00, 1.667@2019-09-02]}')), - (tfss, tfloatarg, TFloatSeqSet('{[0.6@2019-09-01, 1@2019-09-01 12:00:00+00, 1.667@2019-09-02], [1@2019-09-03]}')), + (tfss, tfloatarg, + TFloatSeqSet('{[0.6@2019-09-01, 1@2019-09-01 12:00:00+00, 1.667@2019-09-02], [1@2019-09-03]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1414,16 +1477,16 @@ class TestTFloatRestrictors(TestTFloat): (tfss, timestamp_set, TFloatSeq('{1.5@2019-09-01, 1.5@2019-09-03}')), (tfss, period, TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}')), (tfss, period_set, - TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), ], ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', - 'Instant-PeriodSet', + 'Instant-PeriodSet', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', 'Sequence-PeriodSet', 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', - 'SequenceSet-Period', 'SequenceSet-PeriodSet', + 'SequenceSet-Period', 'SequenceSet-PeriodSet', ] ) def test_at_time(self, temporal, restrictor, expected): @@ -1434,41 +1497,52 @@ def test_at_time(self, temporal, restrictor, expected): [ (tfi, 1.5, TFloatInst('1.5@2019-09-01')), (tfi, 2.5, None), - (tfi, floatrange(1.5, 1.5, True, True), TFloatInst('1.5@2019-09-01')), - # (tfi, [1.5,2.5], TFloatInst('1.5@2019-09-01')), - # (tfi, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)], - # TFloatInst('1.5@2019-09-01')), + (tfi, FloatSet(elements=[1.5, 2.5]), tfi), + (tfi, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + TFloatInst('1.5@2019-09-01')), + (tfi, FloatSpanSet(span_list=[FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)]), + TFloatInst('1.5@2019-09-01')), (tfds, 1.5, TFloatSeq('{1.5@2019-09-01}')), (tfds, 2.5, TFloatSeq('{2.5@2019-09-02}')), - (tfds, floatrange(1.5, 1.5, True, True), TFloatInst('1.5@2019-09-01')), - # (tfds, [1.5,2.5], TFloatSeq('{1.5@2019-09-01}')), - # (tfds, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)], - # TFloatInst('1.5@2019-09-01')), + ( + tfds, FloatSet(elements=[1.5, 2.5]), + TFloatSeq("{1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00}")), + (tfds, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + TFloatInst('1.5@2019-09-01')), + (tfds, FloatSpanSet(span_list=[FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)]), + TFloatSeqSet("{1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00}")), (tfs, 1.5, TFloatSeq('[1.5@2019-09-01]')), (tfs, 2.5, TFloatSeq('[2.5@2019-09-02]')), - (tfs, floatrange(1.5, 1.5, True, True), TFloatInst('1.5@2019-09-01')), - # (tfs, [1.5,2.5], TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02)}')), - # (tfs, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)], - # TFloatInst('1.5@2019-09-01')), + (tfs, FloatSet(elements=[1.5, 2.5]), TFloatSeqSet('{1.5@2019-09-01, 2.5@2019-09-02}')), + (tfs, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + TFloatInst('1.5@2019-09-01')), + (tfs, FloatSpanSet(span_list=[FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)]), + TFloatSeq("{[1.5@2019-09-01 00:00:00+00], [2.5@2019-09-02 00:00:00+00]}")), (tfss, 1.5, TFloatSeqSet('{[1.5@2019-09-01],[1.5@2019-09-03, 1.5@2019-09-05]}')), (tfss, 2.5, TFloatSeqSet('{[2.5@2019-09-02]}')), - (tfss, floatrange(1.5, 1.5, True, True), - TFloatSeqSet('{[1.5@2019-09-01],[1.5@2019-09-03, 1.5@2019-09-05]}')), - # (tfss, [1.5,2.5], TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02),[1.5@2019-09-03, 1.5@2019-09-05]}')) - # (tfss, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)], - # TFloatInst('1.5@2019-09-01')), - ], - ids=['Instant-1.5', 'Instant-2.5', 'Instant-Range', - # 'Instant-ValueList', 'Instant-RangeList', - 'Discrete Sequence-1.5', 'Discrete Sequence-2.5', 'Discrete Sequence-Range', - # 'Discrete Sequence-ValueList', 'Discrete Sequence-RangeList' - 'Sequence-1.5', 'Sequence-2.5', 'Sequence-Range', - # 'Sequence-ValueList', 'Sequence-RangeList', - 'SequenceSet-1.5', 'SequenceSet-2.5', 'Sequence Set-Range', - # 'SequenceSet-ValueList', 'SequenceSet-RangeList' + (tfss, FloatSet(elements=[1.5, 2.5]), TFloatSeqSet('{[1.5@2019-09-01], [2.5@2019-09-02],' + '[1.5@2019-09-03, 1.5@2019-09-05]}')), + (tfss, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + TFloatSeqSet('{[1.5@2019-09-01],[1.5@2019-09-03, 1.5@2019-09-05]}')), + (tfss, FloatSpanSet(span_list=[FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)]), + TFloatSeqSet( + "{[1.5@2019-09-01 00:00:00+00], [2.5@2019-09-02 00:00:00+00], [1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]}")), + ], + ids=['Instant-1.5', 'Instant-2.5', 'Instant-Set', + 'Instant-Span', 'Instant-SpanSet', + 'Discrete Sequence-1.5', 'Discrete Sequence-2.5', 'Discrete Sequence-Set', + 'Discrete Sequence-Span', 'Discrete Sequence-SpanSet', + 'Sequence-1.5', 'Sequence-2.5', 'Sequence-Set', + 'Sequence-Span', 'Sequence-SpanSet', + 'SequenceSet-1.5', 'SequenceSet-2.5', 'Sequence Set-Set', + 'SequenceSet-Span', 'SequenceSet-SpanSet' ] ) def test_at_values(self, temporal, restrictor, expected): @@ -1519,7 +1593,7 @@ def test_at_max(self, temporal, expected): (tfs, period_set, None), (tfss, timestamp, - TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), (tfss, timestamp_set, TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02],(1.5@2019-09-03, 1.5@2019-09-05]}')), (tfss, period, TFloatSeqSet('{[1.5@2019-09-03, 1.5@2019-09-05]}')), @@ -1531,7 +1605,7 @@ def test_at_max(self, temporal, expected): 'Discrete Sequence-PeriodSet', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', 'Sequence-PeriodSet', 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', - 'SequenceSet-Period', 'SequenceSet-PeriodSet', + 'SequenceSet-Period', 'SequenceSet-PeriodSet', ] ) def test_minus_time(self, temporal, restrictor, expected): @@ -1542,33 +1616,41 @@ def test_minus_time(self, temporal, restrictor, expected): [ (tfi, 1.5, None), (tfi, 2.5, TFloatInst('1.5@2019-09-01')), - (tfi, floatrange(1.5, 1.5, True, True), None), + (tfi, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), None), # (tfi, [1.5,2.5], TFloatInst('1.5@2019-09-01')), - # (tfi, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)], - # TFloatInst('1.5@2019-09-01')), + # (tfi, [FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + # FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)], + # TFloatInst('1.5@2019-09-01')), (tfds, 1.5, TFloatSeq('{2.5@2019-09-02}')), (tfds, 2.5, TFloatSeq('{1.5@2019-09-01}')), - (tfds, floatrange(1.5, 1.5, True, True), TFloatSeq('{2.5@2019-09-02}')), + (tfds, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + TFloatSeq('{2.5@2019-09-02}')), # (tfds, [1.5,2.5], TFloatSeq('{1.5@2019-09-01}')), - # (tfds, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)], - # TFloatInst('1.5@2019-09-01')), + # (tfds, [FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + # FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)], + # TFloatInst('1.5@2019-09-01')), (tfs, 1.5, TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), (tfs, 2.5, TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02)}')), - (tfs, floatrange(1.5, 1.5, True, True), TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), + (tfs, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), # (tfs, [1.5,2.5], TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02)}')), - # (tfs, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)], - # TFloatInst('1.5@2019-09-01')), + # (tfs, [FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + # FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)], + # TFloatInst('1.5@2019-09-01')), (tfss, 1.5, TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), (tfss, 2.5, TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02),[1.5@2019-09-03, 1.5@2019-09-05]}')), - (tfs, floatrange(1.5, 1.5, True, True), TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), - # (tfss, [1.5,2.5], TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02),[1.5@2019-09-03, 1.5@2019-09-05]}')) - # (tfss, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)], - # TFloatInst('1.5@2019-09-01')), - ], - ids=['Instant-1.5', 'Instant-2.5', 'Instant-Range', + (tfs, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), + # (tfss, [1.5,2.5], TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02),' + # '[1.5@2019-09-03, 1.5@2019-09-05]}')) + # (tfss, [FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + # FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)], + # TFloatInst('1.5@2019-09-01')), + ], + ids=['Instant-1.5', 'Instant-2.5', 'Instant-Range', # 'Instant-ValueList', 'Instant-RangeList', 'Discrete Sequence-1.5', 'Discrete Sequence-2.5', 'Discrete Sequence-Range', @@ -1637,10 +1719,10 @@ def test_minus_max(self, temporal, expected): ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', - 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', - 'Sequence-PeriodSet', - 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', + 'Sequence-PeriodSet', + 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', ] ) @@ -1652,37 +1734,41 @@ def test_at_minus_time(self, temporal, restrictor): [ (tfi, 1.5), (tfi, 2.5), - (tfi, floatrange(1.5, 1.5, True, True)), - # (tfi, [1.5,2.5]), - # (tfi, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)]), + (tfi, FloatSet(elements=[1.5, 2.5])), + (tfi, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True)), + (tfi, FloatSpanSet(span_list=[FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)])), (tfds, 1.5), (tfds, 2.5), - (tfds, floatrange(1.5, 1.5, True, True)), - # (tfds, [1.5,2.5]), - # (tfds, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)]), + (tfds, FloatSet(elements=[1.5, 2.5])), + (tfds, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True)), + (tfds, FloatSpanSet(span_list=[FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)])), (tfs, 1.5), (tfs, 2.5), - (tfs, floatrange(1.5, 1.5, True, True)), - # (tfs, [1.5,2.5]), - # (tfs, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)]), + (tfs, FloatSet(elements=[1.5, 2.5])), + (tfs, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True)), + (tfs, FloatSpanSet(span_list=[FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)])), (tfss, 1.5), (tfss, 2.5), - (tfss, floatrange(1.5, 1.5, True, True)), - # (tfss, [1.5,2.5]), - # (tfss, [floatrange(1.5, 1.5, True, True), floatrange(2.5, 2.5, True, True)]), + (tfss, FloatSet(elements=[1.5, 2.5])), + (tfss, FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True)), + (tfss, FloatSpanSet(span_list=[FloatSpan(lower=1.5, upper=1.5, lower_inc=True, upper_inc=True), + FloatSpan(lower=2.5, upper=2.5, lower_inc=True, upper_inc=True)])), ], - ids=['Instant-1.5', 'Instant-2.5', 'Instant-Range', - # 'Instant-ValueList', 'Instant-RangeList', - 'Discrete Sequence-1.5', 'Discrete Sequence-2.5', - 'Discrete Sequence-Range', - # 'Discrete Sequence-ValueList', 'Discrete Sequence-RangeList', - 'Sequence-1.5', 'Sequence-2.5', 'Sequence-Range', - # 'Sequence-ValueList', 'Sequence-RangeList', - 'SequenceSet-1.5', 'SequenceSet-2.5', 'SequenceSet-Range', - # 'SequenceSet-ValueList', 'SequenceSet-RangeList', + ids=['Instant-1.5', 'Instant-2.5', 'Instant-Set', + 'Instant-Span', 'Instant-SpanSet', + 'Discrete Sequence-1.5', 'Discrete Sequence-2.5', + 'Discrete Sequence-Set', + 'Discrete Sequence-Span', 'Discrete Sequence-SpanSet', + 'Sequence-1.5', 'Sequence-2.5', 'Sequence-Set', + 'Sequence-Span', 'Sequence-SpanSet', + 'SequenceSet-1.5', 'SequenceSet-2.5', 'SequenceSet-Set', + 'SequenceSet-Span', 'SequenceSet-SpanSet', ] ) def test_at_minus_values(self, temporal, restrictor): @@ -1971,17 +2057,16 @@ class TestTFloatSplitOperations(TestTFloat): 'temporal, expected', [ (tfi, [TFloatInst('1@2019-09-01')]), - (tfds, [TFloatSeq('{1@2019-09-01}'),TFloatSeq('{2@2019-09-02}')]), - (tfs, [TFloatSeq('[1@2019-09-01, 2@2019-09-02)'),TFloatSeq('[2@2019-09-02]')]), - (tfss, [TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02),[1@2019-09-03, 1@2019-09-05]}'),TFloatSeq('[2@2019-09-02]')]), + (tfds, [TFloatSeq('{1@2019-09-01}'), TFloatSeq('{2@2019-09-02}')]), + (tfs, [TFloatSeq('[1@2019-09-01, 2@2019-09-02)'), TFloatSeq('[2@2019-09-02]')]), + (tfss, [TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02),[1@2019-09-03, 1@2019-09-05]}'), + TFloatSeqSet('{[2@2019-09-02]}')]), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_value_split(self, temporal, expected): assert temporal.value_split(2) == expected - # The PyMEOS function uses as default origin the initial timestamp of the - # temporal value while in MEOS the default origin is Monday Janury 3, 2000 @pytest.mark.parametrize( 'temporal, expected', [ @@ -1989,29 +2074,45 @@ def test_value_split(self, temporal, expected): (tfds, [TFloatSeq('{1@2019-09-01, 2@2019-09-02}')]), (tfs, [TFloatSeq('[1@2019-09-01, 2@2019-09-02]')]), (tfss, [TFloatSeq('[1@2019-09-01,2@2019-09-02]'), - TFloatSeq('[1@2019-09-03, 1@2019-09-05]')]), + TFloatSeq('[1@2019-09-03, 1@2019-09-05]')]), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_time_split(self, temporal, expected): - assert temporal.time_split(timedelta(days=2)) == expected + assert temporal.time_split(timedelta(days=2), '2019-09-01') == expected @pytest.mark.parametrize( 'temporal, expected', [ (tfi, [TFloatInst('1@2019-09-01')]), - (tfds, [TFloatSeq('{1@2019-09-01}'), - TFloatSeq('{2@2019-09-02}')]), + (tfds, [TFloatSeq('{1@2019-09-01}'), + TFloatSeq('{2@2019-09-02}')]), (tfs, [TFloatSeq('[1@2019-09-01, 1.5@2019-09-01 12:00:00+00)'), - TFloatSeq('[1.5@2019-09-01 12:00:00+00, 2@2019-09-02]')]), + TFloatSeq('[1.5@2019-09-01 12:00:00+00, 2@2019-09-02]')]), (tfss, [TFloatSeq('[1@2019-09-01,2@2019-09-02]'), - TFloatSeq('[1@2019-09-03, 1@2019-09-05]')]), + TFloatSeq('[1@2019-09-03, 1@2019-09-05]')]), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'], ) def test_time_split_n(self, temporal, expected): assert temporal.time_split_n(2) == expected + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, [TFloatInst('1@2019-09-01')]), + (tfds, [TFloatSeq('{1@2019-09-01}'), TFloatSeq('{2@2019-09-02}')]), + (tfs, [TFloatSeq('[1@2019-09-01, 2@2019-09-02)'), + TFloatSeq('[2@2019-09-02]')]), + (tfss, [TFloatSeq('{[1@2019-09-01, 2@2019-09-02)}'), + TFloatSeq('{[1@2019-09-03, 1@2019-09-05]}'), + TFloatSeq('{[2@2019-09-02]}')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_time_split(self, temporal, expected): + assert temporal.value_time_split(2.0, timedelta(days=2), 0.0, '2019-09-01') == expected + class TestTFloatComparisons(TestTFloat): tf = TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]') @@ -2177,9 +2278,9 @@ class TestTFloatTemporalComparisons(TestTFloat): (tfi, TBoolInst('False@2019-09-01')), (tfds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), (tfs, TBoolSeqSet('{[False@2019-09-01, True@2019-09-01 12:00:00+00],' - '(False@2019-09-01 12:00:00+00, False@2019-09-02]}')), + '(False@2019-09-01 12:00:00+00, False@2019-09-02]}')), (tfss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-01 12:00:00+00],' - '(False@2019-09-01 12:00:00+00, False@2019-09-02],[True@2019-09-03]}')) + '(False@2019-09-01 12:00:00+00, False@2019-09-02],[True@2019-09-03]}')) ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -2204,9 +2305,10 @@ def test_temporal_equal_int(self, temporal, expected): [ (tfi, TBoolInst('False@2019-09-01')), (tfds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), - (tfs, TBoolSeq('{[False@2019-09-01, True@2019-09-01 12:00:00], (False@2019-09-01 12:00:00, False@2019-09-02]}')), + (tfs, + TBoolSeq('{[False@2019-09-01, True@2019-09-01 12:00:00], (False@2019-09-01 12:00:00, False@2019-09-02]}')), (tfss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-01 12:00:00],' - '(False@2019-09-01 12:00:00, False@2019-09-02],[False@2019-09-03, False@2019-09-05]}')) + '(False@2019-09-01 12:00:00, False@2019-09-02],[False@2019-09-03, False@2019-09-05]}')) ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -2245,9 +2347,9 @@ def test_temporal_equal_float(self, temporal, expected): (tfi, TBoolInst('True@2019-09-01')), (tfds, TBoolSeq('{True@2019-09-01, True@2019-09-02}')), (tfs, TBoolSeqSet('{[True@2019-09-01, False@2019-09-01 12:00:00+00],' - '(True@2019-09-01 12:00:00+00, True@2019-09-02]}')), + '(True@2019-09-01 12:00:00+00, True@2019-09-02]}')), (tfss, TBoolSeqSet('{[True@2019-09-01, False@2019-09-01 12:00:00+00],' - '(True@2019-09-01 12:00:00+00, True@2019-09-02],[False@2019-09-03]}')) + '(True@2019-09-01 12:00:00+00, True@2019-09-02],[False@2019-09-03]}')) ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -2273,13 +2375,11 @@ def test_temporal_not_equal_int(self, temporal, expected): (tfi, TBoolInst('True@2019-09-01')), (tfds, TBoolSeq('{True@2019-09-01, True@2019-09-02}')), (tfs, TBoolSeqSet('{[True@2019-09-01, False@2019-09-01 12:00:00],' - '(True@2019-09-01 12:00:00, True@2019-09-02]}')), + '(True@2019-09-01 12:00:00, True@2019-09-02]}')), (tfss, TBoolSeqSet('{[True@2019-09-01, False@2019-09-01 12:00:00],' - '(True@2019-09-01 12:00:00, True@2019-09-02], [True@2019-09-03, True@2019-09-05]}')) + '(True@2019-09-01 12:00:00, True@2019-09-02], [True@2019-09-03, True@2019-09-05]}')) ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_temporal_not_equal_int(self, temporal, expected): assert temporal.temporal_not_equal(2) == expected - - diff --git a/pymeos/tests/main/tgeogpoint_test.py b/pymeos/tests/main/tgeogpoint_test.py index 6069d5c2..ac6663e1 100644 --- a/pymeos/tests/main/tgeogpoint_test.py +++ b/pymeos/tests/main/tgeogpoint_test.py @@ -4,7 +4,7 @@ import pytest import numpy as np -from shapely import Point +from shapely import Point, LineString import shapely.geometry from pymeos import TBool, TBoolInst, TBoolSeq, TBoolSeqSet, \ @@ -33,16 +33,20 @@ class TestTGeogPointConstructors(TestTGeogPoint): @pytest.mark.parametrize( 'source, type, interpolation', [ - (TFloatInst('1.5@2019-09-01'), TGeogPointInst, TInterpolation.NONE), - (TFloatSeq('{1.5@2019-09-01, 0.5@2019-09-02}'), TGeogPointSeq, TInterpolation.DISCRETE), - (TFloatSeq('[1.5@2019-09-01, 0.5@2019-09-02]'), TGeogPointSeq, TInterpolation.LINEAR), - (TFloatSeqSet('{[1.5@2019-09-01, 0.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}'), - TGeogPointSeqSet, TInterpolation.LINEAR) + (TGeogPointInst('Point(1.5 1.5)@2019-09-01'), TGeogPointInst, + TInterpolation.NONE), + (TGeogPointSeq('{Point(1.5 1.5)@2019-09-01, Point(2.5 2.5)@2019-09-02}'), + TGeogPointSeq, TInterpolation.DISCRETE), + (TGeogPointSeq('[Point(1.5 1.5)@2019-09-01, Point(2.5 2.5)@2019-09-02]'), + TGeogPointSeq, TInterpolation.LINEAR), + (TGeogPointSeqSet('{[Point(1.5 1.5)@2019-09-01, Point(2.5 2.5)@2019-09-02],' + '[Point(1.5 1.5)@2019-09-03, Point(1.5 1.5)@2019-09-05]}'), + TGeogPointSeqSet, TInterpolation.LINEAR) ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_from_base_constructor(self, source, type, interpolation): - tp = TGeogPoint.from_base_temporal(Point(1,1), source) + tp = TGeogPoint.from_base_temporal(shapely.set_srid(shapely.Point(1,1), 4326), source) assert isinstance(tp, type) assert tp.interpolation() == interpolation @@ -57,7 +61,7 @@ def test_from_base_constructor(self, source, type, interpolation): ids=['Instant', 'Sequence', 'Discrete Sequence', 'SequenceSet'] ) def test_from_base_time_constructor(self, source, type, interpolation): - tp = TGeogPoint.from_base_time(Point(1,1), source, interpolation) + tp = TGeogPoint.from_base_time(shapely.set_srid(shapely.Point(1,1), 4326), source, interpolation) assert isinstance(tp, type) assert tp.interpolation() == interpolation @@ -81,22 +85,22 @@ def test_string_constructor(self, source, type, interpolation, expected): assert tp.interpolation() == interpolation assert str(tp) == expected - # @pytest.mark.parametrize( - # 'source, type, expected', - # [ - # ('[Point(1 1)@2019-09-01, Point(1.25 1.25)@2019-09-02, Point(1.5 1.5)@2019-09-03, Point(2 2)@2019-09-05]', TGeogPointSeq, - # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-05 00:00:00+00]'), - # ('{[Point(1 1)@2019-09-01, Point(1.25 1.25)@2019-09-02, Point(1.5 1.5)@2019-09-03, Point(2 2)@2019-09-05],' - # '[Point(1 1)@2019-09-07, Point(1 1)@2019-09-08, Point(1 1)@2019-09-09]}', TGeogPointSeqSet, - # '{[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-05 00:00:00+00], ' - # '[POINT(1 1)@2019-09-07 00:00:00+00, POINT(1 1)@2019-09-09 00:00:00+00]}'), - # ], - # ids=['Sequence', 'SequenceSet'] - # ) - # def test_string_constructor_normalization(self, source, type, expected): - # tp = type(source, normalize=True) - # assert isinstance(tp, type) - # assert str(tp) == expected + @pytest.mark.parametrize( + 'source, type, expected', + [ + ('[Point(1 1)@2019-09-01, Point(1.249919068145015 1.250040436011492)@2019-09-02, Point(2 2)@2019-09-05]', TGeogPointSeq, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-05 00:00:00+00]'), + ('{[Point(1 1)@2019-09-01, POINT(1.249919068145015 1.250040436011492)@2019-09-02, Point(2 2)@2019-09-05],' + '[Point(1 1)@2019-09-07, Point(1 1)@2019-09-08, Point(1 1)@2019-09-09]}', TGeogPointSeqSet, + '{[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-05 00:00:00+00], ' + '[POINT(1 1)@2019-09-07 00:00:00+00, POINT(1 1)@2019-09-09 00:00:00+00]}'), + ], + ids=['Sequence', 'SequenceSet'] + ) + def test_string_constructor_normalization(self, source, type, expected): + tp = type(source, normalize=True) + assert isinstance(tp, type) + assert str(tp) == expected @pytest.mark.parametrize( 'value, timestamp', @@ -112,42 +116,43 @@ def test_value_timestamp_instant_constructor(self, value, timestamp): tpi = TGeogPointInst(point=value, timestamp=timestamp) assert str(tpi) == 'POINT(1 1)@2019-09-01 00:00:00+00' - # @pytest.mark.parametrize( - # 'list, interpolation, normalize, expected', - # [ - # (['Point(1 1)@2019-09-01', 'Point(2 2)@2019-09-03'], TInterpolation.DISCRETE, False, - # '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), - # (['Point(1 1)@2019-09-01', 'Point(2 2)@2019-09-03'], TInterpolation.LINEAR, False, - # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), - # ([TGeogPointInst('Point(1 1)@2019-09-01'), TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.DISCRETE, False, - # '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), - # ([TGeogPointInst('Point(1 1)@2019-09-01'), TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, False, - # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), - # (['Point(1 1)@2019-09-01', TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.DISCRETE, False, - # '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), - # (['Point(1 1)@2019-09-01', TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, False, - # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), - - # (['Point(1 1)@2019-09-01', 'Point(1.5 1.5)@2019-09-02', 'Point(2 2)@2019-09-03'], TInterpolation.LINEAR, True, - # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), - # ([TGeogPointInst('Point(1 1)@2019-09-01'), TGeogPointInst('Point(1.5 1.5)@2019-09-02'), TGeogPointInst('Point(2 2)@2019-09-03')], - # TInterpolation.LINEAR, True, - # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), - # (['Point(1 1)@2019-09-01', 'Point(1.5 1.5)@2019-09-02', TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, True, - # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), - # ], - # ids=['String Discrete', 'String Linear', 'TGeogPointInst Discrete', 'TGeogPointInst Linear', 'Mixed Discrete', - # 'Mixed Linear', 'String Linear Normalized', 'TGeogPointInst Linear Normalized', - # 'Mixed Linear Normalized'] - # ) - # def test_instant_list_sequence_constructor(self, list, interpolation, normalize, expected): - # tps = TGeogPointSeq(instant_list=list, interpolation=interpolation, normalize=normalize, upper_inc=True) - # assert str(tps) == expected - # assert tps.interpolation() == interpolation - - # tps2 = TGeogPointSeq.from_instants(list, interpolation=interpolation, normalize=normalize, upper_inc=True) - # assert str(tps2) == expected - # assert tps2.interpolation() == interpolation + @pytest.mark.parametrize( + 'list, interpolation, normalize, expected', + [ + (['Point(1 1)@2019-09-01', 'Point(2 2)@2019-09-03'], TInterpolation.DISCRETE, False, + '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), + (['Point(1 1)@2019-09-01', 'Point(2 2)@2019-09-03'], TInterpolation.LINEAR, False, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + ([TGeogPointInst('Point(1 1)@2019-09-01'), TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.DISCRETE, False, + '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), + ([TGeogPointInst('Point(1 1)@2019-09-01'), TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, False, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + (['Point(1 1)@2019-09-01', TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.DISCRETE, False, + '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), + (['Point(1 1)@2019-09-01', TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, False, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + + (['Point(1 1)@2019-09-01', 'Point(1.499885736561676 1.500057091479197)@2019-09-02', 'Point(2 2)@2019-09-03'], TInterpolation.LINEAR, True, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + ([TGeogPointInst('Point(1 1)@2019-09-01'), TGeogPointInst('Point(1.499885736561676 1.500057091479197)@2019-09-02'), TGeogPointInst('Point(2 2)@2019-09-03')], + TInterpolation.LINEAR, True, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + (['Point(1 1)@2019-09-01', 'Point(1.499885736561676 1.500057091479197)@2019-09-02', TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, True, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + ], + ids=['String Discrete', 'String Linear', 'TGeogPointInst Discrete', 'TGeogPointInst Linear', + 'Mixed Discrete', + 'Mixed Linear', 'String Linear Normalized', 'TGeogPointInst Linear Normalized', + 'Mixed Linear Normalized'] + ) + def test_instant_list_sequence_constructor(self, list, interpolation, normalize, expected): + tps = TGeogPointSeq(instant_list=list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tps) == expected + assert tps.interpolation() == interpolation + + tps2 = TGeogPointSeq.from_instants(list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tps2) == expected + assert tps2.interpolation() == interpolation @pytest.mark.parametrize( 'temporal', @@ -156,6 +161,7 @@ def test_value_timestamp_instant_constructor(self, value, timestamp): 'Instant 3D', 'Discrete Sequence 3D', 'Sequence 3D', 'SequenceSet 3D'] ) def test_from_as_constructor(self, temporal): + assert temporal == temporal.__class__(str(temporal)) assert temporal == temporal.from_wkb(temporal.as_wkb()) assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) assert temporal == temporal.from_mfjson(temporal.as_mfjson()) @@ -1010,10 +1016,10 @@ class TestTGeogPointConversions(TestTGeogPoint): @pytest.mark.parametrize( 'temporal, expected', [ - (tpi, TGeomPointInst('SRID=4326;Point(1 1)@2019-09-01')), - (tpds, TGeomPointSeq('SRID=4326;{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), - (tps, TGeomPointSeq('SRID=4326;[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), - (tpss, TGeomPointSeqSet('SRID=4326;{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],' + (tpi, TGeomPointInst('Point(1 1)@2019-09-01')), + (tpds, TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), + (tps, TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), + (tpss, TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],' '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] @@ -1053,40 +1059,46 @@ def test_to_instant(self, temporal, expected): assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TGeogPointInst('Point(1 1)@2019-09-01'), + (TGeogPointInst('Point(1 1)@2019-09-01'), TInterpolation.LINEAR, TGeogPointSeq('[Point(1 1)@2019-09-01]')), (TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + TInterpolation.DISCRETE, TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), (TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TInterpolation.LINEAR, TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), (TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}'), + TInterpolation.LINEAR, TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequence(self, temporal, expected): - temp = temporal.to_sequence() + def test_to_sequence(self, temporal, interpolation, expected): + temp = temporal.to_sequence(interpolation) assert isinstance(temp, TGeogPointSeq) assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TGeogPointInst('Point(1 1)@2019-09-01'), + (TGeogPointInst('Point(1 1)@2019-09-01'), TInterpolation.LINEAR, TGeogPointSeqSet('{[Point(1 1)@2019-09-01]}')), (TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + TInterpolation.LINEAR, TGeogPointSeqSet('{[Point(1 1)@2019-09-01], [Point(2 2)@2019-09-02]}')), (TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TInterpolation.LINEAR, TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), (TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}'), + TInterpolation.LINEAR, TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequenceset(self, temporal, expected): - temp = temporal.to_sequenceset() + def test_to_sequenceset(self, temporal, interpolation, expected): + temp = temporal.to_sequenceset(interpolation) assert isinstance(temp, TGeogPointSeqSet) assert temp == expected @@ -1162,8 +1174,8 @@ def test_set_interpolation(self, temporal, interpolation, expected): 'Sequence Set posi(tpve days', 'Sequence Set nega(tpve days', 'Sequence Set posi(tpve hours', 'Sequence Set nega(tpve hours'] ) - def test_shift(self, tpoint, delta, expected): - assert tpoint.shift(delta) == expected + def test_shift_time(self, tpoint, delta, expected): + assert tpoint.shift_time(delta) == expected @pytest.mark.parametrize( 'tpoint, delta, expected', @@ -1184,11 +1196,11 @@ def test_shift(self, tpoint, delta, expected): 'Sequence posi(tpve days', 'Sequence posi(tpve hours', 'Sequence Set posi(tpve days', 'Sequence Set posi(tpve hours'] ) - def test_scale(self, tpoint, delta, expected): - assert tpoint.tscale(delta) == expected + def test_scale_time(self, tpoint, delta, expected): + assert tpoint.scale_time(delta) == expected - def test_shift_tscale(self): - assert self.tpss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ + def test_shift_scale_time(self): + assert self.tpss.shift_scale_time(timedelta(days=4), timedelta(hours=2)) == \ TGeogPointSeqSet('{[Point(1 1)@2019-09-05 00:00:00, Point(2 2)@2019-09-05 00:30:00],' '[Point(1 1)@2019-09-05 01:00:00, Point(1 1)@2019-09-05 02:00:00]}') @@ -1214,7 +1226,7 @@ def test_shift_tscale(self): ] ) def test_temporal_sample(self, tpoint, delta, expected): - assert tpoint.temporal_sample(delta).round(1) == expected + assert tpoint.temporal_sample(delta, '2019-09-01').round(1) == expected @pytest.mark.parametrize( 'tpoint, delta, expected', @@ -1228,7 +1240,7 @@ def test_temporal_sample(self, tpoint, delta, expected): 'Sequence Set days', 'Sequence Set hours'] ) def test_stops(self, tpoint, delta, expected): - assert tpoint.stops(0.0, delta) == expected + assert tpoint.stops(0.1, delta) == expected @pytest.mark.parametrize( 'temporal, expected', @@ -1251,7 +1263,7 @@ def test_stops(self, tpoint, delta, expected): ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_round(self, temporal, expected): - assert temporal.round(maxdd=2) + assert temporal.round(max_decimals=2) class TestTGeogPointModifications(TestTGeogPoint): @@ -1482,6 +1494,275 @@ def test_at_minus(self, temporal, restrictor): assert TGeogPoint.merge(temporal.at(restrictor), temporal.minus(restrictor)) == temporal +class TestTGeogPointEverSpatialOperations(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, True), + (tpds, True), + (tps, True), + (tpss, True), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_ever_contained_withindist_intersects(self, temporal, expected): + assert temporal.is_ever_within_distance(Point(1,1), 1) == expected + assert temporal.is_ever_within_distance(TGeogPointInst('Point(1 1)@2019-09-01'), 1) == expected + assert temporal.ever_intersects(Point(1,1)) == expected + assert temporal.ever_intersects(TGeogPointInst('Point(1 1)@2019-09-01')) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, True), + (tpds, True), + (tps, True), + (tpss, True), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_ever_disjoint(self, temporal, expected): + assert temporal.is_ever_disjoint(Point(3,3)) == expected + assert temporal.is_ever_disjoint(TGeogPointInst('Point(3 3)@2019-09-01')) == expected + + +class TestTGeogPointTemporalSpatialOperations(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TBoolInst('True@2019-09-01')), + (tpds, TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (tps, TBoolSeqSet('{[True@2019-09-01], (False@2019-09-01, False@2019-09-02]}')), + (tpss, TBoolSeqSet('{[True@2019-09-01], (False@2019-09-01, False@2019-09-02],' + '[True@2019-09-03, True@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_intersects_disjoint(self, temporal, expected): + assert temporal.intersects(Point(1,1)) == expected + assert temporal.disjoint(Point(1,1)) == ~expected + + # Verify that these results are correct wrt lifting the 9DEM definition of touches + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TBoolInst('False@2019-09-01')), + (tpds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tps, TBoolSeqSet('[False@2019-09-01, False@2019-09-02]')), + (tpss, TBoolSeqSet('{[False@2019-09-01, False@2019-09-02],' + '[False@2019-09-03, False@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_touches(self, temporal, expected): + assert temporal.touches(Point(1,1)) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, Point(1,1), TBoolInst('True@2019-09-01')), + (tpds, Point(1,1), TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tps, Point(1,1), TBoolSeqSet('{[True@2019-09-01, True@2019-09-02]}')), + (tpss, Point(1,1), TBoolSeqSet('{[True@2019-09-01, True@2019-09-02],' + '[True@2019-09-03, True@2019-09-05]}')), + + (tpi, TGeogPointInst('Point(1 1)@2019-09-01'), TBoolInst('True@2019-09-01')), + (tpds, TGeogPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-02}'), + TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (tps, TGeogPointSeq('[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02]'), + TBoolSeqSet('{[True@2019-09-01, True@2019-09-02]}')), + (tpss, + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}'), + TBoolSeqSet('{[True@2019-09-01, True@2019-09-02],' + '[True@2019-09-03, True@2019-09-05]}')), + ], + ids=['Instant Geo', 'Discrete Sequence Geo', 'Sequence Geo', 'SequenceSet Geo', + 'Instant TPoint', 'Discrete Sequence TPoint', 'Sequence TPoint', 'SequenceSet TPoint'] + ) + def test_temporal_withindist(self, temporal, argument, expected): + assert temporal.within_distance(argument, 2) == expected + + +class TestTGeogPointDistanceOperations(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, Point(1,1), TFloatInst('0@2019-09-01')), + (tpds, Point(1,1), TFloatSeq('{0@2019-09-01, 156876.149@2019-09-02}')), + (tps, Point(1,1), TFloatSeq('[0@2019-09-01, 156876.149@2019-09-02]')), + (tpss, Point(1,1), TFloatSeqSet('{[0@2019-09-01, 156876.149@2019-09-02],' + '[0@2019-09-03, 0@2019-09-05]}')), + + (tpi, STBox('GEODSTBOX X((1,1),(1,1))'), TFloatInst('0@2019-09-01')), + (tpds, STBox('GEODSTBOX X((1,1),(1,1))'), TFloatSeq('{0@2019-09-01, 156876.149@2019-09-02}')), + (tps, STBox('GEODSTBOX X((1,1),(1,1))'), TFloatSeq('[0@2019-09-01, 156876.149@2019-09-02]')), + (tpss, STBox('GEODSTBOX X((1,1),(1,1))'), TFloatSeqSet('{[0@2019-09-01, 156876.149@2019-09-02],' + '[0@2019-09-03, 0@2019-09-05]}')), + + (tpi, TGeogPointInst('Point(1 1)@2019-09-01'), TFloatInst('0@2019-09-01')), + (tpds, TGeogPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-02}'), + TFloatSeq('{0@2019-09-01, 156876.149@2019-09-02}')), + (tps, TGeogPointSeq('[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02]'), + TFloatSeq('[0@2019-09-01, 156876.149@2019-09-02]')), + (tpss, + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}'), + TFloatSeqSet('{[0@2019-09-01, 156876.149@2019-09-02],' + '[0@2019-09-03, 0@2019-09-05]}')), + ], + ids=['Instant Geo', 'Discrete Sequence Geo', 'Sequence Geo', 'SequenceSet Geo', + 'Instant STBox', 'Discrete Sequence STBox', 'Sequence STBox', 'SequenceSet STBox', + 'Instant TPoint', 'Discrete Sequence TPoint', 'Sequence TPoint', 'SequenceSet TPoint'] + ) + def test_distance(self, temporal, argument, expected): + assert temporal.distance(argument).round(3) == expected + assert round(temporal.nearest_approach_distance(argument), 3) == 0.0 + + @pytest.mark.parametrize( + 'temporal, argument', + [ + (tpi, Point(1,1)), + (tpds, Point(1,1)), + (tps, Point(1,1)), + (tpss, Point(1,1)), + + (tpi, TGeogPointInst('Point(1 1)@2019-09-01')), + (tpds, TGeogPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-02}')), + (tps, TGeogPointSeq('[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02]')), + (tpss, + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + ], + ids=['Instant Geo', 'Discrete Sequence Geo', 'Sequence Geo', 'SequenceSet Geo', + 'Instant TPoint', 'Discrete Sequence TPoint', 'Sequence TPoint', 'SequenceSet TPoint'] + ) + def test_nearest_approach_instant(self, temporal, argument): + assert temporal.nearest_approach_instant(argument) == TGeogPointInst('Point(1 1)@2019-09-01') + + @pytest.mark.parametrize( + 'temporal, argument', + [ + (tpi, Point(1,1)), + (tpds, Point(1,1)), + (tps, Point(1,1)), + (tpss, Point(1,1)), + + (tpi, TGeogPointInst('Point(1 1)@2019-09-01')), + (tpds, TGeogPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-02}')), + (tps, TGeogPointSeq('[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02]')), + (tpss, + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + ], + ids=['Instant Geo', 'Discrete Sequence Geo', 'Sequence Geo', 'SequenceSet Geo', + 'Instant TPoint', 'Discrete Sequence TPoint', 'Sequence TPoint', 'SequenceSet TPoint'] + ) + def test_shortest_line(self, temporal, argument): + assert temporal.shortest_line(argument) == LineString([(1,1), (1,1)]) + + +class TestTGeogPointSimilarityFunctions(TestTGeogPoint): + tfi = TGeogPointInst('Point(1 1)@2019-09-01') + tfds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tfs = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tfss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TGeogPointInst('Point(3 3)@2019-09-02'), 313705.45), + (tfds, TGeogPointInst('Point(3 3)@2019-09-03'), 313705.45), + (tfs, TGeogPointInst('Point(3 3)@2019-09-03'), 313705.45), + (tfss, TGeogPointInst('Point(3 3)@2019-09-08'), 313705.45), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_frechet_distance(self, temporal, argument, expected): + assert round(temporal.frechet_distance(argument), 2) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TGeogPointInst('Point(3 3)@2019-09-02'), 313705.45), + (tfds, TGeogPointInst('Point(3 3)@2019-09-03'), 470534.77), + (tfs, TGeogPointInst('Point(3 3)@2019-09-03'), 470534.77), + (tfss, TGeogPointInst('Point(3 3)@2019-09-08'), 1097945.67), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_dyntimewarp_distance(self, temporal, argument, expected): + assert round(temporal.dyntimewarp_distance(argument), 2) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TGeogPointInst('Point(3 3)@2019-09-02'), 313705.45), + (tfds, TGeogPointInst('Point(3 3)@2019-09-03'), 313705.45), + (tfs, TGeogPointInst('Point(3 3)@2019-09-03'), 313705.45), + (tfss, TGeogPointInst('Point(3 3)@2019-09-08'), 313705.45), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_hausdorff_distance(self, temporal, argument, expected): + assert round(temporal.hausdorff_distance(argument), 2) == expected + + +class TestTGeogPointSplitOperations(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [TGeogPointInst('Point(1 1)@2019-09-01')]), + (tpds, [TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')]), + (tps, [TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')]), + (tpss, [TGeogPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]'), + TGeogPointSeq('[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time_split(self, temporal, expected): + assert temporal.time_split(timedelta(days=2), '2019-09-01') == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [TGeogPointInst('Point(1 1)@2019-09-01')]), + (tpds, [TGeogPointSeq('{Point(1 1)@2019-09-01}'), + TGeogPointSeq('{Point(2 2)@2019-09-02}')]), + (tps, [TGeogPointSeq('[Point(1 1)@2019-09-01, Point(1.5 1.5)@2019-09-01 12:00:00+00)'), + TGeogPointSeq('[Point(1.5 1.5)@2019-09-01 12:00:00+00, Point(2 2)@2019-09-02]')]), + (tpss, [TGeogPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]'), + TGeogPointSeq('[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time_split_n(self, temporal, expected): + fragments = temporal.time_split_n(2) + rounded = [frag.round(1) for frag in fragments] + assert rounded == expected + + class TestTGeogPointComparisons(TestTGeogPoint): tp = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') other = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') diff --git a/pymeos/tests/main/tgeompoint_test.py b/pymeos/tests/main/tgeompoint_test.py index 55d0a1ca..9b095d0d 100644 --- a/pymeos/tests/main/tgeompoint_test.py +++ b/pymeos/tests/main/tgeompoint_test.py @@ -95,7 +95,7 @@ def test_string_constructor(self, source, type, interpolation, expected): ids=['Sequence', 'SequenceSet'] ) def test_string_constructor_normalization(self, source, type, expected): - tp = type(source, normalize=1) + tp = type(source, normalize=True) assert isinstance(tp, type) assert str(tp) == expected @@ -157,6 +157,7 @@ def test_instant_list_sequence_constructor(self, list, interpolation, normalize, 'Instant 3D', 'Discrete Sequence 3D', 'Sequence 3D', 'SequenceSet 3D'] ) def test_from_as_constructor(self, temporal): + assert temporal == temporal.__class__(str(temporal)) assert temporal == temporal.from_wkb(temporal.as_wkb()) assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) assert temporal == temporal.from_mfjson(temporal.as_mfjson()) @@ -844,19 +845,19 @@ def test_length(self, temporal, expected): def test_cumulative_length(self, temporal, expected): assert temporal.cumulative_length() == expected - # @pytest.mark.parametrize( - # 'temporal, expected', - # [ - # (tpi, None), - # (tpds, None), - # (tps, TFloatSeq('Interp=Step;[1.4142135623730951@2019-09-01, 1.4142135623730951@2019-09-02]') / 3600 / 24), - # (tpss, TFloatSeqSet('Interp=Step;{[1.4142135623730951@2019-09-01, 1.4142135623730951@2019-09-02],' - # '[0@2019-09-03, 0@2019-09-05]}') / 3600 / 24), - # ], - # ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] - # ) - # def test_speed(self, temporal, expected): - # assert temporal.speed() == expected + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, None), + (tpds, None), + (tps, TFloatSeq('Interp=Step;[1.4142135623730951@2019-09-01, 1.4142135623730951@2019-09-02]') / 3600 / 24), + (tpss, TFloatSeqSet('Interp=Step;{[1.4142135623730951@2019-09-01, 1.4142135623730951@2019-09-02],' + '[0@2019-09-03, 0@2019-09-05]}') / 3600 / 24), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_speed(self, temporal, expected): + assert temporal.speed() == expected @pytest.mark.parametrize( 'temporal, expected', @@ -1011,9 +1012,9 @@ def test_angular_difference(self, temporal, expected): (tpi, Point(1,1)), (tpds, Point(1.5,1.5)), (tps, Point(1.5,1.5)), - # (tpss, Point(1.167,1.167)), + (tpss, Point(1.166666666666667,1.166666666666667)), ], - ids=['Instant', 'Discrete Sequence', 'Sequence'] #, 'SequenceSet'] + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_time_weighted_centroid(self, temporal, expected): assert temporal.time_weighted_centroid() == expected @@ -1110,40 +1111,46 @@ def test_to_instant(self, temporal, expected): assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TGeomPointInst('Point(1 1)@2019-09-01'), + (TGeomPointInst('Point(1 1)@2019-09-01'), TInterpolation.LINEAR, TGeomPointSeq('[Point(1 1)@2019-09-01]')), (TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + TInterpolation.DISCRETE, TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), (TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TInterpolation.LINEAR, TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), (TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}'), + TInterpolation.LINEAR, TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequence(self, temporal, expected): - temp = temporal.to_sequence() + def test_to_sequence(self, temporal, interpolation, expected): + temp = temporal.to_sequence(interpolation) assert isinstance(temp, TGeomPointSeq) assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TGeomPointInst('Point(1 1)@2019-09-01'), + (TGeomPointInst('Point(1 1)@2019-09-01'), TInterpolation.LINEAR, TGeomPointSeqSet('{[Point(1 1)@2019-09-01]}')), (TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + TInterpolation.LINEAR, TGeomPointSeqSet('{[Point(1 1)@2019-09-01], [Point(2 2)@2019-09-02]}')), (TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TInterpolation.LINEAR, TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), (TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}'), + TInterpolation.LINEAR, TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequenceset(self, temporal, expected): - temp = temporal.to_sequenceset() + def test_to_sequenceset(self, temporal, interpolation, expected): + temp = temporal.to_sequenceset(interpolation) assert isinstance(temp, TGeomPointSeqSet) assert temp == expected @@ -1219,8 +1226,8 @@ def test_set_interpolation(self, temporal, interpolation, expected): 'Sequence Set posi(tpve days', 'Sequence Set nega(tpve days', 'Sequence Set posi(tpve hours', 'Sequence Set nega(tpve hours'] ) - def test_shift(self, tpoint, delta, expected): - assert tpoint.shift(delta) == expected + def test_shift_time(self, tpoint, delta, expected): + assert tpoint.shift_time(delta) == expected @pytest.mark.parametrize( 'tpoint, delta, expected', @@ -1241,11 +1248,11 @@ def test_shift(self, tpoint, delta, expected): 'Sequence posi(tpve days', 'Sequence posi(tpve hours', 'Sequence Set posi(tpve days', 'Sequence Set posi(tpve hours'] ) - def test_scale(self, tpoint, delta, expected): - assert tpoint.tscale(delta) == expected + def test_scale_time(self, tpoint, delta, expected): + assert tpoint.scale_time(delta) == expected - def test_shift_tscale(self): - assert self.tpss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ + def test_shift_scale_time(self): + assert self.tpss.shift_scale_time(timedelta(days=4), timedelta(hours=2)) == \ TGeomPointSeqSet('{[Point(1 1)@2019-09-05 00:00:00, Point(2 2)@2019-09-05 00:30:00],' '[Point(1 1)@2019-09-05 01:00:00, Point(1 1)@2019-09-05 02:00:00]}') @@ -1271,7 +1278,7 @@ def test_shift_tscale(self): ] ) def test_temporal_sample(self, tpoint, delta, expected): - assert tpoint.temporal_sample(delta) == expected + assert tpoint.temporal_sample(delta, '2019-09-01') == expected @pytest.mark.parametrize( 'tpoint, delta, expected', @@ -1285,7 +1292,7 @@ def test_temporal_sample(self, tpoint, delta, expected): 'Sequence Set days', 'Sequence Set hours'] ) def test_stops(self, tpoint, delta, expected): - assert tpoint.stops(0.0, delta) == expected + assert tpoint.stops(0.1, delta) == expected @pytest.mark.parametrize( 'temporal, expected', @@ -1308,7 +1315,7 @@ def test_stops(self, tpoint, delta, expected): ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_round(self, temporal, expected): - assert temporal.round(maxdd=2) + assert temporal.round(max_decimals=2) @pytest.mark.parametrize( 'temporal, expected', @@ -1904,6 +1911,45 @@ def test_dyntimewarp_distance(self, temporal, argument, expected): def test_hausdorff_distance(self, temporal, argument, expected): assert temporal.hausdorff_distance(argument) == expected + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(3 3)@2019-09-02'), 2.83), + (tpds, TGeomPointInst('Point(3 3)@2019-09-03'), 2.83), + (tps, TGeomPointInst('Point(3 3)@2019-09-03'), 2.83), + (tpss, TGeomPointInst('Point(3 3)@2019-09-08'), 2.83), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_frechet_distance_round(self, temporal, argument, expected): + assert round(temporal.frechet_distance(argument), 2) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(3 3)@2019-09-02'), 2.83), + (tpds, TGeomPointInst('Point(3 3)@2019-09-03'), 4.24), + (tps, TGeomPointInst('Point(3 3)@2019-09-03'), 4.24), + (tpss, TGeomPointInst('Point(3 3)@2019-09-08'), 9.9), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_dyntimewarp_distance_round(self, temporal, argument, expected): + assert round(temporal.dyntimewarp_distance(argument), 2) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(3 3)@2019-09-02'), 2.83), + (tpds, TGeomPointInst('Point(3 3)@2019-09-03'), 2.83), + (tps, TGeomPointInst('Point(3 3)@2019-09-03'), 2.83), + (tpss, TGeomPointInst('Point(3 3)@2019-09-08'), 2.83), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_hausdorff_distance_round(self, temporal, argument, expected): + assert round(temporal.hausdorff_distance(argument), 2) == expected + class TestTGeomPointEverSpatialOperations(TestTGeomPoint): tpi = TGeomPointInst('Point(1 1)@2019-09-01') @@ -1921,14 +1967,27 @@ class TestTGeomPointEverSpatialOperations(TestTGeomPoint): ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_temporal_ever_contained_withindist_intersects_touches(self, temporal, expected): + def test_temporal_ever_contained_withindist_intersects(self, temporal, expected): assert temporal.is_ever_contained_in(Polygon([(0,0),(0,2),(2,2),(2,0),(0,0)])) == expected assert temporal.is_ever_contained_in(STBox('STBOX X((0,0),(2,2))')) == expected assert temporal.is_ever_within_distance(Point(1,1), 1) == expected assert temporal.is_ever_within_distance(TGeomPointInst('Point(1 1)@2019-09-01'), 1) == expected assert temporal.ever_intersects(Point(1,1)) == expected assert temporal.ever_intersects(TGeomPointInst('Point(1 1)@2019-09-01')) == expected - # assert temporal.ever_touches(Point(1,1)) == expected + + # Verify that these results are correct wrt lifting the 9DEM definition of touches + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, False), + (tpds, False), + (tps, False), + (tpss, False), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_ever_touches(self, temporal, expected): + assert temporal.ever_touches(Point(1,1)) == expected @pytest.mark.parametrize( 'temporal, expected', @@ -1962,10 +2021,24 @@ class TestTGeomPointTemporalSpatialOperations(TestTGeomPoint): ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_temporal_intersects_disjoint_touches(self, temporal, expected): + def test_temporal_intersects_disjoint(self, temporal, expected): assert temporal.intersects(Point(1,1)) == expected assert temporal.disjoint(Point(1,1)) == ~expected - # assert temporal.touches(Point(1,1)) == expected + + # Verify that these results are correct wrt lifting the 9DEM definition of touches + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TBoolInst('False@2019-09-01')), + (tpds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tps, TBoolSeqSet('[False@2019-09-01, False@2019-09-02]')), + (tpss, TBoolSeqSet('{[False@2019-09-01, False@2019-09-02],' + '[False@2019-09-03, False@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_touches(self, temporal, expected): + assert temporal.touches(Point(1,1)) == expected @pytest.mark.parametrize( 'temporal, argument, expected', @@ -2098,8 +2171,6 @@ class TestTGeomPointSplitOperations(TestTGeomPoint): tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') - # The PyMEOS function uses as default origin the initial timestamp of the - # temporal value while in MEOS the default origin is Monday Janury 3, 2000 @pytest.mark.parametrize( 'temporal, expected', [ @@ -2112,7 +2183,7 @@ class TestTGeomPointSplitOperations(TestTGeomPoint): ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_time_split(self, temporal, expected): - assert temporal.time_split(timedelta(days=2)) == expected + assert temporal.time_split(timedelta(days=2), '2019-09-01') == expected @pytest.mark.parametrize( 'temporal, expected', @@ -2130,6 +2201,42 @@ def test_time_split(self, temporal, expected): def test_time_split_n(self, temporal, expected): assert temporal.time_split_n(2) == expected + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [TGeomPointInst('Point(1 1)@2019-09-01')]), + (tpds, [TGeomPointSeq('{Point(1 1)@2019-09-01}'), + TGeomPointSeq('{Point(2 2)@2019-09-02}')]), + (tps, [TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02)'), + TGeomPointSeq('[Point(2 2)@2019-09-02]')]), + (tpss, [TGeomPointSeqSet('{[POINT(1 1)@2019-09-01, POINT(2 2)@2019-09-02),' + '[POINT(1 1)@2019-09-03, POINT(1 1)@2019-09-05]}'), + TGeomPointSeqSet('{[POINT(2 2)@2019-09-02]}')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_space_split(self, temporal, expected): + assert temporal.space_split(1.0) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [TGeomPointInst('Point(1 1)@2019-09-01')]), + (tpds, [TGeomPointSeq('{Point(1 1)@2019-09-01}'), + TGeomPointSeq('{Point(2 2)@2019-09-02}')]), + (tps, [TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02)'), + TGeomPointSeq('[Point(2 2)@2019-09-02]')]), + (tpss, [TGeomPointSeq('{[POINT(1 1)@2019-09-01, POINT(2 2)@2019-09-02)}'), + TGeomPointSeq('{[POINT(2 2)@2019-09-02]}'), + TGeomPointSeq('{[POINT(1 1)@2019-09-03, POINT(1 1)@2019-09-05)}'), + TGeomPointSeq('{[POINT(1 1)@2019-09-05]}')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_space_time_split(self, temporal, expected): + assert temporal.space_time_split(1.0, timedelta(days=2), + time_start='2019-09-01') == expected + class TestTGeomPointComparisons(TestTGeomPoint): tp = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') diff --git a/pymeos/tests/main/tint_test.py b/pymeos/tests/main/tint_test.py index 7dadfdf1..aed0676b 100644 --- a/pymeos/tests/main/tint_test.py +++ b/pymeos/tests/main/tint_test.py @@ -1,14 +1,13 @@ from copy import copy -from operator import not_ from datetime import datetime, timezone, timedelta -from spans.types import intrange, floatrange +from operator import not_ import pytest -from pymeos import TBool, TBoolInst, TBoolSeq, TBoolSeqSet, \ +from pymeos import TBoolInst, TBoolSeq, TBoolSeqSet, \ TFloat, TFloatInst, TFloatSeq, TFloatSeqSet, \ TInt, TIntInst, TIntSeq, TIntSeqSet, \ - TInterpolation, TBox, TimestampSet, Period, PeriodSet + TInterpolation, TBox, TimestampSet, Period, PeriodSet, IntSpan, IntSet, IntSpanSet from tests.conftest import TestPyMEOS @@ -46,7 +45,7 @@ def test_from_base_constructor(self, source, type, interpolation): (Period('[2019-09-01, 2019-09-02]'), TIntSeq, TInterpolation.STEPWISE), (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), TIntSeqSet, TInterpolation.STEPWISE) ], - ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_from_base_time_constructor(self, source, type, interpolation): ti = TInt.from_base_time(1, source) @@ -86,7 +85,7 @@ def test_string_constructor(self, source, type, interpolation, expected): ids=['Sequence', 'SequenceSet'] ) def test_string_constructor_normalization(self, source, type, expected): - ti = type(source, normalize=1) + ti = type(source, normalize=True) assert isinstance(ti, type) assert str(ti) == expected @@ -147,6 +146,7 @@ def test_instant_list_sequence_constructor(self, list, interpolation, normalize, ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_from_as_constructor(self, temporal): + assert temporal == temporal.__class__(str(temporal)) assert temporal == temporal.from_wkb(temporal.as_wkb()) assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) assert temporal == temporal.from_mfjson(temporal.as_mfjson()) @@ -217,7 +217,7 @@ def test_as_wkt(self, temporal, expected): (tids, '011D000602000000030100000000A01E4E71340200020000000000F66B85340200'), (tis, '011D000A02000000030100000000A01E4E71340200020000000000F66B85340200'), (tiss, '011D000B0200000002000000030100000000A01E4E71340200020000000000F66B85340200' - '0200000003010000000060CD89993402000100000000207CC5C1340200') + '0200000003010000000060CD89993402000100000000207CC5C1340200') ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -340,7 +340,7 @@ def test_as_hexwkb(self, temporal, expected): ) def test_as_mfjson(self, temporal, expected): assert temporal.as_mfjson() == expected - + class TestTIntConversions(TestTInt): tii = TIntInst('1@2019-09-01') @@ -425,28 +425,28 @@ def test_values(self, temporal, expected): @pytest.mark.parametrize( 'temporal, expected', [ - (tii, intrange(1, 1, True, True)), - (tids, intrange(1, 2, True, True)), - (tis, intrange(1, 2, True, True)), - (tiss, intrange(1, 2, True, True)), + (tii, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True)), + (tids, IntSpan(lower=1, upper=2, lower_inc=True, upper_inc=True)), + (tis, IntSpan(lower=1, upper=2, lower_inc=True, upper_inc=True)), + (tiss, IntSpan(lower=1, upper=2, lower_inc=True, upper_inc=True)), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_value_range(self, temporal, expected): - assert temporal.value_range() == expected + def test_value_span(self, temporal, expected): + assert temporal.value_span() == expected @pytest.mark.parametrize( 'temporal, expected', [ - (tii, [intrange(1, 1, True, True)]), - (tids, [intrange(1, 2, True, True)]), - (tis, [intrange(1, 2, True, True)]), - (tiss, [intrange(1, 2, True, True)]), + (tii, IntSpanSet('{[1,1]}')), + (tids, IntSpanSet('{[1,2]}')), + (tis, IntSpanSet('{[1,2]}')), + (tiss, IntSpanSet('{[1,2]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_value_ranges(self, temporal, expected): - assert temporal.value_ranges() == expected + def test_value_spans(self, temporal, expected): + assert temporal.value_spans() == expected @pytest.mark.parametrize( 'temporal, expected', @@ -785,15 +785,15 @@ def test_lower_upper_inc(self, temporal, expected): assert temporal.upper_inc() == expected def test_sequenceset_sequence_functions(self): - tiss1 =TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],' - '[1@2019-09-03, 1@2019-09-05], [3@2019-09-06]}') + tiss1 = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],' + '[1@2019-09-03, 1@2019-09-05], [3@2019-09-06]}') assert tiss1.num_sequences() == 3 assert tiss1.start_sequence() == TIntSeq('[1@2019-09-01, 2@2019-09-02]') assert tiss1.end_sequence() == TIntSeq('[3@2019-09-06]') assert tiss1.sequence_n(1) == TIntSeq('[1@2019-09-03, 1@2019-09-05]') assert tiss1.sequences() == [TIntSeq('[1@2019-09-01, 2@2019-09-02]'), - TIntSeq('[1@2019-09-03, 1@2019-09-05]'), - TIntSeq('[3@2019-09-06]')] + TIntSeq('[1@2019-09-03, 1@2019-09-05]'), + TIntSeq('[3@2019-09-06]')] @pytest.mark.parametrize( 'temporal, expected', @@ -847,40 +847,46 @@ def test_to_instant(self, temporal, expected): assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TIntInst('1@2019-09-01'), - TIntSeq('[1@2019-09-01]')), + (TIntInst('1@2019-09-01'), TInterpolation.STEPWISE, + TIntSeq('[1@2019-09-01]')), (TIntSeq('{1@2019-09-01, 2@2019-09-02}'), - TIntSeq('{1@2019-09-01, 2@2019-09-02}')), + TInterpolation.DISCRETE, + TIntSeq('{1@2019-09-01, 2@2019-09-02}')), (TIntSeq('[1@2019-09-01, 2@2019-09-02]'), - TIntSeq('[1@2019-09-01, 2@2019-09-02]')), + TInterpolation.STEPWISE, + TIntSeq('[1@2019-09-01, 2@2019-09-02]')), (TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}'), - TIntSeq('[1@2019-09-01, 2@2019-09-02]')), + TInterpolation.STEPWISE, + TIntSeq('[1@2019-09-01, 2@2019-09-02]')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequence(self, temporal, expected): - temp = temporal.to_sequence() + def test_to_sequence(self, temporal, interpolation, expected): + temp = temporal.to_sequence(interpolation) assert isinstance(temp, TIntSeq) assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TIntInst('1@2019-09-01'), - TIntSeqSet('{[1@2019-09-01]}')), + (TIntInst('1@2019-09-01'), TInterpolation.STEPWISE, + TIntSeqSet('{[1@2019-09-01]}')), (TIntSeq('{1@2019-09-01, 2@2019-09-02}'), - TIntSeqSet('{[1@2019-09-01], [2@2019-09-02]}')), + TInterpolation.STEPWISE, + TIntSeqSet('{[1@2019-09-01], [2@2019-09-02]}')), (TIntSeq('[1@2019-09-01, 2@2019-09-02]'), - TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}')), + TInterpolation.STEPWISE, + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}')), (TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}'), - TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}')), + TInterpolation.STEPWISE, + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequenceset(self, temporal, expected): - temp = temporal.to_sequenceset() + def test_to_sequenceset(self, temporal, interpolation, expected): + temp = temporal.to_sequenceset(interpolation) assert isinstance(temp, TIntSeqSet) assert temp == expected @@ -888,17 +894,17 @@ def test_to_sequenceset(self, temporal, expected): 'temporal, interpolation, expected', [ (tii, TInterpolation.DISCRETE, - TIntSeq('{1@2019-09-01}')), + TIntSeq('{1@2019-09-01}')), (tids, TInterpolation.DISCRETE, tids), (tis_d, TInterpolation.DISCRETE, - TIntSeq('{1@2019-09-01}')), + TIntSeq('{1@2019-09-01}')), (tiss_d, TInterpolation.DISCRETE, - TIntSeq('{1@2019-09-01,2@2019-09-03}')), + TIntSeq('{1@2019-09-01,2@2019-09-03}')), - (tii, TInterpolation.STEPWISE, - TIntSeq('[1@2019-09-01]')), - (tids, TInterpolation.STEPWISE, - TIntSeqSet('{[1@2019-09-01], [2@2019-09-02]}')), + (tii, TInterpolation.STEPWISE, + TIntSeq('[1@2019-09-01]')), + (tids, TInterpolation.STEPWISE, + TIntSeqSet('{[1@2019-09-01], [2@2019-09-02]}')), (tis, TInterpolation.STEPWISE, tis), (tiss, TInterpolation.STEPWISE, tiss), ], @@ -908,12 +914,69 @@ def test_to_sequenceset(self, temporal, expected): def test_set_interpolation(self, temporal, interpolation, expected): assert temporal.set_interpolation(interpolation) == expected + @pytest.mark.parametrize( + 'tint, delta, expected', + [(tii, 2, TIntInst('3@2019-09-01')), + (tii, -2, TIntInst('-1@2019-09-01')), + (tids, 2, TIntSeq('{3@2019-09-01, 4@2019-09-02}')), + (tids, -2, TIntSeq('{-1@2019-09-01, 0@2019-09-02}')), + (tis, 2, TIntSeq('[3@2019-09-01, 4@2019-09-02]')), + (tis, -2, TIntSeq('[-1@2019-09-01, 0@2019-09-02]')), + (tiss, 2, TIntSeqSet('{[3@2019-09-01, 4@2019-09-02],' + '[3@2019-09-03, 3@2019-09-05]}')), + (tiss, -2, TIntSeqSet('{[-1@2019-09-01, 0@2019-09-02],' + '[-1@2019-09-03, -1@2019-09-05]}')), + ], + ids=['Instant positive', 'Instant negative', + 'Discrete Sequence positive', 'Discrete Sequence negative', + 'Sequence positive', 'Sequence negative', + 'Sequence Set positive', 'Sequence Set negative', + ] + ) + def test_shift_value(self, tint, delta, expected): + assert tint.shift_value(delta) == expected + + @pytest.mark.parametrize( + 'tint, width, expected', + [(tii, 3, TIntInst('1@2019-09-01')), + (tids, 3, TIntSeq('{1@2019-09-01, 4@2019-09-02}')), + (tis, 3, TIntSeq('[1@2019-09-01, 4@2019-09-02]')), + (tiss, 3, TIntSeqSet('{[1@2019-09-01, 4@2019-09-02],' + '[1@2019-09-03, 1@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set', ] + ) + def test_scale_value(self, tint, width, expected): + assert tint.scale_value(width) == expected + + @pytest.mark.parametrize( + 'tint, delta, width, expected', + [(tii, 2, 3, TIntInst('3@2019-09-01')), + (tii, -2, 3, TIntInst('-1@2019-09-01')), + (tids, 2, 3, TIntSeq('{3@2019-09-01, 6@2019-09-02}')), + (tids, -2, 3, TIntSeq('{-1@2019-09-01, 2@2019-09-02}')), + (tis, 2, 3, TIntSeq('[3@2019-09-01, 6@2019-09-02]')), + (tis, -2, 3, TIntSeq('[-1@2019-09-01, 2@2019-09-02]')), + (tiss, 2, 3, TIntSeqSet('{[3@2019-09-01, 6@2019-09-02],' + '[3@2019-09-03, 3@2019-09-05]}')), + (tiss, -2, 3, TIntSeqSet('{[-1@2019-09-01, 2@2019-09-02],' + '[-1@2019-09-03, -1@2019-09-05]}')), + ], + ids=['Instant positive', 'Instant negative', + 'Discrete Sequence positive', 'Discrete Sequence negative', + 'Sequence positive', 'Sequence negative', + 'Sequence Set positive', 'Sequence Set negative', + ] + ) + def test_shift_scale_value(self, tint, delta, width, expected): + assert tint.shift_scale_value(delta, width) == expected + @pytest.mark.parametrize( 'tint, delta, expected', [(tii, timedelta(days=4), TIntInst('1@2019-09-05')), (tii, timedelta(days=-4), TIntInst('1@2019-08-28')), (tii, timedelta(hours=2), TIntInst('1@2019-09-01 02:00:00')), - (tii, timedelta(hours=-2), TIntInst('1@2019-08-31 22:00:00')), + (tii, timedelta(hours=-2), TIntInst('1@2019-08-31 22:00:00')), (tids, timedelta(days=4), TIntSeq('{1@2019-09-05, 2@2019-09-06}')), (tids, timedelta(days=-4), TIntSeq('{1@2019-08-28, 2@2019-08-29}')), (tids, timedelta(hours=2), TIntSeq('{1@2019-09-01 02:00:00, 2@2019-09-02 02:00:00}')), @@ -923,27 +986,27 @@ def test_set_interpolation(self, temporal, interpolation, expected): (tis, timedelta(hours=2), TIntSeq('[1@2019-09-01 02:00:00, 2@2019-09-02 02:00:00]')), (tis, timedelta(hours=-2), TIntSeq('[1@2019-08-31 22:00:00, 2@2019-09-01 22:00:00]')), (tiss, timedelta(days=4), - TIntSeqSet('{[1@2019-09-05, 2@2019-09-06],[1@2019-09-07, 1@2019-09-09]}')), + TIntSeqSet('{[1@2019-09-05, 2@2019-09-06],[1@2019-09-07, 1@2019-09-09]}')), (tiss, timedelta(days=-4), - TIntSeqSet('{[1@2019-08-28, 2@2019-08-29],[1@2019-08-30, 1@2019-09-01]}')), + TIntSeqSet('{[1@2019-08-28, 2@2019-08-29],[1@2019-08-30, 1@2019-09-01]}')), (tiss, timedelta(hours=2), - TIntSeqSet('{[1@2019-09-01 02:00:00, 2@2019-09-02 02:00:00],' - '[1@2019-09-03 02:00:00, 1@2019-09-05 02:00:00]}')), + TIntSeqSet('{[1@2019-09-01 02:00:00, 2@2019-09-02 02:00:00],' + '[1@2019-09-03 02:00:00, 1@2019-09-05 02:00:00]}')), (tiss, timedelta(hours=-2), - TIntSeqSet('{[1@2019-08-31 22:00:00, 2@2019-09-01 22:00:00],' - '[1@2019-09-02 22:00:00, 1@2019-09-04 22:00:00]}')), + TIntSeqSet('{[1@2019-08-31 22:00:00, 2@2019-09-01 22:00:00],' + '[1@2019-09-02 22:00:00, 1@2019-09-04 22:00:00]}')), ], ids=['Instant positive days', 'Instant negative days', 'Instant positive hours', 'Instant negative hours', - 'Discrete Sequence positive days', 'Discrete Sequence negative days', + 'Discrete Sequence positive days', 'Discrete Sequence negative days', 'Discrete Sequence positive hours', 'Discrete Sequence negative hours', - 'Sequence positive days', 'Sequence negative days', + 'Sequence positive days', 'Sequence negative days', 'Sequence positive hours', 'Sequence negative hours', - 'Sequence Set positive days', 'Sequence Set negative days', + 'Sequence Set positive days', 'Sequence Set negative days', 'Sequence Set positive hours', 'Sequence Set negative hours'] ) - def test_shift(self, tint, delta, expected): - assert tint.shift(delta) == expected + def test_shift_time(self, tint, delta, expected): + assert tint.shift_time(delta) == expected @pytest.mark.parametrize( 'tint, delta, expected', @@ -954,23 +1017,23 @@ def test_shift(self, tint, delta, expected): (tis, timedelta(days=4), TIntSeq('[1@2019-09-01, 2@2019-09-05]')), (tis, timedelta(hours=2), TIntSeq('[1@2019-09-01 00:00:00, 2@2019-09-01 02:00:00]')), (tiss, timedelta(days=4), - TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), (tiss, timedelta(hours=2), - TIntSeqSet('{[1@2019-09-01 00:00:00, 2@2019-09-01 00:30:00],' - '[1@2019-09-01 01:00:00, 1@2019-09-01 02:00:00]}')), - ], + TIntSeqSet('{[1@2019-09-01 00:00:00, 2@2019-09-01 00:30:00],' + '[1@2019-09-01 01:00:00, 1@2019-09-01 02:00:00]}')), + ], ids=['Instant positive days', 'Instant positive hours', 'Discrete Sequence positive days', 'Discrete Sequence positive hours', 'Sequence positive days', 'Sequence positive hours', 'Sequence Set positive days', 'Sequence Set positive hours'] ) - def test_scale(self, tint, delta, expected): - assert tint.tscale(delta) == expected + def test_scale_time(self, tint, delta, expected): + assert tint.scale_time(delta) == expected - def test_shift_tscale(self): - assert self.tiss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ - TIntSeqSet('{[1@2019-09-05 00:00:00, 2@2019-09-05 00:30:00],' - '[1@2019-09-05 01:00:00, 1@2019-09-05 02:00:00]}') + def test_shift_scale_time(self): + assert self.tiss.shift_scale_time(timedelta(days=4), timedelta(hours=2)) == \ + TIntSeqSet('{[1@2019-09-05 00:00:00, 2@2019-09-05 00:30:00],' + '[1@2019-09-05 01:00:00, 1@2019-09-05 02:00:00]}') @pytest.mark.parametrize( 'tint, delta, expected', @@ -981,11 +1044,11 @@ def test_shift_tscale(self): (tis, timedelta(days=4), TIntSeq('{1@2019-09-01}')), (tis, timedelta(hours=12), TIntSeq('{1@2019-09-01, 1@2019-09-01 12:00:00, 2@2019-09-02}')), (tiss, timedelta(days=4), - TIntSeq('{1@2019-09-01,1@2019-09-05}')), + TIntSeq('{1@2019-09-01,1@2019-09-05}')), (tiss, timedelta(hours=12), - TIntSeq('{1@2019-09-01, 1@2019-09-01 12:00:00, 2@2019-09-02,' - '1@2019-09-03, 1@2019-09-03 12:00:00, 1@2019-09-04, ' - '1@2019-09-04 12:00:00, 1@2019-09-05}')), + TIntSeq('{1@2019-09-01, 1@2019-09-01 12:00:00, 2@2019-09-02,' + '1@2019-09-03, 1@2019-09-03 12:00:00, 1@2019-09-04, ' + '1@2019-09-04 12:00:00, 1@2019-09-05}')), ], ids=['Instant days', 'Instant hours', 'Discrete Sequence days', 'Discrete Sequence hours', @@ -993,7 +1056,7 @@ def test_shift_tscale(self): 'Sequence Set days', 'Sequence Set hours'] ) def test_temporal_sample(self, tint, delta, expected): - assert tint.temporal_sample(delta) == expected + assert tint.temporal_sample(delta, '2019-09-01') == expected @pytest.mark.parametrize( 'temporal, expected', @@ -1008,7 +1071,7 @@ def test_temporal_sample(self, tint, delta, expected): def test_to_tfloat(self, temporal, expected): assert temporal.to_tfloat() == expected - + class TestTIntModifications(TestTInt): tii = TIntInst('1@2019-09-01') tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') @@ -1022,7 +1085,7 @@ class TestTIntModifications(TestTInt): (tids, TIntSeq('{1@2019-09-03}'), TIntSeq('{1@2019-09-01, 2@2019-09-02, 1@2019-09-03}')), (tis, TIntSeq('[1@2019-09-03]'), TIntSeqSet('{[1@2019-09-01, 2@2019-09-02, 1@2019-09-03]}')), (tiss, TIntSeq('[1@2019-09-06]'), - TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05],[1@2019-09-06]}')), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05],[1@2019-09-06]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1034,10 +1097,10 @@ def test_insert(self, temporal, sequence, expected): [ (tii, TIntInst('2@2019-09-01'), TIntInst('2@2019-09-01')), (tids, TIntInst('2@2019-09-01'), TIntSeq('{2@2019-09-01, 2@2019-09-02}')), - (tis, TIntInst('2@2019-09-01'), - TIntSeqSet('{[2@2019-09-01], (1@2019-09-01, 2@2019-09-02]}')), + (tis, TIntInst('2@2019-09-01'), + TIntSeqSet('{[2@2019-09-01], (1@2019-09-01, 2@2019-09-02]}')), (tiss, TIntInst('2@2019-09-01'), - TIntSeqSet('{[2@2019-09-01], (1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + TIntSeqSet('{[2@2019-09-01], (1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1051,9 +1114,9 @@ def test_update(self, temporal, instant, expected): (tii, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), tii), (tids, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), TIntSeq('{2@2019-09-02}')), (tis, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), - TIntSeqSet('{(1@2019-09-01, 2@2019-09-02]}')), + TIntSeqSet('{(1@2019-09-01, 2@2019-09-02]}')), (tiss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), - TIntSeqSet('{(1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + TIntSeqSet('{(1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), ], ids=['Instant intersection', 'Instant disjoint', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1067,7 +1130,7 @@ def test_delete(self, temporal, time, expected): (tids, TIntInst('1@2019-09-03'), TIntSeq('{1@2019-09-01, 2@2019-09-02, 1@2019-09-03}')), (tis, TIntInst('1@2019-09-03'), TIntSeq('[1@2019-09-01, 2@2019-09-02, 1@2019-09-03]')), (tiss, TIntInst('1@2019-09-06'), - TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-06]}')), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-06]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1080,7 +1143,7 @@ def test_append_instant(self, temporal, instant, expected): (tids, TIntSeq('{1@2019-09-03}'), TIntSeq('{1@2019-09-01, 2@2019-09-02, 1@2019-09-03}')), (tis, TIntSeq('[1@2019-09-03]'), TIntSeqSet('{[1@2019-09-01, 2@2019-09-02], [1@2019-09-03]}')), (tiss, TIntSeq('[1@2019-09-06]'), - TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05],[1@2019-09-06]}')), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05],[1@2019-09-06]}')), ], ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] ) @@ -1226,7 +1289,7 @@ def test_temporal_div_temporal(self, temporal, argument, expected): ids=['Instant 1', 'Discrete Sequence 1', 'Sequence 1', 'SequenceSet 1', 'Instant 2', 'Discrete Sequence 2', 'Sequence 2', 'SequenceSet 2'] ) - def test_temporal_div_int_float (self, temporal, argument, expected): + def test_temporal_div_int_float(self, temporal, argument, expected): assert temporal.div(argument) == expected assert (temporal / argument) == expected @@ -1289,16 +1352,16 @@ class TestTIntRestrictors(TestTInt): (tiss, timestamp_set, TIntSeq('{1@2019-09-01, 1@2019-09-03}')), (tiss, period, TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}')), (tiss, period_set, - TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), ], ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', - 'Sequence-PeriodSet', + 'Sequence-PeriodSet', 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', - 'SequenceSet-Period', 'SequenceSet-PeriodSet', + 'SequenceSet-Period', 'SequenceSet-PeriodSet', ] ) def test_at_time(self, temporal, restrictor, expected): @@ -1309,39 +1372,44 @@ def test_at_time(self, temporal, restrictor, expected): [ (tii, 1, TIntInst('1@2019-09-01')), (tii, 2, None), - (tii, intrange(1, 1, True, True), TIntInst('1@2019-09-01')), - # (tii, [1,2], TIntInst('1@2019-09-01')), - # (tii, [intrange(1, 1, True, True), intrange(2, 2, True, True)], - # TIntInst('1@2019-09-01')), + (tii, IntSet(elements=[1, 2]), tii), + (tii, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), TIntInst('1@2019-09-01')), + (tii, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)]), + TIntInst('1@2019-09-01')), (tids, 1, TIntSeq('{1@2019-09-01}')), (tids, 2, TIntSeq('{2@2019-09-02}')), - (tids, intrange(1, 1, True, True), TIntSeq('{1@2019-09-01}')), - # (tids, [1,2], TIntSeq('{2@2019-09-02}')), - # (tids, [intrange(1, 1, True, True), tids), + (tids, IntSet(elements=[1, 2]), tids), + (tids, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), TIntSeq('{1@2019-09-01}')), + (tids, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)]), tids), (tis, 1, TIntSeq('[1@2019-09-01, 1@2019-09-02)')), (tis, 2, TIntSeq('[2@2019-09-02]')), - (tis, intrange(1, 1, True, True), TIntSeq('[1@2019-09-01, 1@2019-09-02)')), - # (tis, [1,2], TIntSeq('[2@2019-09-02]')), - # (tis, [intrange(1, 1, True, True), intrange(2, 2, True, True)], - # TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}')), + (tis, IntSet(elements=[1, 2]), tis), + (tis, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), TIntSeq('[1@2019-09-01, 1@2019-09-02)')), + (tis, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)]), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}')), (tiss, 1, TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}')), (tiss, 2, TIntSeqSet('{[2@2019-09-02]}')), - (tiss, intrange(1, 1, True, True), TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}')), - # (tiss, [1,2], TIntSeqSet('{[2@2019-09-02]}')) - # (tiss, [intrange(1, 1, True, True), intrange(2, 2, True, True)], - # tiss), - ], - ids=['Instant-1', 'Instant-2', 'Instant-Range', - # 'Instant-ValueList', 'Instant-RangeList', - 'Discrete Sequence-1', 'Discrete Sequence-2', 'Discrete Sequence-Range', - # 'Discrete Sequence-ValueList', 'Discrete Sequence-RangeList', - 'Sequence-1', 'Sequence-2', 'Sequence-Range', - # 'Sequence-ValueList', 'Sequence-RangeList', - 'SequenceSet-1', 'SequenceSet-2', 'SequenceSet-Range', - # 'SequenceSet-ValueList', 'SequenceSet-RangeList' + (tiss, IntSet(elements=[1, 2]), tiss), + (tiss, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}')), + (tiss, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)]), + tiss), + ], + ids=['Instant-1', 'Instant-2', 'Instant-Set', + 'Instant-Span', 'Instant-SpanSet', + 'Discrete Sequence-1', 'Discrete Sequence-2', 'Discrete Sequence-Set', + 'Discrete Sequence-Span', 'Discrete Sequence-SpanSet', + 'Sequence-1', 'Sequence-2', 'Sequence-Set', + 'Sequence-Span', 'Sequence-SpanSet', + 'SequenceSet-1', 'SequenceSet-2', 'SequenceSet-Set', + 'SequenceSet-Span', 'SequenceSet-SpanSet' ] ) def test_at_values(self, temporal, restrictor, expected): @@ -1360,7 +1428,6 @@ def test_at_values(self, temporal, restrictor, expected): def test_at_min(self, temporal, expected): assert temporal.at_min() == expected - @pytest.mark.parametrize( 'temporal, expected', [ @@ -1393,20 +1460,20 @@ def test_at_max(self, temporal, expected): (tis, period_set, None), (tiss, timestamp, - TIntSeqSet('{(1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + TIntSeqSet('{(1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), (tiss, timestamp_set, TIntSeqSet('{(1@2019-09-01, 2@2019-09-02],(1@2019-09-03, 1@2019-09-05]}')), (tiss, period, TIntSeqSet('{[1@2019-09-03, 1@2019-09-05]}')), (tiss, period_set, None), ], ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', - 'Instant-PeriodSet', + 'Instant-PeriodSet', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', - 'Sequence-PeriodSet', + 'Sequence-PeriodSet', 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', - 'SequenceSet-PeriodSet', + 'SequenceSet-PeriodSet', ] ) def test_minus_time(self, temporal, restrictor, expected): @@ -1417,44 +1484,51 @@ def test_minus_time(self, temporal, restrictor, expected): [ (tii, 1, None), (tii, 2, TIntInst('1@2019-09-01')), - (tii, intrange(1, 1, True, True), None), - # (tii, [1,2], None), - # (tii, [intrange(1, 1, True, True), intrange(2, 2, True, True)], - # None), + (tii, IntSet(elements=[1, 2]), None), + (tii, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), None), + (tii, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)]), + None), (tids, 1, TIntSeq('{2@2019-09-02}')), (tids, 2, TIntSeq('{1@2019-09-01}')), - (tids, intrange(1, 1, True, True), TIntSeq('{2@2019-09-02}')), - # (tids, [1,2], tids), - # (tids, [intrange(1, 1, True, True), intrange(2, 2, True, True)], - # None), + (tids, IntSet(elements=[1, 2]), None), + (tids, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), TIntSeq('{2@2019-09-02}')), + (tids, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)]), + None), (tis, 1, TIntSeqSet('{[2@2019-09-02]}')), (tis, 2, TIntSeqSet('{[1@2019-09-01, 1@2019-09-02)}')), - (tis, intrange(1, 1, True, True), TIntSeqSet('{[2@2019-09-02]}')), - # (tis, [1,2], tis), - # (tis, [intrange(1, 1, True, True), intrange(2, 2, True, True)], - # None), + (tis, IntSet(elements=[1, 2]), None), + (tis, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), TIntSeqSet('{[2@2019-09-02]}')), + (tis, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)]), + None), (tiss, 1, TIntSeqSet('{[2@2019-09-02]}')), (tiss, 2, TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}')), - (tis, intrange(1, 1, True, True), TIntSeqSet('{[2@2019-09-02]}')), - # (tiss, [1,2], tiss) - # (tiss, [intrange(1, 1, True, True), intrange(2, 2, True, True)], - # None), - ], - ids=['Instant-1', 'Instant-2', 'Instant-Range', - # 'Instant-ValueList', 'Instant-RangeList', - 'Discrete Sequence-1', 'Discrete Sequence-2', 'Discrete Sequence-Range', - # 'Discrete Sequence-ValueList', 'Discrete Sequence-RangeList', - 'Sequence-1', 'Sequence-2', 'Sequence-Range', - # 'Sequence-ValueList', 'Sequence-RangeList', - 'SequenceSet-1', 'SequenceSet-2', 'SequenceSet-Range', - # 'SequenceSet-ValueList', 'SequenceSet-RangeList', + (tiss, IntSet(elements=[1, 2]), None), + (tis, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), TIntSeqSet('{[2@2019-09-02]}')), + (tiss, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)]), + None), + ], + ids=['Instant-1', 'Instant-2', 'Instant-Set', + 'Instant-Span', 'Instant-SpanSet', + 'Discrete Sequence-1', 'Discrete Sequence-2', 'Discrete Sequence-Set', + 'Discrete Sequence-Span', 'Discrete Sequence-SpanSet', + 'Sequence-1', 'Sequence-2', 'Sequence-Set', + 'Sequence-Span', 'Sequence-SpanSet', + 'SequenceSet-1', 'SequenceSet-2', 'SequenceSet-Set', + 'SequenceSet-Span', 'SequenceSet-SpanSet' ] ) def test_minus_values(self, temporal, restrictor, expected): - assert temporal.minus(restrictor) == expected + if expected is None: + assert temporal.minus(restrictor) is None + else: + assert temporal.minus(restrictor) == expected @pytest.mark.parametrize( 'temporal, expected', @@ -1491,6 +1565,10 @@ def test_minus_min(self, temporal, expected): (tii, period_set), (tii, 1), (tii, 2), + (tii, IntSet(elements=[1, 2])), + (tii, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True)), + (tii, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)])), (tids, timestamp), (tids, timestamp_set), @@ -1498,6 +1576,10 @@ def test_minus_min(self, temporal, expected): (tids, period_set), (tids, 1), (tids, 2), + (tids, IntSet(elements=[1, 2])), + (tids, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True)), + (tids, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)])), (tis, timestamp), (tis, timestamp_set), @@ -1505,6 +1587,10 @@ def test_minus_min(self, temporal, expected): (tis, period_set), (tis, 1), (tis, 2), + (tis, IntSet(elements=[1, 2])), + (tis, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True)), + (tis, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)])), (tiss, timestamp), (tiss, timestamp_set), @@ -1512,15 +1598,26 @@ def test_minus_min(self, temporal, expected): (tiss, period_set), (tiss, 1), (tiss, 2), - + (tiss, IntSet(elements=[1, 2])), + (tiss, IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True)), + (tiss, IntSpanSet(span_list=[IntSpan(lower=1, upper=1, lower_inc=True, upper_inc=True), + IntSpan(lower=2, upper=2, lower_inc=True, upper_inc=True)])), ], - ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-1', - 'Instant-2', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', + 'Instant-PeriodSet', 'Instant-1', 'Instant-2', + 'Instant-Set', 'Instant-Span', 'Instant-SpanSet', + 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-1', - 'Discrete Sequence-2', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', - 'Sequence-PeriodSet', 'Sequence-1', 'Sequence-2', 'SequenceSet-Timestamp', - 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-1', - 'SequenceSet-2'] + 'Discrete Sequence-2', 'Discrete Sequence-Set', + 'Discrete Sequence-Span', 'Discrete Sequence-SpanSet', + 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-1', 'Sequence-2', + 'Sequence-Set', 'Sequence-Span', 'Sequence-SpanSet', + 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', + 'SequenceSet-Period', 'SequenceSet-PeriodSet', + 'SequenceSet-1', 'SequenceSet-2', + 'SequenceSet-Set', 'SequenceSet-Span', 'SequenceSet-SpanSet', + ] ) def test_at_minus(self, temporal, restrictor): assert TInt.merge(temporal.at(restrictor), temporal.minus(restrictor)) == temporal @@ -1808,17 +1905,18 @@ class TestTIntSplitOperations(TestTInt): 'temporal, expected', [ (tii, [TIntInst('1@2019-09-01')]), - (tids, [TIntSeq('{1@2019-09-01}'),TIntSeq('{2@2019-09-02}')]), - (tis, [TIntSeq('[1@2019-09-01, 1@2019-09-02)'),TIntSeq('[2@2019-09-02]')]), - (tiss, [TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}'),TIntSeq('[2@2019-09-02]')]), + (tids, [TIntSeq('{1@2019-09-01}'), TIntSeq('{2@2019-09-02}')]), + (tis, [TIntSeq('[1@2019-09-01, 1@2019-09-02)'), TIntSeq('[2@2019-09-02]')]), + (tiss, + [TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}'), TIntSeq('[2@2019-09-02]')]), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_value_split(self, temporal, expected): assert temporal.value_split(2) == expected - # The PyMEOS function uses as default origin the initial timestamp of the - # temporal value while in MEOS the default origin is Monday Janury 3, 2000 + ## The PyMEOS function uses as default origin the initial timestamp of the + ## temporal value while in MEOS the default origin is Monday Janury 3, 2000 @pytest.mark.parametrize( 'temporal, expected', [ @@ -1826,12 +1924,28 @@ def test_value_split(self, temporal, expected): (tids, [TIntSeq('{1@2019-09-01,2@2019-09-02}')]), (tis, [TIntSeq('[1@2019-09-01, 2@2019-09-02]')]), (tiss, [TIntSeq('[1@2019-09-01,2@2019-09-02]'), - TIntSeq('[1@2019-09-03, 1@2019-09-05]')]), + TIntSeq('[1@2019-09-03, 1@2019-09-05]')]), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_time_split(self, temporal, expected): - assert temporal.time_split(timedelta(days=2)) == expected + assert temporal.time_split(timedelta(days=2), '2019-09-01') == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, [TIntInst('1@2019-09-01')]), + (tids, [TIntSeq('{1@2019-09-01}'), TIntSeq('{2@2019-09-02}')]), + (tis, [TIntSeq('[1@2019-09-01, 1@2019-09-02)'), + TIntSeq('[2@2019-09-02]')]), + (tiss, [TIntSeq('{[1@2019-09-01, 1@2019-09-02)}'), + TIntSeq('{[1@2019-09-03, 1@2019-09-05]}'), + TIntSeq('{[2@2019-09-02]}')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_time_split(self, temporal, expected): + assert temporal.value_time_split(2, timedelta(days=2), 0, '2019-09-01') == expected class TestTIntComparisons(TestTInt): @@ -2046,5 +2160,3 @@ def test_temporal_not_equal_int(self, temporal, expected): assert temporal.temporal_not_equal(1) == expected assert temporal.temporal_not_equal(2) == ~expected - - diff --git a/pymeos/tests/main/ttext_test.py b/pymeos/tests/main/ttext_test.py index 20df2c0c..9e99adef 100644 --- a/pymeos/tests/main/ttext_test.py +++ b/pymeos/tests/main/ttext_test.py @@ -82,7 +82,7 @@ def test_string_constructor(self, source, type, interpolation, expected): ids=['Sequence', 'SequenceSet'] ) def test_string_constructor_normalization(self, source, type, expected): - tt = type(source, normalize=1) + tt = type(source, normalize=True) assert isinstance(tt, type) assert str(tt) == expected @@ -141,6 +141,7 @@ def test_instant_list_sequence_constructor(self, list, interpolation, normalize, ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) def test_from_as_constructor(self, temporal): + assert temporal == temporal.__class__(str(temporal)) assert temporal == temporal.from_wkb(temporal.as_wkb()) assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) assert temporal == temporal.from_mfjson(temporal.as_mfjson()) @@ -747,40 +748,46 @@ def test_to_instant(self, temporal, expected): assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TTextInst('AAA@2019-09-01'), + (TTextInst('AAA@2019-09-01'), TInterpolation.STEPWISE, TTextSeq('[AAA@2019-09-01]')), (TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}'), + TInterpolation.DISCRETE, TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}')), (TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]'), + TInterpolation.STEPWISE, TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]')), (TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02]}'), + TInterpolation.STEPWISE, TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequence(self, temporal, expected): - temp = temporal.to_sequence() + def test_to_sequence(self, temporal, interpolation, expected): + temp = temporal.to_sequence(interpolation) assert isinstance(temp, TTextSeq) assert temp == expected @pytest.mark.parametrize( - 'temporal, expected', + 'temporal, interpolation, expected', [ - (TTextInst('AAA@2019-09-01'), + (TTextInst('AAA@2019-09-01'), TInterpolation.STEPWISE, TTextSeqSet('{[AAA@2019-09-01]}')), (TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}'), + TInterpolation.STEPWISE, TTextSeqSet('{[AAA@2019-09-01], [BBB@2019-09-02]}')), (TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]'), + TInterpolation.STEPWISE, TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02]}')), (TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02]}'), + TInterpolation.STEPWISE, TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02]}')), ], ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] ) - def test_to_sequenceset(self, temporal, expected): - temp = temporal.to_sequenceset() + def test_to_sequenceset(self, temporal, interpolation, expected): + temp = temporal.to_sequenceset(interpolation) assert isinstance(temp, TTextSeqSet) assert temp == expected @@ -818,8 +825,8 @@ def test_to_sequenceset(self, temporal, expected): 'Sequence Set positive days', 'Sequence Set negative days', 'Sequence Set positive hours', 'Sequence Set negative hours'] ) - def test_shift(self, ttext, delta, expected): - assert ttext.shift(delta) == expected + def test_shift_time(self, ttext, delta, expected): + assert ttext.shift_time(delta) == expected @pytest.mark.parametrize( 'ttext, delta, expected', @@ -840,11 +847,11 @@ def test_shift(self, ttext, delta, expected): 'Sequence positive days', 'Sequence positive hours', 'Sequence Set positive days', 'Sequence Set positive hours'] ) - def test_scale(self, ttext, delta, expected): - assert ttext.tscale(delta) == expected + def test_scale_time(self, ttext, delta, expected): + assert ttext.scale_time(delta) == expected - def test_shift_tscale(self): - assert self.ttss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ + def test_shift_scale(self): + assert self.ttss.shift_scale_time(timedelta(days=4), timedelta(hours=2)) == \ TTextSeqSet('{[AAA@2019-09-05 00:00:00, BBB@2019-09-05 00:30:00],' '[AAA@2019-09-05 01:00:00, AAA@2019-09-05 02:00:00]}') @@ -869,7 +876,7 @@ def test_shift_tscale(self): 'Sequence Set days', 'Sequence Set hours'] ) def test_temporal_sample(self, tint, delta, expected): - assert tint.temporal_sample(delta) == expected + assert tint.temporal_sample(delta, '2019-09-01') == expected class TestTTextModifications(TestTText): @@ -1041,7 +1048,7 @@ class TestTTextRestrictors(TestTText): (tti, period_set, TTextInst('AAA@2019-09-01')), (tti, 'AAA', TTextInst('AAA@2019-09-01')), (tti, 'BBB', None), - # (tti, [AAA,BBB], None), + (tti, ['AAA', 'BBB'], tti), (ttds, timestamp, TTextSeq('{AAA@2019-09-01}')), (ttds, timestamp_set, TTextSeq('{AAA@2019-09-01}')), @@ -1049,7 +1056,7 @@ class TestTTextRestrictors(TestTText): (ttds, period_set, TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}')), (ttds, 'AAA', TTextSeq('{AAA@2019-09-01}')), (ttds, 'BBB', TTextSeq('{BBB@2019-09-02}')), - # (ttds, [AAA,BBB], TTextSeq('{BBB@2019-09-02}')), + (ttds, ['AAA', 'BBB'], ttds), (tts, timestamp, TTextSeq('[AAA@2019-09-01]')), (tts, timestamp_set, TTextSeq('{AAA@2019-09-01}')), @@ -1057,7 +1064,7 @@ class TestTTextRestrictors(TestTText): (tts, period_set, TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]')), (tts, 'AAA', TTextSeq('[AAA@2019-09-01, AAA@2019-09-02)')), (tts, 'BBB', TTextSeq('[BBB@2019-09-02]')), - # (tts, [AAA,BBB], TTextSeq('[BBB@2019-09-02]')), + (tts, ['AAA', 'BBB'], tts), (ttss, timestamp, TTextSeqSet('[AAA@2019-09-01]')), (ttss, timestamp_set, TTextSeq('{AAA@2019-09-01, AAA@2019-09-03}')), @@ -1066,18 +1073,21 @@ class TestTTextRestrictors(TestTText): TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}')), (ttss, 'AAA', TTextSeqSet('{[AAA@2019-09-01, AAA@2019-09-02),[AAA@2019-09-03, AAA@2019-09-05]}')), (ttss, 'BBB', TTextSeqSet('{[BBB@2019-09-02]}')), - # (ttss, [AAA,BBB], TTextSeqSet('{[BBB@2019-09-02]}')) + (ttss, ['AAA', 'BBB'], ttss) ], ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', - 'Instant-PeriodSet', 'Instant-AAA', 'Instant-BBB', # 'Instant-[AAA,BBB]', + 'Instant-PeriodSet', 'Instant-AAA', 'Instant-BBB', + 'Instant-[AAA,BBB]', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', - 'Discrete Sequence-AAA', 'Discrete Sequence-BBB', # 'Discrete Sequence-[AAA,BBB]', + 'Discrete Sequence-AAA', 'Discrete Sequence-BBB', + 'Discrete Sequence-[AAA,BBB]', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', - 'Sequence-PeriodSet', 'Sequence-AAA', 'Sequence-BBB', # 'Sequence-[AAA,BBB]', + 'Sequence-PeriodSet', 'Sequence-AAA', 'Sequence-BBB', + 'Sequence-[AAA,BBB]', 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-AAA', 'SequenceSet-BBB', - # 'SequenceSet-[AAA,BBB]' + 'SequenceSet-[AAA,BBB]' ] ) def test_at(self, temporal, restrictor, expected): @@ -1119,7 +1129,7 @@ def test_at_min(self, temporal, expected): (tti, period_set, None), (tti, 'AAA', None), (tti, 'BBB', TTextInst('AAA@2019-09-01')), - # (tti, [AAA,BBB], None), + (tti, ['AAA', 'BBB'], None), (ttds, timestamp, TTextSeq('{BBB@2019-09-02}')), (ttds, timestamp_set, TTextSeq('{BBB@2019-09-02}')), @@ -1127,7 +1137,7 @@ def test_at_min(self, temporal, expected): (ttds, period_set, None), (ttds, 'AAA', TTextSeq('{BBB@2019-09-02}')), (ttds, 'BBB', TTextSeq('{AAA@2019-09-01}')), - # (ttds, [AAA,BBB], TTextSeq('{BBB@2019-09-02}')), + (ttds, ['AAA', 'BBB'], None), (tts, timestamp, TTextSeqSet('{(AAA@2019-09-01, BBB@2019-09-02]}')), (tts, timestamp_set, TTextSeqSet('{(AAA@2019-09-01, BBB@2019-09-02]}')), @@ -1135,7 +1145,7 @@ def test_at_min(self, temporal, expected): (tts, period_set, None), (tts, 'AAA', TTextSeqSet('{[BBB@2019-09-02]}')), (tts, 'BBB', TTextSeqSet('{[AAA@2019-09-01, AAA@2019-09-02)}')), - # (tts, [AAA,BBB], TTextSeq('[BBB@2019-09-02]')), + (tts, ['AAA', 'BBB'], None), (ttss, timestamp, TTextSeqSet('{(AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}')), @@ -1144,23 +1154,28 @@ def test_at_min(self, temporal, expected): (ttss, period, TTextSeqSet('{[AAA@2019-09-03, AAA@2019-09-05]}')), (ttss, period_set, None), (ttss, 'AAA', TTextSeqSet('{[BBB@2019-09-02]}')), - (ttss, 'BBB', TTextSeqSet('{[AAA@2019-09-01, AAA@2019-09-02),[AAA@2019-09-03, AAA@2019-09-05]}')) - # (ttss, [AAA,BBB], TTextSeqSet('{[BBB@2019-09-02]}')) + (ttss, 'BBB', TTextSeqSet('{[AAA@2019-09-01, AAA@2019-09-02),[AAA@2019-09-03, AAA@2019-09-05]}')), + (ttss, ['AAA', 'BBB'], None), ], ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', - 'Instant-PeriodSet', 'Instant-AAA', 'Instant-BBB', # 'Instant-[AAA,BBB]', + 'Instant-PeriodSet', 'Instant-AAA', 'Instant-BBB', 'Instant-[AAA,BBB]', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', - 'Discrete Sequence-AAA', 'Discrete Sequence-BBB', # 'Discrete Sequence-[AAA,BBB]', + 'Discrete Sequence-AAA', 'Discrete Sequence-BBB', + 'Discrete Sequence-[AAA,BBB]', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', - 'Sequence-PeriodSet', 'Sequence-AAA', 'Sequence-BBB', # 'Sequence-[AAA,BBB]', + 'Sequence-PeriodSet', 'Sequence-AAA', 'Sequence-BBB', + 'Sequence-[AAA,BBB]', 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-AAA', 'SequenceSet-BBB', - # 'SequenceSet-[AAA,BBB]' + 'SequenceSet-[AAA,BBB]' ] ) def test_minus(self, temporal, restrictor, expected): - assert temporal.minus(restrictor) == expected + if expected is None: + assert temporal.minus(restrictor) is None + else: + assert temporal.minus(restrictor) == expected @pytest.mark.parametrize( 'temporal, expected', @@ -1197,7 +1212,7 @@ def test_minus_max(self, temporal, expected): (tti, period_set), (tti, 'AAA'), (tti, 'BBB'), - # (tti, ['AAA','BBB']), + (tti, ['AAA','BBB']), (ttds, timestamp), (ttds, timestamp_set), @@ -1205,7 +1220,7 @@ def test_minus_max(self, temporal, expected): (ttds, period_set), (ttds, 'AAA'), (ttds, 'BBB'), - # (ttds, ['AAA','BBB']), + (ttds, ['AAA','BBB']), (tts, timestamp), (tts, timestamp_set), @@ -1213,7 +1228,7 @@ def test_minus_max(self, temporal, expected): (tts, period_set), (tts, 'AAA'), (tts, 'BBB'), - # (tts, ['AAA','BBB']), + (tts, ['AAA','BBB']), (ttss, timestamp), (ttss, timestamp_set), @@ -1221,18 +1236,18 @@ def test_minus_max(self, temporal, expected): (ttss, period_set), (ttss, 'AAA'), (ttss, 'BBB'), - # (ttss, ['AAA','BBB']), + (ttss, ['AAA','BBB']), ], ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', - 'Instant-PeriodSet', 'Instant-AAA', 'Instant-BBB', # 'Instant-[AAA,BBB]', + 'Instant-PeriodSet', 'Instant-AAA', 'Instant-BBB', 'Instant-[AAA,BBB]', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', - 'Discrete Sequence-AAA', 'Discrete Sequence-BBB', # 'Discrete Sequence-[AAA,BBB]', + 'Discrete Sequence-AAA', 'Discrete Sequence-BBB', 'Discrete Sequence-[AAA,BBB]', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', - 'Sequence-PeriodSet', 'Sequence-AAA', 'Sequence-BBB', # 'Sequence-[AAA,BBB]', + 'Sequence-PeriodSet', 'Sequence-AAA', 'Sequence-BBB', 'Sequence-[AAA,BBB]', 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-AAA', 'SequenceSet-BBB', - # 'SequenceSet-[AAA,BBB]' + 'SequenceSet-[AAA,BBB]' ] ) def test_at_minus(self, temporal, restrictor): diff --git a/pymeos_cffi/README.md b/pymeos_cffi/README.md index 18759190..16f8dc7c 100644 --- a/pymeos_cffi/README.md +++ b/pymeos_cffi/README.md @@ -1,6 +1,6 @@ # PyMEOS CFFI -![MEOS Logo](../doc/images/meos-logo.png) +![MEOS Logo](../docs/images/meos-logo.png) [MEOS (Mobility Engine, Open Source)](https://www.libmeos.org/) is a C library which enables the manipulation of temporal and spatio-temporal data based on [MobilityDB](https://mobilitydb.com/)'s data types and functions. diff --git a/pymeos_cffi/docker/Dockerfile b/pymeos_cffi/docker/Dockerfile index d6cd2a13..803f440d 100644 --- a/pymeos_cffi/docker/Dockerfile +++ b/pymeos_cffi/docker/Dockerfile @@ -2,7 +2,8 @@ FROM pymeos/meos:latest WORKDIR MobilityDB RUN git fetch -RUN git checkout develop +RUN git checkout errmsg +RUN git pull WORKDIR build @@ -12,13 +13,15 @@ RUN make -j RUN make install COPY build_wheels.sh /build_wheels.sh +RUN sed -i -e 's/\r$//' /build_wheels.sh RUN chmod +x /build_wheels.sh RUN rm -rf /opt/python/cp36-cp36m +RUN rm -rf /opt/python/cp312-cp312 RUN mkdir /wheelhouse_int ENV LD_LIBRARY_PATH=/usr/lib64;/lib64 WORKDIR / -CMD ["./build_wheels.sh"] \ No newline at end of file +CMD ["/build_wheels.sh"] \ No newline at end of file diff --git a/pymeos_cffi/docker/MEOS.Dockerfile b/pymeos_cffi/docker/MEOS.Dockerfile index 4b58088e..c80def5c 100644 --- a/pymeos_cffi/docker/MEOS.Dockerfile +++ b/pymeos_cffi/docker/MEOS.Dockerfile @@ -3,4 +3,4 @@ FROM quay.io/pypa/manylinux2014_x86_64 RUN yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm RUN yum -y update RUN yum -y install gcc gcc-c++ make cmake postgresql13-devel proj-devel json-c-devel geos39-devel gsl-devel -RUN git clone https://github.com/MobilityDB/MobilityDB +RUN git clone https://github.com/estebanzimanyi/MobilityDB diff --git a/pymeos_cffi/docker/MobilityDB.Dockerfile b/pymeos_cffi/docker/MobilityDB.Dockerfile new file mode 100644 index 00000000..4a1d064f --- /dev/null +++ b/pymeos_cffi/docker/MobilityDB.Dockerfile @@ -0,0 +1,10 @@ +FROM debian:buster-slim + +RUN apt install build-essential cmake postgresql-server-dev-11 liblwgeom-dev libproj-dev libjson-c-dev libprotobuf-c-dev + +RUN git clone --branch develop https://github.com/MobilityDB/MobilityDB +WORKDIR MobilityDB/build + + + + diff --git a/pymeos_cffi/pymeos_cffi/__init__.py b/pymeos_cffi/pymeos_cffi/__init__.py index 6db82fb3..f35edcd8 100644 --- a/pymeos_cffi/pymeos_cffi/__init__.py +++ b/pymeos_cffi/pymeos_cffi/__init__.py @@ -27,19 +27,19 @@ 'MeosGeoJsonInputError', 'MeosGeoJsonOutputError', # Functions + 'meos_set_debug', + 'py_error_handler', 'create_pointer', 'get_address', 'datetime_to_timestamptz', 'timestamptz_to_datetime', 'timedelta_to_interval', 'interval_to_timedelta', + 'geo_to_gserialized', 'geometry_to_gserialized', + 'geography_to_gserialized', 'gserialized_to_shapely_point', 'gserialized_to_shapely_geometry', - 'intrange_to_intspan', - 'intspan_to_intrange', - 'floatrange_to_floatspan', - 'floatspan_to_floatrange', 'as_tinstant', 'as_tsequence', 'as_tsequenceset', @@ -53,6 +53,10 @@ 'lwpoint_get_m', 'lwgeom_has_z', 'lwgeom_has_m', + 'meos_errno', + 'meos_errno_set', + 'meos_errno_restore', + 'meos_errno_reset', 'meos_initialize', 'meos_finalize', 'bool_in', @@ -65,6 +69,7 @@ 'pg_interval_make', 'pg_interval_mul', 'pg_interval_out', + 'pg_interval_to_char', 'pg_interval_pl', 'pg_time_in', 'pg_time_out', @@ -73,16 +78,17 @@ 'pg_timestamp_mi_interval', 'pg_timestamp_out', 'pg_timestamp_pl_interval', + 'pg_timestamp_to_char', 'pg_timestamptz_in', 'pg_timestamptz_out', - 'text2cstring', - 'pg_timestamp_to_char', 'pg_timestamptz_to_char', - 'pg_interval_to_char', - 'pg_to_timestamp', 'pg_to_date', + 'pg_to_timestamp', + 'text2cstring', 'geography_from_hexewkb', + 'geography_from_text', 'geometry_from_hexewkb', + 'geometry_from_text', 'gserialized_as_ewkb', 'gserialized_as_ewkt', 'gserialized_as_geojson', @@ -91,8 +97,6 @@ 'gserialized_from_ewkb', 'gserialized_from_geojson', 'gserialized_out', - 'geometry_from_text', - 'geography_from_text', 'pgis_geography_in', 'pgis_geometry_in', 'pgis_gserialized_same', @@ -109,10 +113,10 @@ 'floatspanset_in', 'floatspanset_out', 'geogset_in', - 'geoset_out', 'geomset_in', 'geoset_as_ewkt', 'geoset_as_text', + 'geoset_out', 'intset_in', 'intset_out', 'intspan_in', @@ -127,17 +131,14 @@ 'set_as_wkb', 'set_from_hexwkb', 'set_from_wkb', - 'set_out', - 'span_as_wkb', 'span_as_hexwkb', + 'span_as_wkb', 'span_from_hexwkb', 'span_from_wkb', - 'span_out', - 'spanset_as_wkb', 'spanset_as_hexwkb', + 'spanset_as_wkb', 'spanset_from_hexwkb', 'spanset_from_wkb', - 'spanset_out', 'textset_in', 'textset_out', 'timestampset_in', @@ -154,7 +155,6 @@ 'span_copy', 'spanset_copy', 'spanset_make', - 'spanset_make_exp', 'textset_make', 'timestampset_make', 'bigint_to_bigintset', @@ -215,7 +215,6 @@ 'periodset_upper', 'set_hash', 'set_hash_extended', - 'set_mem_size', 'set_num_values', 'set_span', 'span_hash', @@ -227,7 +226,6 @@ 'spanset_hash', 'spanset_hash_extended', 'spanset_lower_inc', - 'spanset_mem_size', 'spanset_num_spans', 'spanset_span', 'spanset_span_n', @@ -244,16 +242,100 @@ 'timestampset_start_timestamp', 'timestampset_timestamp_n', 'timestampset_values', + 'bigintset_shift_scale', + 'bigintspan_shift_scale', + 'bigintspanset_shift_scale', 'floatset_round', + 'floatset_shift_scale', + 'floatspan_intspan', 'floatspan_round', + 'floatspan_shift_scale', + 'floatspanset_intspanset', 'floatspanset_round', + 'floatspanset_shift_scale', 'geoset_round', + 'intset_shift_scale', + 'intspan_floatspan', + 'intspan_shift_scale', + 'intspanset_floatspanset', + 'intspanset_shift_scale', + 'period_shift_scale', 'period_tprecision', + 'periodset_shift_scale', 'periodset_tprecision', - 'period_shift_tscale', - 'periodset_shift_tscale', + 'textset_lower', + 'textset_upper', 'timestamp_tprecision', - 'timestampset_shift_tscale', + 'timestampset_shift_scale', + 'intersection_bigintset_bigint', + 'intersection_bigintspan_bigint', + 'intersection_bigintspanset_bigint', + 'intersection_floatset_float', + 'intersection_floatspan_float', + 'intersection_floatspanset_float', + 'intersection_geoset_geo', + 'intersection_intset_int', + 'intersection_intspan_int', + 'intersection_intspanset_int', + 'intersection_period_timestamp', + 'intersection_periodset_timestamp', + 'intersection_set_set', + 'intersection_span_span', + 'intersection_spanset_span', + 'intersection_spanset_spanset', + 'intersection_textset_text', + 'intersection_timestampset_timestamp', + 'minus_bigint_bigintset', + 'minus_bigint_bigintspan', + 'minus_bigint_bigintspanset', + 'minus_bigintset_bigint', + 'minus_bigintspan_bigint', + 'minus_bigintspanset_bigint', + 'minus_float_floatset', + 'minus_float_floatspan', + 'minus_float_floatspanset', + 'minus_floatset_float', + 'minus_floatspan_float', + 'minus_floatspanset_float', + 'minus_geo_geoset', + 'minus_geoset_geo', + 'minus_int_intset', + 'minus_int_intspan', + 'minus_int_intspanset', + 'minus_intset_int', + 'minus_intspan_int', + 'minus_intspanset_int', + 'minus_period_timestamp', + 'minus_periodset_timestamp', + 'minus_set_set', + 'minus_span_span', + 'minus_span_spanset', + 'minus_spanset_span', + 'minus_spanset_spanset', + 'minus_text_textset', + 'minus_textset_text', + 'minus_timestamp_period', + 'minus_timestamp_periodset', + 'minus_timestamp_timestampset', + 'minus_timestampset_timestamp', + 'union_bigintset_bigint', + 'union_bigintspan_bigint', + 'union_bigintspanset_bigint', + 'union_floatset_float', + 'union_floatspan_float', + 'union_floatspanset_float', + 'union_geoset_geo', + 'union_intset_int', + 'union_intspan_int', + 'union_intspanset_int', + 'union_period_timestamp', + 'union_periodset_timestamp', + 'union_set_set', + 'union_span_span', + 'union_spanset_span', + 'union_spanset_spanset', + 'union_textset_text', + 'union_timestampset_timestamp', 'adjacent_bigintspan_bigint', 'adjacent_bigintspanset_bigint', 'adjacent_floatspan_float', @@ -305,149 +387,146 @@ 'overlaps_span_span', 'overlaps_spanset_span', 'overlaps_spanset_spanset', + 'after_period_timestamp', + 'after_periodset_timestamp', + 'after_timestamp_period', + 'after_timestamp_periodset', 'after_timestamp_timestampset', + 'after_timestampset_timestamp', + 'before_period_timestamp', 'before_periodset_timestamp', + 'before_timestamp_period', + 'before_timestamp_periodset', 'before_timestamp_timestampset', + 'before_timestampset_timestamp', + 'left_bigint_bigintset', + 'left_bigint_bigintspan', + 'left_bigint_bigintspanset', + 'left_bigintset_bigint', + 'left_bigintspan_bigint', + 'left_bigintspanset_bigint', + 'left_float_floatset', 'left_float_floatspan', + 'left_float_floatspanset', + 'left_floatset_float', 'left_floatspan_float', + 'left_floatspanset_float', + 'left_int_intset', 'left_int_intspan', + 'left_int_intspanset', + 'left_intset_int', 'left_intspan_int', + 'left_intspanset_int', 'left_set_set', 'left_span_span', 'left_span_spanset', 'left_spanset_span', 'left_spanset_spanset', + 'left_text_textset', + 'left_textset_text', 'overafter_period_timestamp', 'overafter_periodset_timestamp', 'overafter_timestamp_period', 'overafter_timestamp_periodset', 'overafter_timestamp_timestampset', + 'overafter_timestampset_timestamp', 'overbefore_period_timestamp', 'overbefore_periodset_timestamp', 'overbefore_timestamp_period', 'overbefore_timestamp_periodset', 'overbefore_timestamp_timestampset', + 'overbefore_timestampset_timestamp', + 'overleft_bigint_bigintset', + 'overleft_bigint_bigintspan', + 'overleft_bigint_bigintspanset', + 'overleft_bigintset_bigint', + 'overleft_bigintspan_bigint', + 'overleft_bigintspanset_bigint', + 'overleft_float_floatset', 'overleft_float_floatspan', + 'overleft_float_floatspanset', + 'overleft_floatset_float', 'overleft_floatspan_float', + 'overleft_floatspanset_float', + 'overleft_int_intset', 'overleft_int_intspan', + 'overleft_int_intspanset', + 'overleft_intset_int', 'overleft_intspan_int', + 'overleft_intspanset_int', 'overleft_set_set', 'overleft_span_span', 'overleft_span_spanset', 'overleft_spanset_span', 'overleft_spanset_spanset', + 'overleft_text_textset', + 'overleft_textset_text', + 'overright_bigint_bigintset', + 'overright_bigint_bigintspan', + 'overright_bigint_bigintspanset', + 'overright_bigintset_bigint', + 'overright_bigintspan_bigint', + 'overright_bigintspanset_bigint', + 'overright_float_floatset', 'overright_float_floatspan', + 'overright_float_floatspanset', + 'overright_floatset_float', 'overright_floatspan_float', + 'overright_floatspanset_float', + 'overright_int_intset', 'overright_int_intspan', + 'overright_int_intspanset', + 'overright_intset_int', 'overright_intspan_int', + 'overright_intspanset_int', 'overright_set_set', 'overright_span_span', 'overright_span_spanset', 'overright_spanset_span', 'overright_spanset_spanset', + 'overright_text_textset', + 'overright_textset_text', + 'right_bigint_bigintset', + 'right_bigint_bigintspan', + 'right_bigint_bigintspanset', + 'right_bigintset_bigint', + 'right_bigintspan_bigint', + 'right_bigintspanset_bigint', + 'right_float_floatset', 'right_float_floatspan', + 'right_float_floatspanset', + 'right_floatset_float', 'right_floatspan_float', + 'right_floatspanset_float', + 'right_int_intset', 'right_int_intspan', + 'right_int_intspanset', + 'right_intset_int', 'right_intspan_int', + 'right_intspanset_int', 'right_set_set', 'right_span_span', 'right_span_spanset', 'right_spanset_span', 'right_spanset_spanset', - 'intersection_bigintset_bigint', - 'intersection_bigintspan_bigint', - 'intersection_bigintspanset_bigint', - 'intersection_floatset_float', - 'intersection_floatspan_float', - 'intersection_floatspanset_float', - 'intersection_intset_int', - 'intersection_intspan_int', - 'intersection_intspanset_int', - 'intersection_period_timestamp', - 'intersection_periodset_timestamp', - 'intersection_set_set', - 'intersection_span_span', - 'intersection_spanset_span', - 'intersection_spanset_spanset', - 'intersection_textset_text', - 'intersection_timestampset_timestamp', - 'minus_bigint_bigintset', - 'minus_bigint_bigintspan', - 'minus_bigint_bigintspanset', - 'minus_bigintset_bigint', - 'minus_bigintspan_bigint', - 'minus_bigintspanset_bigint', - 'minus_float_floatset', - 'minus_float_floatspan', - 'minus_float_floatspanset', - 'minus_floatset_float', - 'minus_floatspan_float', - 'minus_floatspanset_float', - 'minus_int_intset', - 'minus_int_intspan', - 'minus_int_intspanset', - 'minus_intset_int', - 'minus_intspan_int', - 'minus_intspanset_int', - 'minus_period_timestamp', - 'minus_periodset_timestamp', - 'minus_set_set', - 'minus_span_span', - 'minus_span_spanset', - 'minus_spanset_span', - 'minus_spanset_spanset', - 'minus_text_textset', - 'minus_textset_text', - 'minus_timestamp_period', - 'minus_timestamp_periodset', - 'minus_timestampset_timestamp', - 'union_bigintset_bigint', - 'union_bigintspan_bigint', - 'union_bigintspanset_bigint', - 'union_floatset_float', - 'union_floatspan_float', - 'union_floatspanset_float', - 'union_intset_int', - 'union_intspan_int', - 'union_intspanset_int', - 'union_period_timestamp', - 'union_periodset_timestamp', - 'union_set_set', - 'union_span_span', - 'union_spanset_span', - 'union_spanset_spanset', - 'union_textset_text', - 'union_timestampset_timestamp', + 'right_text_textset', + 'right_textset_text', + 'distance_bigintset_bigint', + 'distance_bigintspan_bigint', + 'distance_bigintspanset_bigint', + 'distance_floatset_float', 'distance_floatspan_float', + 'distance_floatspanset_float', + 'distance_intset_int', 'distance_intspan_int', - 'distance_set_set', + 'distance_intspanset_int', 'distance_period_timestamp', 'distance_periodset_timestamp', + 'distance_set_set', 'distance_span_span', 'distance_spanset_span', 'distance_spanset_spanset', 'distance_timestampset_timestamp', - 'bigint_extent_transfn', - 'bigint_union_transfn', - 'float_extent_transfn', - 'float_union_transfn', - 'int_extent_transfn', - 'int_union_transfn', - 'period_tcount_transfn', - 'periodset_tcount_transfn', - 'set_extent_transfn', - 'set_union_finalfn', - 'set_union_transfn', - 'span_extent_transfn', - 'span_union_transfn', - 'spanset_extent_transfn', - 'spanset_union_finalfn', - 'spanset_union_transfn', - 'text_union_transfn', - 'timestamp_extent_transfn', - 'timestamp_tcount_transfn', - 'timestamp_union_transfn', - 'timestampset_tcount_transfn', 'set_cmp', 'set_eq', 'set_ge', @@ -469,6 +548,27 @@ 'spanset_le', 'spanset_lt', 'spanset_ne', + 'bigint_extent_transfn', + 'bigint_union_transfn', + 'float_extent_transfn', + 'float_union_transfn', + 'int_extent_transfn', + 'int_union_transfn', + 'period_tcount_transfn', + 'periodset_tcount_transfn', + 'set_extent_transfn', + 'set_union_finalfn', + 'set_union_transfn', + 'span_extent_transfn', + 'span_union_transfn', + 'spanset_extent_transfn', + 'spanset_union_finalfn', + 'spanset_union_transfn', + 'text_union_transfn', + 'timestamp_extent_transfn', + 'timestamp_tcount_transfn', + 'timestamp_union_transfn', + 'timestampset_tcount_transfn', 'tbox_in', 'tbox_out', 'tbox_from_wkb', @@ -481,69 +581,81 @@ 'stbox_as_hexwkb', 'stbox_in', 'stbox_out', - 'stbox_make', - 'stbox_copy', - 'tbox_make', - 'tbox_copy', - 'int_to_tbox', - 'float_to_tbox', - 'timestamp_to_tbox', - 'timestampset_to_tbox', - 'period_to_tbox', - 'periodset_to_tbox', - 'int_timestamp_to_tbox', 'float_period_to_tbox', 'float_timestamp_to_tbox', 'geo_period_to_stbox', 'geo_timestamp_to_stbox', - 'geo_to_stbox', 'int_period_to_tbox', - 'numspan_to_tbox', - 'span_timestamp_to_tbox', + 'int_timestamp_to_tbox', 'span_period_to_tbox', + 'span_timestamp_to_tbox', + 'stbox_copy', + 'stbox_make', + 'tbox_copy', + 'tbox_make', + 'float_to_tbox', + 'geo_to_stbox', + 'int_to_tbox', + 'numset_to_tbox', + 'numspan_to_tbox', + 'numspanset_to_tbox', + 'period_to_stbox', + 'period_to_tbox', + 'periodset_to_stbox', + 'periodset_to_tbox', + 'stbox_to_geo', + 'stbox_to_period', 'tbox_to_floatspan', 'tbox_to_period', - 'stbox_to_period', - 'tnumber_to_tbox', - 'stbox_to_geo', - 'tpoint_to_stbox', 'timestamp_to_stbox', + 'timestamp_to_tbox', 'timestampset_to_stbox', - 'period_to_stbox', - 'periodset_to_stbox', - 'tbox_hasx', - 'tbox_hast', - 'tbox_xmin', - 'tbox_xmin_inc', - 'tbox_xmax', - 'tbox_xmax_inc', - 'tbox_tmin', - 'tbox_tmin_inc', - 'tbox_tmax', - 'tbox_tmax_inc', + 'timestampset_to_tbox', + 'tnumber_to_tbox', + 'tpoint_to_stbox', + 'stbox_hast', 'stbox_hasx', 'stbox_hasz', - 'stbox_hast', 'stbox_isgeodetic', - 'stbox_xmin', + 'stbox_srid', + 'stbox_tmax', + 'stbox_tmax_inc', + 'stbox_tmin', + 'stbox_tmin_inc', 'stbox_xmax', - 'stbox_ymin', + 'stbox_xmin', 'stbox_ymax', - 'stbox_zmin', + 'stbox_ymin', 'stbox_zmax', - 'stbox_tmin', - 'stbox_tmin_inc', - 'stbox_tmax', - 'stbox_tmax_inc', - 'stbox_srid', + 'stbox_zmin', + 'tbox_hast', + 'tbox_hasx', + 'tbox_tmax', + 'tbox_tmax_inc', + 'tbox_tmin', + 'tbox_tmin_inc', + 'tbox_xmax', + 'tbox_xmax_inc', + 'tbox_xmin', + 'tbox_xmin_inc', 'stbox_expand_space', 'stbox_expand_time', 'stbox_get_space', 'stbox_round', 'stbox_set_srid', - 'tbox_expand_value', + 'stbox_shift_scale_time', 'tbox_expand_time', + 'tbox_expand_value', 'tbox_round', + 'tbox_shift_scale_float', + 'tbox_shift_scale_int', + 'tbox_shift_scale_time', + 'union_tbox_tbox', + 'inter_tbox_tbox', + 'intersection_tbox_tbox', + 'union_stbox_stbox', + 'inter_stbox_stbox', + 'intersection_stbox_stbox', 'contains_tbox_tbox', 'contained_tbox_tbox', 'overlaps_tbox_tbox', @@ -578,12 +690,6 @@ 'overbefore_stbox_stbox', 'after_stbox_stbox', 'overafter_stbox_stbox', - 'union_tbox_tbox', - 'inter_tbox_tbox', - 'intersection_tbox_tbox', - 'union_stbox_stbox', - 'inter_stbox_stbox', - 'intersection_stbox_stbox', 'stbox_quad_split', 'tbox_eq', 'tbox_ne', @@ -640,9 +746,7 @@ 'tpointseq_from_base_timestampset', 'tpointseqset_from_base_periodset', 'tsequence_make', - 'tsequence_make_exp', 'tsequenceset_make', - 'tsequenceset_make_exp', 'tsequenceset_make_gaps', 'ttext_from_base_temp', 'ttextinst_make', @@ -699,15 +803,29 @@ 'ttext_min_value', 'ttext_start_value', 'ttext_values', + 'temporal_scale_time', 'temporal_set_interp', - 'temporal_shift', - 'temporal_shift_tscale', + 'temporal_shift_scale_time', + 'temporal_shift_time', 'temporal_to_tinstant', 'temporal_to_tsequence', 'temporal_to_tsequenceset', - 'temporal_tprecision', - 'temporal_tsample', - 'temporal_tscale', + 'tfloat_scale_value', + 'tfloat_shift_scale_value', + 'tfloat_shift_value', + 'tint_scale_value', + 'tint_shift_scale_value', + 'tint_shift_value', + 'temporal_append_tinstant', + 'temporal_append_tsequence', + 'temporal_delete_period', + 'temporal_delete_periodset', + 'temporal_delete_timestamp', + 'temporal_delete_timestampset', + 'temporal_insert', + 'temporal_merge', + 'temporal_merge_array', + 'temporal_update', 'tbool_at_value', 'tbool_minus_value', 'tbool_value_at_timestamp', @@ -747,77 +865,13 @@ 'ttext_at_value', 'ttext_minus_value', 'ttext_value_at_timestamp', - 'temporal_append_tinstant', - 'temporal_append_tsequence', - 'temporal_delete_period', - 'temporal_delete_periodset', - 'temporal_delete_timestamp', - 'temporal_delete_timestampset', - 'temporal_insert', - 'temporal_merge', - 'temporal_merge_array', - 'temporal_update', - 'tand_bool_tbool', - 'tand_tbool_bool', - 'tand_tbool_tbool', - 'tbool_when_true', - 'tnot_tbool', - 'tor_bool_tbool', - 'tor_tbool_bool', - 'tor_tbool_tbool', - 'add_float_tfloat', - 'add_int_tint', - 'add_tfloat_float', - 'add_tint_int', - 'add_tnumber_tnumber', - 'div_float_tfloat', - 'div_int_tint', - 'div_tfloat_float', - 'div_tint_int', - 'div_tnumber_tnumber', - 'float_degrees', - 'mult_float_tfloat', - 'mult_int_tint', - 'mult_tfloat_float', - 'mult_tint_int', - 'mult_tnumber_tnumber', - 'sub_float_tfloat', - 'sub_int_tint', - 'sub_tfloat_float', - 'sub_tint_int', - 'sub_tnumber_tnumber', - 'tfloat_round', - 'tfloat_degrees', - 'tfloat_derivative', - 'tfloat_radians', - 'tnumber_abs', - 'tnumber_angular_difference', - 'tnumber_delta_value', - 'textcat_text_ttext', - 'textcat_ttext_text', - 'textcat_ttext_ttext', - 'ttext_upper', - 'ttext_lower', - 'distance_tfloat_float', - 'distance_tint_int', - 'distance_tnumber_tnumber', - 'distance_tpoint_geo', - 'distance_tpoint_tpoint', - 'nad_stbox_geo', - 'nad_stbox_stbox', - 'nad_tbox_tbox', - 'nad_tfloat_float', - 'nad_tfloat_tfloat', - 'nad_tint_int', - 'nad_tint_tint', - 'nad_tnumber_tbox', - 'nad_tpoint_geo', - 'nad_tpoint_stbox', - 'nad_tpoint_tpoint', - 'nai_tpoint_geo', - 'nai_tpoint_tpoint', - 'shortestline_tpoint_geo', - 'shortestline_tpoint_tpoint', + 'temporal_cmp', + 'temporal_eq', + 'temporal_ge', + 'temporal_gt', + 'temporal_le', + 'temporal_lt', + 'temporal_ne', 'tbool_always_eq', 'tbool_ever_eq', 'tfloat_always_eq', @@ -840,13 +894,6 @@ 'ttext_ever_eq', 'ttext_ever_le', 'ttext_ever_lt', - 'temporal_cmp', - 'temporal_eq', - 'temporal_ge', - 'temporal_gt', - 'temporal_le', - 'temporal_lt', - 'temporal_ne', 'teq_bool_tbool', 'teq_float_tfloat', 'teq_int_tint', @@ -897,6 +944,68 @@ 'tne_tpoint_point', 'tne_tint_int', 'tne_ttext_text', + 'tand_bool_tbool', + 'tand_tbool_bool', + 'tand_tbool_tbool', + 'tbool_when_true', + 'tnot_tbool', + 'tor_bool_tbool', + 'tor_tbool_bool', + 'tor_tbool_tbool', + 'add_float_tfloat', + 'add_int_tint', + 'add_tfloat_float', + 'add_tint_int', + 'add_tnumber_tnumber', + 'div_float_tfloat', + 'div_int_tint', + 'div_tfloat_float', + 'div_tint_int', + 'div_tnumber_tnumber', + 'float_degrees', + 'mult_float_tfloat', + 'mult_int_tint', + 'mult_tfloat_float', + 'mult_tint_int', + 'mult_tnumber_tnumber', + 'sub_float_tfloat', + 'sub_int_tint', + 'sub_tfloat_float', + 'sub_tint_int', + 'sub_tnumber_tnumber', + 'tfloat_round', + 'tfloatarr_round', + 'tfloat_degrees', + 'tfloat_derivative', + 'tfloat_radians', + 'tnumber_abs', + 'tnumber_angular_difference', + 'tnumber_delta_value', + 'textcat_text_ttext', + 'textcat_ttext_text', + 'textcat_ttext_ttext', + 'ttext_upper', + 'ttext_lower', + 'distance_tfloat_float', + 'distance_tint_int', + 'distance_tnumber_tnumber', + 'distance_tpoint_point', + 'distance_tpoint_tpoint', + 'nad_stbox_geo', + 'nad_stbox_stbox', + 'nad_tbox_tbox', + 'nad_tfloat_float', + 'nad_tfloat_tfloat', + 'nad_tint_int', + 'nad_tint_tint', + 'nad_tnumber_tbox', + 'nad_tpoint_geo', + 'nad_tpoint_stbox', + 'nad_tpoint_tpoint', + 'nai_tpoint_geo', + 'nai_tpoint_tpoint', + 'shortestline_tpoint_geo', + 'shortestline_tpoint_tpoint', 'bearing_point_point', 'bearing_tpoint_point', 'bearing_tpoint_tpoint', @@ -905,7 +1014,9 @@ 'tpoint_convex_hull', 'tpoint_cumulative_length', 'tpoint_direction', - 'tpoint_get_coord', + 'tpoint_get_x', + 'tpoint_get_y', + 'tpoint_get_z', 'tpoint_is_simple', 'tpoint_length', 'tpoint_speed', @@ -913,11 +1024,16 @@ 'tpoint_stboxes', 'tpoint_trajectory', 'geo_expand_space', - 'tgeompoint_tgeogpoint', + 'geo_to_tpoint', + 'tgeogpoint_to_tgeompoint', + 'tgeompoint_to_tgeogpoint', + 'tpoint_AsMVTGeom', 'tpoint_expand_space', - 'tpoint_round', 'tpoint_make_simple', + 'tpoint_round', + 'tpointarr_round', 'tpoint_set_srid', + 'tpoint_to_geo_meas', 'econtains_geo_tpoint', 'edisjoint_tpoint_geo', 'edisjoint_tpoint_tpoint', @@ -954,29 +1070,31 @@ 'tpoint_twcentroid', 'ttext_tmax_transfn', 'ttext_tmin_transfn', + 'temporal_simplify_min_dist', + 'temporal_simplify_min_tdelta', + 'temporal_simplify_dp', + 'temporal_simplify_max_dist', + 'temporal_tprecision', + 'temporal_tsample', + 'temporal_dyntimewarp_distance', + 'temporal_dyntimewarp_path', + 'temporal_frechet_distance', + 'temporal_frechet_path', + 'temporal_hausdorff_distance', 'float_bucket', 'floatspan_bucket_list', 'int_bucket', 'intspan_bucket_list', 'period_bucket_list', 'stbox_tile_list', - 'tbox_tile_list', + 'tintbox_tile_list', + 'tfloatbox_tile_list', 'temporal_time_split', 'tfloat_value_split', 'tfloat_value_time_split', 'timestamptz_bucket', 'tint_value_split', 'tint_value_time_split', - 'temporal_dyntimewarp_distance', - 'temporal_dyntimewarp_path', - 'temporal_frechet_distance', - 'temporal_frechet_path', - 'temporal_hausdorff_distance', - 'geo_to_tpoint', - 'temporal_simplify_min_dist', - 'temporal_simplify_min_tdelta', - 'temporal_simplify_dp', - 'temporal_simplify_max_dist', - 'tpoint_AsMVTGeom', - 'tpoint_to_geo_meas', + 'tpoint_space_split', + 'tpoint_space_time_split', ] diff --git a/pymeos_cffi/pymeos_cffi/builder/build_header.py b/pymeos_cffi/pymeos_cffi/builder/build_header.py index 38e3e1a0..2b0a30c1 100644 --- a/pymeos_cffi/pymeos_cffi/builder/build_header.py +++ b/pymeos_cffi/pymeos_cffi/builder/build_header.py @@ -26,7 +26,7 @@ def remove_if_not_defined(m): return content -def main(header_path, so_path=None): +def main(header_path, so_path=None, destination_path='pymeos_cffi/builder/meos.h'): with open(header_path, 'r') as f: content = f.read() # Remove comments @@ -45,12 +45,12 @@ def main(header_path, so_path=None): # Add error handler content += '\n\nextern "Python" void py_error_handler(int, int, char*);' - with open('pymeos_cffi/builder/meos.h', 'w') as f: + with open(destination_path, 'w') as f: f.write(content) if __name__ == '__main__': if len(sys.argv) > 1: - main(sys.argv[1], sys.argv[2]) + main(*sys.argv[1:]) else: main('/usr/local/include/meos.h', '/usr/local/lib/libmeos.so') diff --git a/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions.py b/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions.py index 848f2c2b..104227b7 100644 --- a/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions.py +++ b/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions.py @@ -1,4 +1,6 @@ +import os.path import re +import sys from re import RegexFlag from typing import List, Optional @@ -42,6 +44,10 @@ def __init__(self, ctype: str, ptype: str, conversion: Optional[str]) -> None: hidden_functions = [ '_check_error', +] + +# List of MEOS functions that should not defined in functions.py +skipped_functions = [ 'py_error_handler', 'meos_initialize_timezone', 'meos_initialize_error_handler', @@ -56,19 +62,7 @@ def __init__(self, ctype: str, ptype: str, conversion: Optional[str]) -> None: 'meos_finalize': remove_error_check_modifier, 'cstring2text': cstring2text_modifier, 'text2cstring': text2cstring_modifier, - 'timestampset_make': timestampset_make_modifier, - 'tint_at_values': tint_at_values_modifier, - 'tint_minus_values': tint_minus_values_modifier, - 'tfloat_at_values': tfloat_at_values_modifier, - 'tfloat_minus_values': tfloat_minus_values_modifier, - 'tbool_at_values': tbool_at_values_modifier, - 'tbool_minus_values': tbool_minus_values_modifier, - 'ttext_at_values': array_length_remover_modifier('values_converted'), - 'ttext_minus_values': array_length_remover_modifier('values_converted'), - 'tpoint_at_values': array_length_remover_modifier('values_converted'), - 'tpoint_minus_values': array_length_remover_modifier('values_converted'), 'gserialized_from_lwgeom': gserialized_from_lwgeom_modifier, - 'tpointseq_make_coords': tpointseq_make_coords_modifier, 'spanset_make': spanset_make_modifier, 'temporal_from_wkb': from_wkb_modifier('temporal_from_wkb', 'Temporal'), 'set_from_wkb': from_wkb_modifier('set_from_wkb', 'Set'), @@ -82,6 +76,12 @@ def __init__(self, ctype: str, ptype: str, conversion: Optional[str]) -> None: 'spanset_as_wkb': as_wkb_modifier, 'tbox_as_wkb': as_wkb_modifier, 'stbox_as_wkb': as_wkb_modifier, + 'timestampset_make': timestampset_make_modifier, + 'intset_make': array_parameter_modifier('values', 'count'), + 'bigintset_make': array_parameter_modifier('values', 'count'), + 'floatset_make': array_parameter_modifier('values', 'count'), + 'textset_make': textset_make_modifier, + 'geoset_make': array_length_remover_modifier('values', 'count'), } # List of result function parameters in tuples of (function, parameter) @@ -93,21 +93,31 @@ def __init__(self, ctype: str, ptype: str, conversion: Optional[str]) -> None: ('tpoint_value_at_timestamp', 'value'), } -# List of output function parameters in tuples of (function, parameter). All parameters named result are assumed -# to be output parameters, and it's not necessary to list them here. +# List of output function parameters in tuples of (function, parameter). +# All parameters named result are assumed to be output parameters, and it is +# not necessary to list them here. output_parameters = { - ('temporal_time_split', 'buckets'), - ('temporal_time_split', 'newcount'), - ('tint_value_split', 'buckets'), - ('tint_value_split', 'newcount'), - ('tfloat_value_split', 'buckets'), - ('tfloat_value_split', 'newcount'), - ('tint_value_time_split', 'newcount'), - ('tfloat_value_time_split', 'newcount'), + ('temporal_time_split', 'time_buckets'), + ('temporal_time_split', 'count'), + ('tint_value_split', 'value_buckets'), + ('tint_value_split', 'count'), + ('tfloat_value_split', 'value_buckets'), + ('tfloat_value_split', 'count'), + ('tint_value_time_split', 'value_buckets'), + ('tint_value_time_split', 'time_buckets'), + ('tint_value_time_split', 'count'), + ('tfloat_value_time_split', 'value_buckets'), + ('tfloat_value_time_split', 'time_buckets'), + ('tfloat_value_time_split', 'count'), + ('tpoint_space_split', 'space_buckets'), + ('tpoint_space_split', 'count'), + ('tpoint_space_time_split', 'space_buckets'), + ('tpoint_space_time_split', 'time_buckets'), + ('tpoint_space_time_split', 'count'), ('tbox_as_hexwkb', 'size'), ('stbox_as_hexwkb', 'size'), - ('tbox_tile_list', 'rows'), - ('tbox_tile_list', 'columns'), + ('tintbox_tile_list', 'count'), + ('tfloatbox_tile_list', 'count'), ('stbox_tile_list', 'cellcount'), } @@ -117,27 +127,27 @@ def __init__(self, ctype: str, ptype: str, conversion: Optional[str]) -> None: ('temporal_append_tinstant', 'maxt'), ('temporal_as_mfjson', 'srs'), ('gserialized_as_geojson', 'srs'), - ('period_shift_tscale', 'shift'), - ('period_shift_tscale', 'duration'), - ('period_shift_tscale', 'delta'), - ('period_shift_tscale', 'scale'), - ('timestampset_shift_tscale', 'shift'), - ('timestampset_shift_tscale', 'duration'), - ('periodset_shift_tscale', 'shift'), - ('periodset_shift_tscale', 'duration'), - ('temporal_shift_tscale', 'shift'), - ('temporal_shift_tscale', 'duration'), - ('temporal_shift_tscale', 'shift'), + ('period_shift_scale', 'shift'), + ('period_shift_scale', 'duration'), + ('timestampset_shift_scale', 'shift'), + ('timestampset_shift_scale', 'duration'), + ('periodset_shift_scale', 'shift'), + ('periodset_shift_scale', 'duration'), + ('temporal_shift_scale_time', 'shift'), + ('temporal_shift_scale_time', 'duration'), ('tbox_make', 'p'), ('tbox_make', 's'), ('stbox_make', 'p'), - ('tpointseq_make_coords', 'zcoords'), + ('stbox_shift_scale_time', 'shift'), + ('stbox_shift_scale_time', 'duration'), ('temporal_tcount_transfn', 'state'), ('temporal_extent_transfn', 'p'), ('tnumber_extent_transfn', 'box'), ('tpoint_extent_transfn', 'box'), ('tbool_tand_transfn', 'state'), ('tbool_tor_transfn', 'state'), + ('tbox_shift_scale_time', 'shift'), + ('tbox_shift_scale_time', 'duration'), ('tint_tmin_transfn', 'state'), ('tfloat_tmin_transfn', 'state'), ('tint_tmax_transfn', 'state'), @@ -153,20 +163,15 @@ def __init__(self, ctype: str, ptype: str, conversion: Optional[str]) -> None: ('period_tcount_transfn', 'interval'), ('periodset_tcount_transfn', 'interval'), ('timestamp_extent_transfn', 'p'), - ('timestampset_extent_transfn', 'p'), - ('period_extent_transfn', 'p'), - ('periodset_extent_transfn', 'p'), - ('timestamp_tunion_transfn', 'state'), - ('timestampset_tunion_transfn', 'state'), - ('period_tunion_transfn', 'state'), - ('periodset_tunion_transfn', 'state'), ('timestamp_tcount_transfn', 'state'), ('timestampset_tcount_transfn', 'state'), ('period_tcount_transfn', 'state'), ('periodset_tcount_transfn', 'state'), ('stbox_tile_list', 'duration'), - ('tbox_tile_list', 'xorigin'), - ('tbox_tile_list', 'torigin'), + ('tintbox_tile_list', 'xorigin'), + ('tintbox_tile_list', 'torigin'), + ('tfloatbox_tile_list', 'xorigin'), + ('tfloatbox_tile_list', 'torigin'), } @@ -191,8 +196,23 @@ def is_output_parameter(function: str, parameter: Parameter) -> bool: return (function, parameter.name) in output_parameters -def main(): - with open('pymeos_cffi/builder/meos.h') as f: +def check_modifiers(functions: List[str]) -> None: + for func in function_modifiers.keys(): + if func not in functions: + print(f'Modifier defined for non-existent function {func}') + for func, param in result_parameters: + if func not in functions: + print(f'Result parameter defined for non-existent function {func} ({param})') + for func, param in output_parameters: + if func not in functions: + print(f'Output parameter defined for non-existent function {func} ({param})') + for func, param in nullable_parameters: + if func not in functions: + print(f'Nullable Parameter defined for non-existent function {func} ({param})') + + +def main(header_path='pymeos_cffi/builder/meos.h'): + with open(header_path) as f: content = f.read() # Regex lines: # 1st line: Match beginning of function with optional "extern", "static" and "inline" @@ -207,16 +227,20 @@ def main(): r'\((?P[\w\s,\*]*)\);' matches = re.finditer(f_regex, ''.join(content.splitlines()), flags=RegexFlag.MULTILINE) - with open('pymeos_cffi/builder/templates/functions.py') as f: + template_path = os.path.join(os.path.dirname(__file__), 'templates/functions.py') + with open(template_path) as f: base = f.read() - with open('pymeos_cffi/functions.py', 'w+') as file: + functions_path = os.path.join(os.path.dirname(__file__), '../functions.py') + init_path = os.path.join(os.path.dirname(__file__), '../__init__.py') + + with open(functions_path, 'w+') as file: file.write(base) for match in matches: named = match.groupdict() function = named['function'] inner_return_type = named['returnType'] - if function == 'py_error_handler': + if function in skipped_functions: continue return_type = get_return_type(inner_return_type) inner_params = named['params'] @@ -225,9 +249,10 @@ def main(): file.write(function_string) file.write('\n\n\n') - with open('pymeos_cffi/functions.py', 'r') as funcs, open('pymeos_cffi/__init__.py', 'w+') as init: + functions = [] + with open(functions_path, 'r') as funcs, open(init_path, 'w+') as init: content = funcs.read() - f_names = re.finditer(r'def (\w+)\(', content) + matches = list(re.finditer(r'def (\w+)\(', content)) init.write('from .functions import *\n\n') init.write('from .errors import *\n\n') init.write('__all__ = [\n' @@ -256,13 +281,16 @@ def main(): " 'MeosGeoJsonOutputError',\n" " # Functions\n" ) - for fn in f_names: + for fn in matches: function_name = fn.group(1) if function_name in hidden_functions: continue init.write(f" '{function_name}',\n") + functions.append(function_name) init.write(']\n') + check_modifiers(functions) + def get_params(function: str, inner_params: str) -> List[Parameter]: return [p for p in (get_param(function, param.strip()) for param in inner_params.split(',')) if p is not None] @@ -277,11 +305,10 @@ def get_param(function: str, inner_param: str) -> Optional[Parameter]: param_type = ' '.join(split[:-1]) # Check if parameter is pointer and fix type and name accordingly - if split[-1].startswith('**'): - param_type += ' **' - elif split[-1].startswith('*'): - param_type += ' *' param_name = split[-1].lstrip('*') + pointer_level = len(split[-1]) - len(param_name) + if pointer_level > 0: + param_type += ' ' + '*' * pointer_level # Check if the parameter name is a reserved word and change it if necessary reserved_words = { @@ -394,7 +421,7 @@ def build_function_string(function_name: str, return_type: ReturnType, parameter if result_param.is_interoperable(): returning_object += '[0]' - # If original C function returned bool, use it to return it when result is True, or raise exception when False + # If original C function returned bool, use it to return it when result is True, or return None otherwise. if return_type.return_type == 'bool': result_manipulation = (result_manipulation or '') + \ @@ -460,4 +487,4 @@ def build_function_string(function_name: str, return_type: ReturnType, parameter if __name__ == '__main__': - main() + main(*sys.argv[1:]) diff --git a/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions_modifiers.py b/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions_modifiers.py index aa361106..6a0feea0 100644 --- a/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions_modifiers.py +++ b/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions_modifiers.py @@ -1,4 +1,5 @@ -from typing import Callable +import re +from typing import Callable, Optional def array_length_remover_modifier(list_name: str, length_param_name: str = 'count') -> Callable[[str], str]: @@ -7,6 +8,29 @@ def array_length_remover_modifier(list_name: str, length_param_name: str = 'coun .replace(f', {length_param_name}', f', len({list_name})') +def array_parameter_modifier(list_name: str, length_param_name: Optional[str] = None) -> Callable[[str], str]: + def custom_array_modifier(function: str) -> str: + type_regex = list_name + r": '([\w \*]+)'" + match = next(re.finditer(type_regex, function)) + whole_type = match.group(1) + base_type = ' '.join(whole_type.split(' ')[:-1]) + function = function \ + .replace(match.group(0), f"{list_name}: 'List[{base_type}]'") \ + .replace(f"_ffi.cast('{whole_type}', {list_name})", f"_ffi.new('{base_type} []', {list_name})") + if length_param_name: + function = function \ + .replace(f', {length_param_name}: int', '') \ + .replace(f', {length_param_name}', f', len({list_name})') + return function + + return custom_array_modifier + + +def textset_make_modifier(function: str) -> str: + function = array_parameter_modifier('values', 'count')(function) + return function.replace("_ffi.cast('const text *', x)", "cstring2text(x)").replace("'List[const text]'", 'List[str]') + + def meos_initialize_modifier(_: str) -> str: return """def meos_initialize(tz_str: "Optional[str]") -> None: tz_str_converted = tz_str.encode('utf-8') if tz_str is not None else _ffi.NULL @@ -49,44 +73,10 @@ def as_wkb_modifier(function: str) -> str: def timestampset_make_modifier(function: str) -> str: return function \ .replace('values: int', 'values: List[int]') \ + .replace(', count: int', '') \ .replace("values_converted = _ffi.cast('const TimestampTz *', values)", - "values_converted = [_ffi.cast('const TimestampTz', x) for x in values]") - - -def tbool_at_values_modifier(function: str) -> str: - return function \ - .replace("values: 'bool *', count: int", 'values: List[bool]') \ - .replace("_ffi.cast('bool *', values)", - "_ffi.new('bool []', values)") \ - .replace(', count', ', len(values_converted)') - - -def tbool_minus_values_modifier(function: str) -> str: - return tbool_at_values_modifier(function) - - -def tint_at_values_modifier(function: str) -> str: - return function \ - .replace("values: 'int *', count: int", 'values: List[int]') \ - .replace("_ffi.cast('int *', values)", - "_ffi.new('int []', values)") \ - .replace(', count', ', len(values_converted)') - - -def tint_minus_values_modifier(function: str) -> str: - return tint_at_values_modifier(function) - - -def tfloat_at_values_modifier(function: str) -> str: - return function \ - .replace("values: 'double *', count: int", 'values: List[float]') \ - .replace("_ffi.cast('double *', values)", - "_ffi.new('double []', values)") \ - .replace(', count', ', len(values_converted)') - - -def tfloat_minus_values_modifier(function: str) -> str: - return tfloat_at_values_modifier(function) + "values_converted = [_ffi.cast('const TimestampTz', x) for x in values]") \ + .replace('count', 'len(values)') def spanset_make_modifier(function: str) -> str: @@ -101,15 +91,3 @@ def gserialized_from_lwgeom_modifier(function: str) -> str: return function \ .replace(", size: 'size_t *'", '') \ .replace("_ffi.cast('size_t *', size)", '_ffi.NULL') - - -def tpointseq_make_coords_modifier(function: str) -> str: - return function \ - .replace('times: int', "times: 'const TimestampTz *'") \ - .replace(" xcoords_converted = _ffi.cast('const double *', xcoords)\n", '') \ - .replace(" ycoords_converted = _ffi.cast('const double *', ycoords)\n", '') \ - .replace(" times_converted = _ffi.cast('const TimestampTz *', times)\n", '') \ - .replace("_ffi.cast('const double *', zcoords)", 'zcoords') \ - .replace('xcoords_converted', 'xcoords') \ - .replace('ycoords_converted', 'ycoords') \ - .replace('times_converted', 'times') diff --git a/pymeos_cffi/pymeos_cffi/builder/meos.h b/pymeos_cffi/pymeos_cffi/builder/meos.h index f63cdcb3..89e08bec 100644 --- a/pymeos_cffi/pymeos_cffi/builder/meos.h +++ b/pymeos_cffi/pymeos_cffi/builder/meos.h @@ -729,6 +729,47 @@ typedef struct SkipListElem *elems; } SkipList; +/***************************************************************************** + * Error codes + *****************************************************************************/ + +typedef enum +{ + MEOS_SUCCESS = 0, + + MEOS_ERR_INTERNAL_ERROR = 1, + MEOS_ERR_INTERNAL_TYPE_ERROR = 2, + MEOS_ERR_VALUE_OUT_OF_RANGE = 3, + MEOS_ERR_DIVISION_BY_ZERO = 4, + MEOS_ERR_MEMORY_ALLOC_ERROR = 5, + MEOS_ERR_AGGREGATION_ERROR = 6, + MEOS_ERR_DIRECTORY_ERROR = 7, + MEOS_ERR_FILE_ERROR = 8, + + MEOS_ERR_INVALID_ARG = 10, + MEOS_ERR_INVALID_ARG_TYPE = 11, + MEOS_ERR_INVALID_ARG_VALUE = 12, + + MEOS_ERR_MFJSON_INPUT = 20, + MEOS_ERR_MFJSON_OUTPUT = 21, + MEOS_ERR_TEXT_INPUT = 22, + MEOS_ERR_TEXT_OUTPUT = 23, + MEOS_ERR_WKB_INPUT = 24, + MEOS_ERR_WKB_OUTPUT = 25, + MEOS_ERR_GEOJSON_INPUT = 26, + MEOS_ERR_GEOJSON_OUTPUT = 27, + +} errorCode; + +extern void meos_error(int errlevel, int errcode, char *format, ...); + + + +extern int meos_errno(void); +extern int meos_errno_set(int err); +extern int meos_errno_restore(int err); +extern int meos_errno_reset(void); + /***************************************************************************** * Initialization of the MEOS library *****************************************************************************/ @@ -744,7 +785,7 @@ extern void meos_initialize(const char *tz_str, error_handler_fn err_handler); extern void meos_finalize(void); /***************************************************************************** - * Functions for input/output PostgreSQL time types + * Functions for PostgreSQL types *****************************************************************************/ extern bool bool_in(const char *in_str); @@ -757,6 +798,7 @@ extern Interval *pg_interval_in(const char *str, int32 typmod); extern Interval *pg_interval_make(int32 years, int32 months, int32 weeks, int32 days, int32 hours, int32 mins, double secs); extern Interval *pg_interval_mul(const Interval *span, double factor); extern char *pg_interval_out(const Interval *span); +extern text *pg_interval_to_char(Interval *it, text *fmt); extern Interval *pg_interval_pl(const Interval *span1, const Interval *span2); extern TimeADT pg_time_in(const char *str, int32 typmod); extern char *pg_time_out(TimeADT time); @@ -765,21 +807,22 @@ extern Interval *pg_timestamp_mi(TimestampTz dt1, TimestampTz dt2); extern TimestampTz pg_timestamp_mi_interval(TimestampTz timestamp, const Interval *span); extern char *pg_timestamp_out(Timestamp dt); extern TimestampTz pg_timestamp_pl_interval(TimestampTz timestamp, const Interval *span); +extern text *pg_timestamp_to_char(Timestamp dt, text *fmt); extern TimestampTz pg_timestamptz_in(const char *str, int32 typmod); extern char *pg_timestamptz_out(TimestampTz dt); -extern char *text2cstring(const text *textptr); -extern text *pg_timestamp_to_char(Timestamp dt, text *fmt); extern text *pg_timestamptz_to_char(TimestampTz dt, text *fmt); -extern text *pg_interval_to_char(Interval *it, text *fmt); -extern TimestampTz pg_to_timestamp(text *date_txt, text *fmt); extern DateADT pg_to_date(text *date_txt, text *fmt); +extern TimestampTz pg_to_timestamp(text *date_txt, text *fmt); +extern char *text2cstring(const text *textptr); /***************************************************************************** - * Functions for input/output and manipulation of PostGIS types + * Functions for PostGIS types *****************************************************************************/ extern GSERIALIZED *geography_from_hexewkb(const char *wkt); +extern GSERIALIZED *geography_from_text(char *wkt, int srid); extern GSERIALIZED *geometry_from_hexewkb(const char *wkt); +extern GSERIALIZED *geometry_from_text(char *wkt, int srid); extern bytea *gserialized_as_ewkb(const GSERIALIZED *gs, char *type); extern char *gserialized_as_ewkt(const GSERIALIZED *gs, int precision); extern char *gserialized_as_geojson(const GSERIALIZED *gs, int option, int precision, char *srs); @@ -788,11 +831,9 @@ extern char *gserialized_as_text(const GSERIALIZED *gs, int precision); extern GSERIALIZED *gserialized_from_ewkb(const bytea *bytea_wkb, int32 srid); extern GSERIALIZED *gserialized_from_geojson(const char *geojson); extern char *gserialized_out(const GSERIALIZED *gs); -extern GSERIALIZED *geometry_from_text(char *wkt, int srid); -extern GSERIALIZED *geography_from_text(char *wkt, int srid); extern GSERIALIZED *pgis_geography_in(char *input, int32 geom_typmod); extern GSERIALIZED *pgis_geometry_in(char *input, int32 geom_typmod); -extern bool pgis_gserialized_same(const GSERIALIZED *gs1, const GSERIALIZED *geom2); +extern bool pgis_gserialized_same(const GSERIALIZED *gs1, const GSERIALIZED *gs2); /***************************************************************************** * Functions for set and span types @@ -813,10 +854,10 @@ extern char *floatspan_out(const Span *s, int maxdd); extern SpanSet *floatspanset_in(const char *str); extern char *floatspanset_out(const SpanSet *ss, int maxdd); extern Set *geogset_in(const char *str); -extern char *geoset_out(const Set *set, int maxdd); extern Set *geomset_in(const char *str); extern char *geoset_as_ewkt(const Set *set, int maxdd); extern char *geoset_as_text(const Set *set, int maxdd); +extern char *geoset_out(const Set *set, int maxdd); extern Set *intset_in(const char *str); extern char *intset_out(const Set *set); extern Span *intspan_in(const char *str); @@ -831,17 +872,14 @@ extern char *set_as_hexwkb(const Set *s, uint8_t variant, size_t *size_out); extern uint8_t *set_as_wkb(const Set *s, uint8_t variant, size_t *size_out); extern Set *set_from_hexwkb(const char *hexwkb); extern Set *set_from_wkb(const uint8_t *wkb, size_t size); -extern char *set_out(const Set *s, int maxdd); -extern uint8_t *span_as_wkb(const Span *s, uint8_t variant, size_t *size_out); extern char *span_as_hexwkb(const Span *s, uint8_t variant, size_t *size_out); +extern uint8_t *span_as_wkb(const Span *s, uint8_t variant, size_t *size_out); extern Span *span_from_hexwkb(const char *hexwkb); extern Span *span_from_wkb(const uint8_t *wkb, size_t size); -extern char *span_out(const Span *s, int maxdd); -extern uint8_t *spanset_as_wkb(const SpanSet *ss, uint8_t variant, size_t *size_out); extern char *spanset_as_hexwkb(const SpanSet *ss, uint8_t variant, size_t *size_out); +extern uint8_t *spanset_as_wkb(const SpanSet *ss, uint8_t variant, size_t *size_out); extern SpanSet *spanset_from_hexwkb(const char *hexwkb); extern SpanSet *spanset_from_wkb(const uint8_t *wkb, size_t size); -extern char *spanset_out(const SpanSet *ss, int maxdd); extern Set *textset_in(const char *str); extern char *textset_out(const Set *set); extern Set *timestampset_in(const char *str); @@ -863,7 +901,6 @@ extern Set *set_copy(const Set *s); extern Span *span_copy(const Span *s); extern SpanSet *spanset_copy(const SpanSet *ps); extern SpanSet *spanset_make(Span *spans, int count, bool normalize); -extern SpanSet *spanset_make_exp(Span *spans, int count, int maxcount, bool normalize, bool ordered); extern Set *textset_make(const text **values, int count); extern Set *timestampset_make(const TimestampTz *values, int count); @@ -934,7 +971,6 @@ extern TimestampTz *periodset_timestamps(const SpanSet *ps, int *count); extern TimestampTz periodset_upper(const SpanSet *ps); extern uint32 set_hash(const Set *s); extern uint64 set_hash_extended(const Set *s, uint64 seed); -extern int set_mem_size(const Set *s); extern int set_num_values(const Set *s); extern Span *set_span(const Set *s); extern uint32 span_hash(const Span *s); @@ -946,14 +982,13 @@ extern Span *spanset_end_span(const SpanSet *ss); extern uint32 spanset_hash(const SpanSet *ps); extern uint64 spanset_hash_extended(const SpanSet *ps, uint64 seed); extern bool spanset_lower_inc(const SpanSet *ss); -extern int spanset_mem_size(const SpanSet *ss); extern int spanset_num_spans(const SpanSet *ss); extern Span *spanset_span(const SpanSet *ss); extern Span *spanset_span_n(const SpanSet *ss, int i); extern const Span **spanset_spans(const SpanSet *ss); extern Span *spanset_start_span(const SpanSet *ss); extern bool spanset_upper_inc(const SpanSet *ss); -extern double spanset_width(const SpanSet *ss); +extern double spanset_width(const SpanSet *ss, bool boundspan); extern STBox *spatialset_stbox(const Set *s); extern text *textset_end_value(const Set *s); extern text *textset_start_value(const Set *s); @@ -968,16 +1003,105 @@ extern TimestampTz *timestampset_values(const Set *ts); +extern Set *bigintset_shift_scale(const Set *s, int64 shift, int64 width, bool hasshift, bool haswidth); +extern Span *bigintspan_shift_scale(const Span *s, int64 shift, int64 width, bool hasshift, bool haswidth); +extern SpanSet *bigintspanset_shift_scale(const SpanSet *ss, int64 shift, int64 width, bool hasshift, bool haswidth); extern Set *floatset_round(const Set *s, int maxdd); +extern Set *floatset_shift_scale(const Set *s, double shift, double width, bool hasshift, bool haswidth); +extern Span *floatspan_intspan(const Span *s); extern Span *floatspan_round(const Span *s, int maxdd); +extern Span *floatspan_shift_scale(const Span *s, double shift, double width, bool hasshift, bool haswidth); +extern SpanSet *floatspanset_intspanset(const SpanSet *ss); extern SpanSet *floatspanset_round(const SpanSet *ss, int maxdd); +extern SpanSet *floatspanset_shift_scale(const SpanSet *ss, double shift, double width, bool hasshift, bool haswidth); extern Set *geoset_round(const Set *s, int maxdd); +extern Set *intset_shift_scale(const Set *s, int shift, int width, bool hasshift, bool haswidth); +extern Span *intspan_floatspan(const Span *s); +extern Span *intspan_shift_scale(const Span *s, int shift, int width, bool hasshift, bool haswidth); +extern SpanSet *intspanset_floatspanset(const SpanSet *ss); +extern SpanSet *intspanset_shift_scale(const SpanSet *ss, int shift, int width, bool hasshift, bool haswidth); +extern Span *period_shift_scale(const Span *p, const Interval *shift, const Interval *duration); extern Span *period_tprecision(const Span *s, const Interval *duration, TimestampTz torigin); +extern SpanSet *periodset_shift_scale(const SpanSet *ss, const Interval *shift, const Interval *duration); extern SpanSet *periodset_tprecision(const SpanSet *ss, const Interval *duration, TimestampTz torigin); -extern Span *period_shift_tscale(const Span *p, const Interval *shift, const Interval *duration); -extern SpanSet *periodset_shift_tscale(const SpanSet *ps, const Interval *shift, const Interval *duration); +extern Set *textset_lower(const Set *s); +extern Set *textset_upper(const Set *s); extern TimestampTz timestamp_tprecision(TimestampTz t, const Interval *duration, TimestampTz torigin); -extern Set *timestampset_shift_tscale(const Set *ts, const Interval *shift, const Interval *duration); +extern Set *timestampset_shift_scale(const Set *ts, const Interval *shift, const Interval *duration); + + + + + +extern bool intersection_bigintset_bigint(const Set *s, int64 i, int64 *result); +extern bool intersection_bigintspan_bigint(const Span *s, int64 i, int64 *result); +extern bool intersection_bigintspanset_bigint(const SpanSet *ss, int64 i, int64 *result); +extern bool intersection_floatset_float(const Set *s, double d, double *result); +extern bool intersection_floatspan_float(const Span *s, double d, double *result); +extern bool intersection_floatspanset_float(const SpanSet *ss, double d, double *result); +extern bool intersection_geoset_geo(const Set *s, const GSERIALIZED *gs, GSERIALIZED **result); +extern bool intersection_intset_int(const Set *s, int i, int *result); +extern bool intersection_intspan_int(const Span *s, int i, int *result); +extern bool intersection_intspanset_int(const SpanSet *ss, int i, int *result); +extern bool intersection_period_timestamp(const Span *s, TimestampTz t, TimestampTz *result); +extern bool intersection_periodset_timestamp(const SpanSet *ss, TimestampTz t, TimestampTz *result); +extern Set *intersection_set_set(const Set *s1, const Set *s2); +extern Span *intersection_span_span(const Span *s1, const Span *s2); +extern SpanSet *intersection_spanset_span(const SpanSet *ss, const Span *s); +extern SpanSet *intersection_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); +extern bool intersection_textset_text(const Set *s, const text *txt, text **result); +extern bool intersection_timestampset_timestamp(const Set *s, TimestampTz t, TimestampTz *result); +extern bool minus_bigint_bigintset(int64 i, const Set *s, int64 *result); +extern bool minus_bigint_bigintspan(int64 i, const Span *s, int64 *result); +extern bool minus_bigint_bigintspanset(int64 i, const SpanSet *ss, int64 *result); +extern Set *minus_bigintset_bigint(const Set *s, int64 i); +extern SpanSet *minus_bigintspan_bigint(const Span *s, int64 i); +extern SpanSet *minus_bigintspanset_bigint(const SpanSet *ss, int64 i); +extern bool minus_float_floatset(double d, const Set *s, double *result); +extern bool minus_float_floatspan(double d, const Span *s, double *result); +extern bool minus_float_floatspanset(double d, const SpanSet *ss, double *result); +extern Set *minus_floatset_float(const Set *s, double d); +extern SpanSet *minus_floatspan_float(const Span *s, double d); +extern SpanSet *minus_floatspanset_float(const SpanSet *ss, double d); +extern bool minus_geo_geoset(const GSERIALIZED *gs, const Set *s, GSERIALIZED **result); +extern Set *minus_geoset_geo(const Set *s, const GSERIALIZED *gs); +extern bool minus_int_intset(int i, const Set *s, int *result); +extern bool minus_int_intspan(int i, const Span *s, int *result); +extern bool minus_int_intspanset(int i, const SpanSet *ss, int *result); +extern Set *minus_intset_int(const Set *s, int i); +extern SpanSet *minus_intspan_int(const Span *s, int i); +extern SpanSet *minus_intspanset_int(const SpanSet *ss, int i); +extern SpanSet *minus_period_timestamp(const Span *s, TimestampTz t); +extern SpanSet *minus_periodset_timestamp(const SpanSet *ss, TimestampTz t); +extern Set *minus_set_set(const Set *s1, const Set *s2); +extern SpanSet *minus_span_span(const Span *s1, const Span *s2); +extern SpanSet *minus_span_spanset(const Span *s, const SpanSet *ss); +extern SpanSet *minus_spanset_span(const SpanSet *ss, const Span *s); +extern SpanSet *minus_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); +extern bool minus_text_textset(const text *txt, const Set *s, text **result); +extern Set *minus_textset_text(const Set *s, const text *txt); +extern bool minus_timestamp_period(TimestampTz t, const Span *s, TimestampTz *result); +extern bool minus_timestamp_periodset(TimestampTz t, const SpanSet *ss, TimestampTz *result); +extern bool minus_timestamp_timestampset(TimestampTz t, const Set *s, TimestampTz *result); +extern Set *minus_timestampset_timestamp(const Set *s, TimestampTz t); +extern Set *union_bigintset_bigint(const Set *s, int64 i); +extern SpanSet *union_bigintspan_bigint(const Span *s, int64 i); +extern SpanSet *union_bigintspanset_bigint(const SpanSet *ss, int64 i); +extern Set *union_floatset_float(const Set *s, double d); +extern SpanSet *union_floatspan_float(const Span *s, double d); +extern SpanSet *union_floatspanset_float(const SpanSet *ss, double d); +extern Set *union_geoset_geo(const Set *s, const GSERIALIZED *gs); +extern Set *union_intset_int(const Set *s, int i); +extern SpanSet *union_intspan_int(const Span *s, int i); +extern SpanSet *union_intspanset_int(const SpanSet *ss, int i); +extern SpanSet *union_period_timestamp(const Span *s, TimestampTz t); +extern SpanSet *union_periodset_timestamp(SpanSet *ss, TimestampTz t); +extern Set *union_set_set(const Set *s1, const Set *s2); +extern SpanSet *union_span_span(const Span *s1, const Span *s2); +extern SpanSet *union_spanset_span(const SpanSet *ss, const Span *s); +extern SpanSet *union_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); +extern Set *union_textset_text(const Set *s, const text *txt); +extern Set *union_timestampset_timestamp(const Set *s, const TimestampTz t); /***************************************************************************** * Bounding box functions for set and span types @@ -1041,164 +1165,151 @@ extern bool overlaps_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); +extern bool after_period_timestamp(const Span *s, TimestampTz t); +extern bool after_periodset_timestamp(const SpanSet *ss, TimestampTz t); +extern bool after_timestamp_period(TimestampTz t, const Span *s); +extern bool after_timestamp_periodset(TimestampTz t, const SpanSet *ss); extern bool after_timestamp_timestampset(TimestampTz t, const Set *ts); -extern bool before_periodset_timestamp(const SpanSet *ps, TimestampTz t); +extern bool after_timestampset_timestamp(const Set *s, TimestampTz t); +extern bool before_period_timestamp(const Span *s, TimestampTz t); +extern bool before_periodset_timestamp(const SpanSet *ss, TimestampTz t); +extern bool before_timestamp_period(TimestampTz t, const Span *s); +extern bool before_timestamp_periodset(TimestampTz t, const SpanSet *ss); extern bool before_timestamp_timestampset(TimestampTz t, const Set *ts); +extern bool before_timestampset_timestamp(const Set *s, TimestampTz t); +extern bool left_bigint_bigintset(int64 i, const Set *s); +extern bool left_bigint_bigintspan(int64 i, const Span *s); +extern bool left_bigint_bigintspanset(int64 i, const SpanSet *ss); +extern bool left_bigintset_bigint(const Set *s, int64 i); +extern bool left_bigintspan_bigint(const Span *s, int64 i); +extern bool left_bigintspanset_bigint(const SpanSet *ss, int64 i); +extern bool left_float_floatset(double d, const Set *s); extern bool left_float_floatspan(double d, const Span *s); +extern bool left_float_floatspanset(double d, const SpanSet *ss); +extern bool left_floatset_float(const Set *s, double d); extern bool left_floatspan_float(const Span *s, double d); +extern bool left_floatspanset_float(const SpanSet *ss, double d); +extern bool left_int_intset(int i, const Set *s); extern bool left_int_intspan(int i, const Span *s); +extern bool left_int_intspanset(int i, const SpanSet *ss); +extern bool left_intset_int(const Set *s, int i); extern bool left_intspan_int(const Span *s, int i); +extern bool left_intspanset_int(const SpanSet *ss, int i); extern bool left_set_set(const Set *s1, const Set *s2); extern bool left_span_span(const Span *s1, const Span *s2); extern bool left_span_spanset(const Span *s, const SpanSet *ss); extern bool left_spanset_span(const SpanSet *ss, const Span *s); extern bool left_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); -extern bool overafter_period_timestamp(const Span *p, TimestampTz t); -extern bool overafter_periodset_timestamp(const SpanSet *ps, TimestampTz t); -extern bool overafter_timestamp_period(TimestampTz t, const Span *p); -extern bool overafter_timestamp_periodset(TimestampTz t, const SpanSet *ps); +extern bool left_text_textset(text *txt, const Set *s); +extern bool left_textset_text(const Set *s, text *txt); +extern bool overafter_period_timestamp(const Span *s, TimestampTz t); +extern bool overafter_periodset_timestamp(const SpanSet *ss, TimestampTz t); +extern bool overafter_timestamp_period(TimestampTz t, const Span *s); +extern bool overafter_timestamp_periodset(TimestampTz t, const SpanSet *ss); extern bool overafter_timestamp_timestampset(TimestampTz t, const Set *ts); -extern bool overbefore_period_timestamp(const Span *p, TimestampTz t); -extern bool overbefore_periodset_timestamp(const SpanSet *ps, TimestampTz t); -extern bool overbefore_timestamp_period(TimestampTz t, const Span *p); -extern bool overbefore_timestamp_periodset(TimestampTz t, const SpanSet *ps); +extern bool overafter_timestampset_timestamp(const Set *s, TimestampTz t); +extern bool overbefore_period_timestamp(const Span *s, TimestampTz t); +extern bool overbefore_periodset_timestamp(const SpanSet *ss, TimestampTz t); +extern bool overbefore_timestamp_period(TimestampTz t, const Span *s); +extern bool overbefore_timestamp_periodset(TimestampTz t, const SpanSet *ss); extern bool overbefore_timestamp_timestampset(TimestampTz t, const Set *ts); +extern bool overbefore_timestampset_timestamp(const Set *s, TimestampTz t); +extern bool overleft_bigint_bigintset(int64 i, const Set *s); +extern bool overleft_bigint_bigintspan(int64 i, const Span *s); +extern bool overleft_bigint_bigintspanset(int64 i, const SpanSet *ss); +extern bool overleft_bigintset_bigint(const Set *s, int64 i); +extern bool overleft_bigintspan_bigint(const Span *s, int64 i); +extern bool overleft_bigintspanset_bigint(const SpanSet *ss, int64 i); +extern bool overleft_float_floatset(double d, const Set *s); extern bool overleft_float_floatspan(double d, const Span *s); +extern bool overleft_float_floatspanset(double d, const SpanSet *ss); +extern bool overleft_floatset_float(const Set *s, double d); extern bool overleft_floatspan_float(const Span *s, double d); +extern bool overleft_floatspanset_float(const SpanSet *ss, double d); +extern bool overleft_int_intset(int i, const Set *s); extern bool overleft_int_intspan(int i, const Span *s); +extern bool overleft_int_intspanset(int i, const SpanSet *ss); +extern bool overleft_intset_int(const Set *s, int i); extern bool overleft_intspan_int(const Span *s, int i); +extern bool overleft_intspanset_int(const SpanSet *ss, int i); extern bool overleft_set_set(const Set *s1, const Set *s2); extern bool overleft_span_span(const Span *s1, const Span *s2); extern bool overleft_span_spanset(const Span *s, const SpanSet *ss); extern bool overleft_spanset_span(const SpanSet *ss, const Span *s); extern bool overleft_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); +extern bool overleft_text_textset(text *txt, const Set *s); +extern bool overleft_textset_text(const Set *s, text *txt); +extern bool overright_bigint_bigintset(int64 i, const Set *s); +extern bool overright_bigint_bigintspan(int64 i, const Span *s); +extern bool overright_bigint_bigintspanset(int64 i, const SpanSet *ss); +extern bool overright_bigintset_bigint(const Set *s, int64 i); +extern bool overright_bigintspan_bigint(const Span *s, int64 i); +extern bool overright_bigintspanset_bigint(const SpanSet *ss, int64 i); +extern bool overright_float_floatset(double d, const Set *s); extern bool overright_float_floatspan(double d, const Span *s); +extern bool overright_float_floatspanset(double d, const SpanSet *ss); +extern bool overright_floatset_float(const Set *s, double d); extern bool overright_floatspan_float(const Span *s, double d); +extern bool overright_floatspanset_float(const SpanSet *ss, double d); +extern bool overright_int_intset(int i, const Set *s); extern bool overright_int_intspan(int i, const Span *s); +extern bool overright_int_intspanset(int i, const SpanSet *ss); +extern bool overright_intset_int(const Set *s, int i); extern bool overright_intspan_int(const Span *s, int i); +extern bool overright_intspanset_int(const SpanSet *ss, int i); extern bool overright_set_set(const Set *s1, const Set *s2); extern bool overright_span_span(const Span *s1, const Span *s2); extern bool overright_span_spanset(const Span *s, const SpanSet *ss); extern bool overright_spanset_span(const SpanSet *ss, const Span *s); extern bool overright_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); +extern bool overright_text_textset(text *txt, const Set *s); +extern bool overright_textset_text(const Set *s, text *txt); +extern bool right_bigint_bigintset(int64 i, const Set *s); +extern bool right_bigint_bigintspan(int64 i, const Span *s); +extern bool right_bigint_bigintspanset(int64 i, const SpanSet *ss); +extern bool right_bigintset_bigint(const Set *s, int64 i); +extern bool right_bigintspan_bigint(const Span *s, int64 i); +extern bool right_bigintspanset_bigint(const SpanSet *ss, int64 i); +extern bool right_float_floatset(double d, const Set *s); extern bool right_float_floatspan(double d, const Span *s); +extern bool right_float_floatspanset(double d, const SpanSet *ss); +extern bool right_floatset_float(const Set *s, double d); extern bool right_floatspan_float(const Span *s, double d); +extern bool right_floatspanset_float(const SpanSet *ss, double d); +extern bool right_int_intset(int i, const Set *s); extern bool right_int_intspan(int i, const Span *s); +extern bool right_int_intspanset(int i, const SpanSet *ss); +extern bool right_intset_int(const Set *s, int i); extern bool right_intspan_int(const Span *s, int i); +extern bool right_intspanset_int(const SpanSet *ss, int i); extern bool right_set_set(const Set *s1, const Set *s2); extern bool right_span_span(const Span *s1, const Span *s2); extern bool right_span_spanset(const Span *s, const SpanSet *ss); extern bool right_spanset_span(const SpanSet *ss, const Span *s); extern bool right_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); +extern bool right_text_textset(text *txt, const Set *s); +extern bool right_textset_text(const Set *s, text *txt); -extern bool intersection_bigintset_bigint(const Set *s, int64 i, int64 *result); -extern bool intersection_bigintspan_bigint(const Span *s, int64 i, int64 *result); -extern bool intersection_bigintspanset_bigint(const SpanSet *ss, int64 i, int64 *result); -extern bool intersection_floatset_float(const Set *s, double d, double *result); -extern bool intersection_floatspan_float(const Span *s, double d, double *result); -extern bool intersection_floatspanset_float(const SpanSet *ss, double d, double *result); -extern bool intersection_intset_int(const Set *s, int i, int *result); -extern bool intersection_intspan_int(const Span *s, int i, int *result); -extern bool intersection_intspanset_int(const SpanSet *ss, int i, int *result); -extern bool intersection_period_timestamp(const Span *s, TimestampTz t, TimestampTz *result); -extern bool intersection_periodset_timestamp(const SpanSet *ss, TimestampTz t, TimestampTz *result); -extern Set *intersection_set_set(const Set *s1, const Set *s2); -extern Span *intersection_span_span(const Span *s1, const Span *s2); -extern SpanSet *intersection_spanset_span(const SpanSet *ss, const Span *s); -extern SpanSet *intersection_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); -extern bool intersection_textset_text(const Set *s, const text *txt, text **result); -extern bool intersection_timestampset_timestamp(const Set *s, TimestampTz t, TimestampTz *result); -extern bool minus_bigint_bigintset(int64 i, const Set *s, int64 *result); -extern bool minus_bigint_bigintspan(int64 i, const Span *s, int64 *result); -extern bool minus_bigint_bigintspanset(int64 i, const SpanSet *ss, int64 *result); -extern Set *minus_bigintset_bigint(const Set *s, int64 i); -extern SpanSet *minus_bigintspan_bigint(const Span *s, int64 i); -extern SpanSet *minus_bigintspanset_bigint(const SpanSet *ss, int64 i); -extern bool minus_float_floatset(double d, const Set *s, double *result); -extern bool minus_float_floatspan(double d, const Span *s, double *result); -extern bool minus_float_floatspanset(double d, const SpanSet *ss, double *result); -extern Set *minus_floatset_float(const Set *s, double d); -extern SpanSet *minus_floatspan_float(const Span *s, double d); -extern SpanSet *minus_floatspanset_float(const SpanSet *ss, double d); -extern bool minus_int_intset(int i, const Set *s, int *result); -extern bool minus_int_intspan(int i, const Span *s, int *result); -extern bool minus_int_intspanset(int i, const SpanSet *ss, int *result); -extern Set *minus_intset_int(const Set *s, int i); -extern SpanSet *minus_intspan_int(const Span *s, int i); -extern SpanSet *minus_intspanset_int(const SpanSet *ss, int i); -extern SpanSet *minus_period_timestamp(const Span *s, TimestampTz t); -extern SpanSet *minus_periodset_timestamp(const SpanSet *ss, TimestampTz t); -extern Set *minus_set_set(const Set *s1, const Set *s2); -extern SpanSet *minus_span_span(const Span *s1, const Span *s2); -extern SpanSet *minus_span_spanset(const Span *s, const SpanSet *ss); -extern SpanSet *minus_spanset_span(const SpanSet *ss, const Span *s); -extern SpanSet *minus_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); -extern bool minus_text_textset(const text *txt, const Set *s, text **result); -extern Set *minus_textset_text(const Set *s, const text *txt); -extern bool minus_timestamp_period(TimestampTz t, const Span *s, TimestampTz *result); -extern bool minus_timestamp_periodset(TimestampTz t, const SpanSet *ss, TimestampTz *result); -extern Set *minus_timestampset_timestamp(const Set *s, TimestampTz t); -extern Set *union_bigintset_bigint(const Set *s, int64 i); -extern SpanSet *union_bigintspan_bigint(const Span *s, int64 i); -extern SpanSet *union_bigintspanset_bigint(const SpanSet *ss, int64 i); -extern Set *union_floatset_float(const Set *s, double d); -extern SpanSet *union_floatspan_float(const Span *s, double d); -extern SpanSet *union_floatspanset_float(const SpanSet *ss, double d); -extern Set *union_intset_int(const Set *s, int i); -extern SpanSet *union_intspan_int(const Span *s, int i); -extern SpanSet *union_intspanset_int(const SpanSet *ss, int i); -extern SpanSet *union_period_timestamp(const Span *s, TimestampTz t); -extern SpanSet *union_periodset_timestamp(SpanSet *ss, TimestampTz t); -extern Set *union_set_set(const Set *s1, const Set *s2); -extern SpanSet *union_span_span(const Span *s1, const Span *s2); -extern SpanSet *union_spanset_span(const SpanSet *ss, const Span *s); -extern SpanSet *union_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); -extern Set *union_textset_text(const Set *s, text *txt); -extern Set *union_timestampset_timestamp(const Set *s, const TimestampTz t); - - - - - +extern double distance_bigintset_bigint(const Set *s, int64 i); +extern double distance_bigintspan_bigint(const Span *s, int64 i); +extern double distance_bigintspanset_bigint(const SpanSet *ss, int64 i); +extern double distance_floatset_float(const Set *s, double d); extern double distance_floatspan_float(const Span *s, double d); +extern double distance_floatspanset_float(const SpanSet *ss, double d); +extern double distance_intset_int(const Set *s, int i); extern double distance_intspan_int(const Span *s, int i); +extern double distance_intspanset_int(const SpanSet *ss, int i); +extern double distance_period_timestamp(const Span *s, TimestampTz t); +extern double distance_periodset_timestamp(const SpanSet *ss, TimestampTz t); extern double distance_set_set(const Set *s1, const Set *s2); -extern double distance_period_timestamp(const Span *p, TimestampTz t); -extern double distance_periodset_timestamp(const SpanSet *ps, TimestampTz t); extern double distance_span_span(const Span *s1, const Span *s2); extern double distance_spanset_span(const SpanSet *ss, const Span *s); extern double distance_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); -extern double distance_timestampset_timestamp(const Set *ts, TimestampTz t); - - - - - -extern Span *bigint_extent_transfn(Span *s, int64 i); -extern Set *bigint_union_transfn(Set *state, int64 i); -extern Span *float_extent_transfn(Span *s, double d); -extern Set *float_union_transfn(Set *state, double d); -extern Span *int_extent_transfn(Span *s, int i); -extern Set *int_union_transfn(Set *state, int i); -extern SkipList *period_tcount_transfn(SkipList *state, const Span *p); -extern SkipList *periodset_tcount_transfn(SkipList *state, const SpanSet *ps); -extern Span *set_extent_transfn(Span *span, const Set *set); -extern Set *set_union_finalfn(Set *state); -extern Set *set_union_transfn(Set *state, Set *set); -extern Span *span_extent_transfn(Span *s1, const Span *s2); -extern SpanSet *span_union_transfn(SpanSet *state, const Span *span); -extern Span *spanset_extent_transfn(Span *s, const SpanSet *ss); -extern SpanSet *spanset_union_finalfn(SpanSet *state); -extern SpanSet *spanset_union_transfn(SpanSet *state, const SpanSet *ss); -extern Set *text_union_transfn(Set *state, const text *txt); -extern Span *timestamp_extent_transfn(Span *p, TimestampTz t); -extern SkipList *timestamp_tcount_transfn(SkipList *state, TimestampTz t); -extern Set *timestamp_union_transfn(Set *state, TimestampTz t); -extern SkipList *timestampset_tcount_transfn(SkipList *state, const Set *ts); +extern double distance_timestampset_timestamp(const Set *s, TimestampTz t); @@ -1226,6 +1337,32 @@ extern bool spanset_le(const SpanSet *ss1, const SpanSet *ss2); extern bool spanset_lt(const SpanSet *ss1, const SpanSet *ss2); extern bool spanset_ne(const SpanSet *ss1, const SpanSet *ss2); + + + + +extern Span *bigint_extent_transfn(Span *s, int64 i); +extern Set *bigint_union_transfn(Set *state, int64 i); +extern Span *float_extent_transfn(Span *s, double d); +extern Set *float_union_transfn(Set *state, double d); +extern Span *int_extent_transfn(Span *s, int i); +extern Set *int_union_transfn(Set *state, int i); +extern SkipList *period_tcount_transfn(SkipList *state, const Span *p); +extern SkipList *periodset_tcount_transfn(SkipList *state, const SpanSet *ps); +extern Span *set_extent_transfn(Span *span, const Set *set); +extern Set *set_union_finalfn(Set *state); +extern Set *set_union_transfn(Set *state, Set *set); +extern Span *span_extent_transfn(Span *s1, const Span *s2); +extern SpanSet *span_union_transfn(SpanSet *state, const Span *span); +extern Span *spanset_extent_transfn(Span *s, const SpanSet *ss); +extern SpanSet *spanset_union_finalfn(SpanSet *state); +extern SpanSet *spanset_union_transfn(SpanSet *state, const SpanSet *ss); +extern Set *text_union_transfn(Set *state, const text *txt); +extern Span *timestamp_extent_transfn(Span *p, TimestampTz t); +extern SkipList *timestamp_tcount_transfn(SkipList *state, TimestampTz t); +extern Set *timestamp_union_transfn(Set *state, TimestampTz t); +extern SkipList *timestampset_tcount_transfn(SkipList *state, const Set *ts); + /****************************************************************************** * Functions for box types *****************************************************************************/ @@ -1249,72 +1386,74 @@ extern char *stbox_out(const STBox *box, int maxdd); +extern TBox *float_period_to_tbox(double d, const Span *p); +extern TBox *float_timestamp_to_tbox(double d, TimestampTz t); +extern STBox *geo_period_to_stbox(const GSERIALIZED *gs, const Span *p); +extern STBox *geo_timestamp_to_stbox(const GSERIALIZED *gs, TimestampTz t); +extern TBox *int_period_to_tbox(int i, const Span *p); +extern TBox *int_timestamp_to_tbox(int i, TimestampTz t); +extern TBox *span_period_to_tbox(const Span *span, const Span *p); +extern TBox *span_timestamp_to_tbox(const Span *span, TimestampTz t); +extern STBox *stbox_copy(const STBox *box); extern STBox * stbox_make(bool hasx, bool hasz, bool geodetic, int32 srid, double xmin, double xmax, double ymin, double ymax, double zmin, double zmax, const Span *p); -extern STBox *stbox_copy(const STBox *box); -extern TBox *tbox_make(const Span *s, const Span *p); extern TBox *tbox_copy(const TBox *box); +extern TBox *tbox_make(const Span *s, const Span *p); -extern TBox *int_to_tbox(int i); extern TBox *float_to_tbox(double d); -extern TBox *timestamp_to_tbox(TimestampTz t); -extern TBox *timestampset_to_tbox(const Set *ss); -extern TBox *period_to_tbox(const Span *p); -extern TBox *periodset_to_tbox(const SpanSet *ps); -extern TBox *int_timestamp_to_tbox(int i, TimestampTz t); -extern TBox *float_period_to_tbox(double d, const Span *p); -extern TBox *float_timestamp_to_tbox(double d, TimestampTz t); -extern STBox *geo_period_to_stbox(const GSERIALIZED *gs, const Span *p); -extern STBox *geo_timestamp_to_stbox(const GSERIALIZED *gs, TimestampTz t); extern STBox *geo_to_stbox(const GSERIALIZED *gs); -extern TBox *int_period_to_tbox(int i, const Span *p); +extern TBox *int_to_tbox(int i); +extern TBox *numset_to_tbox(const Set *s); extern TBox *numspan_to_tbox(const Span *s); -extern TBox *span_timestamp_to_tbox(const Span *span, TimestampTz t); -extern TBox *span_period_to_tbox(const Span *span, const Span *p); +extern TBox *numspanset_to_tbox(const SpanSet *ss); +extern STBox *period_to_stbox(const Span *p); +extern TBox *period_to_tbox(const Span *p); +extern STBox *periodset_to_stbox(const SpanSet *ps); +extern TBox *periodset_to_tbox(const SpanSet *ps); +extern GSERIALIZED *stbox_to_geo(const STBox *box); +extern Span *stbox_to_period(const STBox *box); extern Span *tbox_to_floatspan(const TBox *box); extern Span *tbox_to_period(const TBox *box); -extern Span *stbox_to_period(const STBox *box); -extern TBox *tnumber_to_tbox(const Temporal *temp); -extern GSERIALIZED *stbox_to_geo(const STBox *box); -extern STBox *tpoint_to_stbox(const Temporal *temp); extern STBox *timestamp_to_stbox(TimestampTz t); +extern TBox *timestamp_to_tbox(TimestampTz t); extern STBox *timestampset_to_stbox(const Set *ts); -extern STBox *period_to_stbox(const Span *p); -extern STBox *periodset_to_stbox(const SpanSet *ps); +extern TBox *timestampset_to_tbox(const Set *ss); +extern TBox *tnumber_to_tbox(const Temporal *temp); +extern STBox *tpoint_to_stbox(const Temporal *temp); -extern bool tbox_hasx(const TBox *box); -extern bool tbox_hast(const TBox *box); -extern bool tbox_xmin(const TBox *box, double *result); -extern bool tbox_xmin_inc(const TBox *box, bool *result); -extern bool tbox_xmax(const TBox *box, double *result); -extern bool tbox_xmax_inc(const TBox *box, bool *result); -extern bool tbox_tmin(const TBox *box, TimestampTz *result); -extern bool tbox_tmin_inc(const TBox *box, bool *result); -extern bool tbox_tmax(const TBox *box, TimestampTz *result); -extern bool tbox_tmax_inc(const TBox *box, bool *result); +extern bool stbox_hast(const STBox *box); extern bool stbox_hasx(const STBox *box); extern bool stbox_hasz(const STBox *box); -extern bool stbox_hast(const STBox *box); extern bool stbox_isgeodetic(const STBox *box); -extern bool stbox_xmin(const STBox *box, double *result); +extern int32 stbox_srid(const STBox *box); +extern bool stbox_tmax(const STBox *box, TimestampTz *result); +extern bool stbox_tmax_inc(const STBox *box, bool *result); +extern bool stbox_tmin(const STBox *box, TimestampTz *result); +extern bool stbox_tmin_inc(const STBox *box, bool *result); extern bool stbox_xmax(const STBox *box, double *result); -extern bool stbox_ymin(const STBox *box, double *result); +extern bool stbox_xmin(const STBox *box, double *result); extern bool stbox_ymax(const STBox *box, double *result); -extern bool stbox_zmin(const STBox *box, double *result); +extern bool stbox_ymin(const STBox *box, double *result); extern bool stbox_zmax(const STBox *box, double *result); -extern bool stbox_tmin(const STBox *box, TimestampTz *result); -extern bool stbox_tmin_inc(const STBox *box, bool *result); -extern bool stbox_tmax(const STBox *box, TimestampTz *result); -extern bool stbox_tmax_inc(const STBox *box, bool *result); -extern int32 stbox_srid(const STBox *box); +extern bool stbox_zmin(const STBox *box, double *result); +extern bool tbox_hast(const TBox *box); +extern bool tbox_hasx(const TBox *box); +extern bool tbox_tmax(const TBox *box, TimestampTz *result); +extern bool tbox_tmax_inc(const TBox *box, bool *result); +extern bool tbox_tmin(const TBox *box, TimestampTz *result); +extern bool tbox_tmin_inc(const TBox *box, bool *result); +extern bool tbox_xmax(const TBox *box, double *result); +extern bool tbox_xmax_inc(const TBox *box, bool *result); +extern bool tbox_xmin(const TBox *box, double *result); +extern bool tbox_xmin_inc(const TBox *box, bool *result); @@ -1325,11 +1464,28 @@ extern STBox *stbox_expand_time(const STBox *box, const Interval *interval); extern STBox *stbox_get_space(const STBox *box); extern STBox *stbox_round(const STBox *box, int maxdd); extern STBox *stbox_set_srid(const STBox *box, int32 srid); -extern TBox *tbox_expand_value(const TBox *box, const double d); +extern STBox *stbox_shift_scale_time(const STBox *box, const Interval *shift, const Interval *duration); extern TBox *tbox_expand_time(const TBox *box, const Interval *interval); +extern TBox *tbox_expand_value(const TBox *box, const double d); extern TBox *tbox_round(const TBox *box, int maxdd); +extern TBox *tbox_shift_scale_float(const TBox *box, double shift, double width, bool hasshift, bool haswidth); +extern TBox *tbox_shift_scale_int(const TBox *box, int shift, int width, bool hasshift, bool haswidth); +extern TBox *tbox_shift_scale_time(const TBox *box, const Interval *shift, const Interval *duration); + + + +extern TBox *union_tbox_tbox(const TBox *box1, const TBox *box2, bool strict); +extern bool inter_tbox_tbox(const TBox *box1, const TBox *box2, TBox *result); +extern TBox *intersection_tbox_tbox(const TBox *box1, const TBox *box2); +extern STBox *union_stbox_stbox(const STBox *box1, const STBox *box2, bool strict); +extern bool inter_stbox_stbox(const STBox *box1, const STBox *box2, STBox *result); +extern STBox *intersection_stbox_stbox(const STBox *box1, const STBox *box2); + +/***************************************************************************** + * Bounding box functions for box types + *****************************************************************************/ @@ -1377,17 +1533,6 @@ extern bool overafter_stbox_stbox(const STBox *box1, const STBox *box2); -extern TBox *union_tbox_tbox(const TBox *box1, const TBox *box2, bool strict); -extern bool inter_tbox_tbox(const TBox *box1, const TBox *box2, TBox *result); -extern TBox *intersection_tbox_tbox(const TBox *box1, const TBox *box2); -extern STBox *union_stbox_stbox(const STBox *box1, const STBox *box2, bool strict); -extern bool inter_stbox_stbox(const STBox *box1, const STBox *box2, STBox *result); -extern STBox *intersection_stbox_stbox(const STBox *box1, const STBox *box2); - - - - - extern STBox *stbox_quad_split(const STBox *box, int *count); @@ -1461,9 +1606,7 @@ extern TSequence *tpointseq_from_base_period(const GSERIALIZED *gs, const Span * extern TSequence *tpointseq_from_base_timestampset(const GSERIALIZED *gs, const Set *ts); extern TSequenceSet *tpointseqset_from_base_periodset(const GSERIALIZED *gs, const SpanSet *ps, interpType interp); extern TSequence *tsequence_make(const TInstant **instants, int count, bool lower_inc, bool upper_inc, interpType interp, bool normalize); -extern TSequence *tsequence_make_exp(const TInstant **instants, int count, int maxcount, bool lower_inc, bool upper_inc, interpType interp, bool normalize); extern TSequenceSet *tsequenceset_make(const TSequence **sequences, int count, bool normalize); -extern TSequenceSet *tsequenceset_make_exp(const TSequence **sequences, int count, int maxcount, bool normalize); extern TSequenceSet *tsequenceset_make_gaps(const TInstant **instants, int count, interpType interp, Interval *maxt, double maxdist); extern Temporal *ttext_from_base_temp(const text *txt, const Temporal *temp); extern TInstant *ttextinst_make(const text *txt, TimestampTz t); @@ -1535,15 +1678,34 @@ extern text **ttext_values(const Temporal *temp, int *count); +extern Temporal *temporal_scale_time(const Temporal *temp, const Interval *duration); extern Temporal *temporal_set_interp(const Temporal *temp, interpType interp); -extern Temporal *temporal_shift(const Temporal *temp, const Interval *shift); -extern Temporal *temporal_shift_tscale(const Temporal *temp, const Interval *shift, const Interval *duration); +extern Temporal *temporal_shift_scale_time(const Temporal *temp, const Interval *shift, const Interval *duration); +extern Temporal *temporal_shift_time(const Temporal *temp, const Interval *shift); extern Temporal *temporal_to_tinstant(const Temporal *temp); -extern Temporal *temporal_to_tsequence(const Temporal *temp); -extern Temporal *temporal_to_tsequenceset(const Temporal *temp); -extern Temporal *temporal_tprecision(const Temporal *temp, const Interval *duration, TimestampTz origin); -extern Temporal *temporal_tsample(const Temporal *temp, const Interval *duration, TimestampTz origin); -extern Temporal *temporal_tscale(const Temporal *temp, const Interval *duration); +extern Temporal *temporal_to_tsequence(const Temporal *temp, interpType interp); +extern Temporal *temporal_to_tsequenceset(const Temporal *temp, interpType interp); +extern Temporal *tfloat_scale_value(const Temporal *temp, double width); +extern Temporal *tfloat_shift_scale_value(const Temporal *temp, double shift, double width); +extern Temporal *tfloat_shift_value(const Temporal *temp, double shift); +extern Temporal *tint_scale_value(const Temporal *temp, int width); +extern Temporal *tint_shift_scale_value(const Temporal *temp, int shift, int width); +extern Temporal *tint_shift_value(const Temporal *temp, int shift); + + + + + +extern Temporal *temporal_append_tinstant(Temporal *temp, const TInstant *inst, double maxdist, Interval *maxt, bool expand); +extern Temporal *temporal_append_tsequence(Temporal *temp, const TSequence *seq, bool expand); +extern Temporal *temporal_delete_period(const Temporal *temp, const Span *p, bool connect); +extern Temporal *temporal_delete_periodset(const Temporal *temp, const SpanSet *ps, bool connect); +extern Temporal *temporal_delete_timestamp(const Temporal *temp, TimestampTz t, bool connect); +extern Temporal *temporal_delete_timestampset(const Temporal *temp, const Set *ts, bool connect); +extern Temporal *temporal_insert(const Temporal *temp1, const Temporal *temp2, bool connect); +extern Temporal *temporal_merge(const Temporal *temp1, const Temporal *temp2); +extern Temporal *temporal_merge_array(Temporal **temparr, int count); +extern Temporal *temporal_update(const Temporal *temp1, const Temporal *temp2, bool connect); @@ -1589,101 +1751,19 @@ extern Temporal *ttext_at_value(const Temporal *temp, text *txt); extern Temporal *ttext_minus_value(const Temporal *temp, text *txt); extern bool ttext_value_at_timestamp(const Temporal *temp, TimestampTz t, bool strict, text **value); +/***************************************************************************** + * Comparison functions for temporal types + *****************************************************************************/ - -extern Temporal *temporal_append_tinstant(Temporal *temp, const TInstant *inst, double maxdist, Interval *maxt, bool expand); -extern Temporal *temporal_append_tsequence(Temporal *temp, const TSequence *seq, bool expand); -extern Temporal *temporal_delete_period(const Temporal *temp, const Span *p, bool connect); -extern Temporal *temporal_delete_periodset(const Temporal *temp, const SpanSet *ps, bool connect); -extern Temporal *temporal_delete_timestamp(const Temporal *temp, TimestampTz t, bool connect); -extern Temporal *temporal_delete_timestampset(const Temporal *temp, const Set *ts, bool connect); -extern Temporal *temporal_insert(const Temporal *temp1, const Temporal *temp2, bool connect); -extern Temporal *temporal_merge(const Temporal *temp1, const Temporal *temp2); -extern Temporal *temporal_merge_array(Temporal **temparr, int count); -extern Temporal *temporal_update(const Temporal *temp1, const Temporal *temp2, bool connect); - - - - - -extern Temporal *tand_bool_tbool(bool b, const Temporal *temp); -extern Temporal *tand_tbool_bool(const Temporal *temp, bool b); -extern Temporal *tand_tbool_tbool(const Temporal *temp1, const Temporal *temp2); -extern SpanSet *tbool_when_true(const Temporal *temp); -extern Temporal *tnot_tbool(const Temporal *temp); -extern Temporal *tor_bool_tbool(bool b, const Temporal *temp); -extern Temporal *tor_tbool_bool(const Temporal *temp, bool b); -extern Temporal *tor_tbool_tbool(const Temporal *temp1, const Temporal *temp2); - - - - - -extern Temporal *add_float_tfloat(double d, const Temporal *tnumber); -extern Temporal *add_int_tint(int i, const Temporal *tnumber); -extern Temporal *add_tfloat_float(const Temporal *tnumber, double d); -extern Temporal *add_tint_int(const Temporal *tnumber, int i); -extern Temporal *add_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); -extern Temporal *div_float_tfloat(double d, const Temporal *tnumber); -extern Temporal *div_int_tint(int i, const Temporal *tnumber); -extern Temporal *div_tfloat_float(const Temporal *tnumber, double d); -extern Temporal *div_tint_int(const Temporal *tnumber, int i); -extern Temporal *div_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); -extern double float_degrees(double value, bool normalize); -extern Temporal *mult_float_tfloat(double d, const Temporal *tnumber); -extern Temporal *mult_int_tint(int i, const Temporal *tnumber); -extern Temporal *mult_tfloat_float(const Temporal *tnumber, double d); -extern Temporal *mult_tint_int(const Temporal *tnumber, int i); -extern Temporal *mult_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); -extern Temporal *sub_float_tfloat(double d, const Temporal *tnumber); -extern Temporal *sub_int_tint(int i, const Temporal *tnumber); -extern Temporal *sub_tfloat_float(const Temporal *tnumber, double d); -extern Temporal *sub_tint_int(const Temporal *tnumber, int i); -extern Temporal *sub_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); -extern Temporal *tfloat_round(const Temporal *temp, int maxdd); -extern Temporal *tfloat_degrees(const Temporal *temp, bool normalize); -extern Temporal *tfloat_derivative(const Temporal *temp); -extern Temporal *tfloat_radians(const Temporal *temp); -extern Temporal *tnumber_abs(const Temporal *temp); -extern Temporal *tnumber_angular_difference(const Temporal *temp); -extern Temporal *tnumber_delta_value(const Temporal *temp); - - - - - -extern Temporal *textcat_text_ttext(const text *txt, const Temporal *temp); -extern Temporal *textcat_ttext_text(const Temporal *temp, const text *txt); -extern Temporal *textcat_ttext_ttext(const Temporal *temp1, const Temporal *temp2); -extern Temporal *ttext_upper(const Temporal *temp); -extern Temporal *ttext_lower(const Temporal *temp); - - - - - -extern Temporal *distance_tfloat_float(const Temporal *temp, double d); -extern Temporal *distance_tint_int(const Temporal *temp, int i); -extern Temporal *distance_tnumber_tnumber(const Temporal *temp1, const Temporal *temp2); -extern Temporal *distance_tpoint_geo(const Temporal *temp, const GSERIALIZED *geo); -extern Temporal *distance_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2); -extern double nad_stbox_geo(const STBox *box, const GSERIALIZED *gs); -extern double nad_stbox_stbox(const STBox *box1, const STBox *box2); -extern double nad_tbox_tbox(const TBox *box1, const TBox *box2); -extern double nad_tfloat_float(const Temporal *temp, double d); -extern double nad_tfloat_tfloat(const Temporal *temp1, const Temporal *temp2); -extern int nad_tint_int(const Temporal *temp, int i); -extern int nad_tint_tint(const Temporal *temp1, const Temporal *temp2); -extern double nad_tnumber_tbox(const Temporal *temp, const TBox *box); -extern double nad_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs); -extern double nad_tpoint_stbox(const Temporal *temp, const STBox *box); -extern double nad_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2); -extern TInstant *nai_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs); -extern TInstant *nai_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2); -extern bool shortestline_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs, GSERIALIZED **result); -extern bool shortestline_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2, GSERIALIZED **result); +extern int temporal_cmp(const Temporal *temp1, const Temporal *temp2); +extern bool temporal_eq(const Temporal *temp1, const Temporal *temp2); +extern bool temporal_ge(const Temporal *temp1, const Temporal *temp2); +extern bool temporal_gt(const Temporal *temp1, const Temporal *temp2); +extern bool temporal_le(const Temporal *temp1, const Temporal *temp2); +extern bool temporal_lt(const Temporal *temp1, const Temporal *temp2); +extern bool temporal_ne(const Temporal *temp1, const Temporal *temp2); @@ -1716,13 +1796,6 @@ extern bool ttext_ever_lt(const Temporal *temp, text *txt); -extern int temporal_cmp(const Temporal *temp1, const Temporal *temp2); -extern bool temporal_eq(const Temporal *temp1, const Temporal *temp2); -extern bool temporal_ge(const Temporal *temp1, const Temporal *temp2); -extern bool temporal_gt(const Temporal *temp1, const Temporal *temp2); -extern bool temporal_le(const Temporal *temp1, const Temporal *temp2); -extern bool temporal_lt(const Temporal *temp1, const Temporal *temp2); -extern bool temporal_ne(const Temporal *temp1, const Temporal *temp2); extern Temporal *teq_bool_tbool(bool b, const Temporal *temp); extern Temporal *teq_float_tfloat(double d, const Temporal *temp); extern Temporal *teq_int_tint(int i, const Temporal *temp); @@ -1774,13 +1847,95 @@ extern Temporal *tne_tpoint_point(const Temporal *temp, const GSERIALIZED *gs); extern Temporal *tne_tint_int(const Temporal *temp, int i); extern Temporal *tne_ttext_text(const Temporal *temp, const text *txt); + + + + +extern Temporal *tand_bool_tbool(bool b, const Temporal *temp); +extern Temporal *tand_tbool_bool(const Temporal *temp, bool b); +extern Temporal *tand_tbool_tbool(const Temporal *temp1, const Temporal *temp2); +extern SpanSet *tbool_when_true(const Temporal *temp); +extern Temporal *tnot_tbool(const Temporal *temp); +extern Temporal *tor_bool_tbool(bool b, const Temporal *temp); +extern Temporal *tor_tbool_bool(const Temporal *temp, bool b); +extern Temporal *tor_tbool_tbool(const Temporal *temp1, const Temporal *temp2); + + + + + +extern Temporal *add_float_tfloat(double d, const Temporal *tnumber); +extern Temporal *add_int_tint(int i, const Temporal *tnumber); +extern Temporal *add_tfloat_float(const Temporal *tnumber, double d); +extern Temporal *add_tint_int(const Temporal *tnumber, int i); +extern Temporal *add_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); +extern Temporal *div_float_tfloat(double d, const Temporal *tnumber); +extern Temporal *div_int_tint(int i, const Temporal *tnumber); +extern Temporal *div_tfloat_float(const Temporal *tnumber, double d); +extern Temporal *div_tint_int(const Temporal *tnumber, int i); +extern Temporal *div_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); +extern double float_degrees(double value, bool normalize); +extern Temporal *mult_float_tfloat(double d, const Temporal *tnumber); +extern Temporal *mult_int_tint(int i, const Temporal *tnumber); +extern Temporal *mult_tfloat_float(const Temporal *tnumber, double d); +extern Temporal *mult_tint_int(const Temporal *tnumber, int i); +extern Temporal *mult_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); +extern Temporal *sub_float_tfloat(double d, const Temporal *tnumber); +extern Temporal *sub_int_tint(int i, const Temporal *tnumber); +extern Temporal *sub_tfloat_float(const Temporal *tnumber, double d); +extern Temporal *sub_tint_int(const Temporal *tnumber, int i); +extern Temporal *sub_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); +extern Temporal *tfloat_round(const Temporal *temp, int maxdd); +extern Temporal **tfloatarr_round(const Temporal **temp, int count, int maxdd); +extern Temporal *tfloat_degrees(const Temporal *temp, bool normalize); +extern Temporal *tfloat_derivative(const Temporal *temp); +extern Temporal *tfloat_radians(const Temporal *temp); +extern Temporal *tnumber_abs(const Temporal *temp); +extern Temporal *tnumber_angular_difference(const Temporal *temp); +extern Temporal *tnumber_delta_value(const Temporal *temp); + + + + + +extern Temporal *textcat_text_ttext(const text *txt, const Temporal *temp); +extern Temporal *textcat_ttext_text(const Temporal *temp, const text *txt); +extern Temporal *textcat_ttext_ttext(const Temporal *temp1, const Temporal *temp2); +extern Temporal *ttext_upper(const Temporal *temp); +extern Temporal *ttext_lower(const Temporal *temp); + + + + + +extern Temporal *distance_tfloat_float(const Temporal *temp, double d); +extern Temporal *distance_tint_int(const Temporal *temp, int i); +extern Temporal *distance_tnumber_tnumber(const Temporal *temp1, const Temporal *temp2); +extern Temporal *distance_tpoint_point(const Temporal *temp, const GSERIALIZED *gs); +extern Temporal *distance_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2); +extern double nad_stbox_geo(const STBox *box, const GSERIALIZED *gs); +extern double nad_stbox_stbox(const STBox *box1, const STBox *box2); +extern double nad_tbox_tbox(const TBox *box1, const TBox *box2); +extern double nad_tfloat_float(const Temporal *temp, double d); +extern double nad_tfloat_tfloat(const Temporal *temp1, const Temporal *temp2); +extern int nad_tint_int(const Temporal *temp, int i); +extern int nad_tint_tint(const Temporal *temp1, const Temporal *temp2); +extern double nad_tnumber_tbox(const Temporal *temp, const TBox *box); +extern double nad_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs); +extern double nad_tpoint_stbox(const Temporal *temp, const STBox *box); +extern double nad_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2); +extern TInstant *nai_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs); +extern TInstant *nai_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2); +extern bool shortestline_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs, GSERIALIZED **result); +extern bool shortestline_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2, GSERIALIZED **result); + /***************************************************************************** Spatial functions for temporal point types *****************************************************************************/ -extern bool bearing_point_point(const GSERIALIZED *geo1, const GSERIALIZED *geo2, double *result); +extern bool bearing_point_point(const GSERIALIZED *gs1, const GSERIALIZED *gs2, double *result); extern Temporal *bearing_tpoint_point(const Temporal *temp, const GSERIALIZED *gs, bool invert); extern Temporal *bearing_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2); extern Temporal *tpoint_angular_difference(const Temporal *temp); @@ -1788,7 +1943,9 @@ extern Temporal *tpoint_azimuth(const Temporal *temp); extern GSERIALIZED *tpoint_convex_hull(const Temporal *temp); extern Temporal *tpoint_cumulative_length(const Temporal *temp); extern bool tpoint_direction(const Temporal *temp, double *result); -extern Temporal *tpoint_get_coord(const Temporal *temp, int coord); +extern Temporal *tpoint_get_x(const Temporal *temp); +extern Temporal *tpoint_get_y(const Temporal *temp); +extern Temporal *tpoint_get_z(const Temporal *temp); extern bool tpoint_is_simple(const Temporal *temp); extern double tpoint_length(const Temporal *temp); extern Temporal *tpoint_speed(const Temporal *temp); @@ -1801,17 +1958,22 @@ extern GSERIALIZED *tpoint_trajectory(const Temporal *temp); extern STBox *geo_expand_space(const GSERIALIZED *gs, double d); -extern Temporal *tgeompoint_tgeogpoint(const Temporal *temp, bool oper); +Temporal *geo_to_tpoint(const GSERIALIZED *gs); +extern Temporal *tgeogpoint_to_tgeompoint(const Temporal *temp); +extern Temporal *tgeompoint_to_tgeogpoint(const Temporal *temp); +bool tpoint_AsMVTGeom(const Temporal *temp, const STBox *bounds, int32_t extent, int32_t buffer, bool clip_geom, GSERIALIZED **gsarr, int64 **timesarr, int *count); extern STBox *tpoint_expand_space(const Temporal *temp, double d); -extern Temporal *tpoint_round(const Temporal *temp, int maxdd); extern Temporal **tpoint_make_simple(const Temporal *temp, int *count); +extern Temporal *tpoint_round(const Temporal *temp, int maxdd); +extern Temporal **tpointarr_round(const Temporal **temp, int count, int maxdd); extern Temporal *tpoint_set_srid(const Temporal *temp, int32 srid); +bool tpoint_to_geo_meas(const Temporal *tpoint, const Temporal *measure, bool segmentize, GSERIALIZED **result); -extern int econtains_geo_tpoint(const GSERIALIZED *geo, const Temporal *temp); +extern int econtains_geo_tpoint(const GSERIALIZED *gs, const Temporal *temp); extern int edisjoint_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs); extern int edisjoint_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2); extern int edwithin_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs, double dist); @@ -1819,11 +1981,16 @@ extern int edwithin_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2, extern int eintersects_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs); extern int eintersects_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2); extern int etouches_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs); + + + + + extern Temporal *tcontains_geo_tpoint(const GSERIALIZED *gs, const Temporal *temp, bool restr, bool atvalue); -extern Temporal *tdisjoint_tpoint_geo(const Temporal *temp, const GSERIALIZED *geo, bool restr, bool atvalue); +extern Temporal *tdisjoint_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs, bool restr, bool atvalue); extern Temporal *tdwithin_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs, double dist, bool restr, bool atvalue); extern Temporal *tdwithin_tpoint_tpoint(const Temporal *temp1, const Temporal *temp2, double dist, bool restr, bool atvalue); -extern Temporal *tintersects_tpoint_geo(const Temporal *temp, const GSERIALIZED *geo, bool restr, bool atvalue); +extern Temporal *tintersects_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs, bool restr, bool atvalue); extern Temporal *ttouches_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs, bool restr, bool atvalue); @@ -1853,23 +2020,23 @@ extern GSERIALIZED *tpoint_twcentroid(const Temporal *temp); extern SkipList *ttext_tmax_transfn(SkipList *state, const Temporal *temp); extern SkipList *ttext_tmin_transfn(SkipList *state, const Temporal *temp); +/***************************************************************************** + * Analytics functions for temporal types + *****************************************************************************/ + + + +Temporal *temporal_simplify_min_dist(const Temporal *temp, double dist); +Temporal *temporal_simplify_min_tdelta(const Temporal *temp, const Interval *mint); +Temporal *temporal_simplify_dp(const Temporal *temp, double eps_dist, bool synchronized); +Temporal *temporal_simplify_max_dist(const Temporal *temp, double eps_dist, bool synchronized); -extern double float_bucket(double value, double size, double origin); -extern Span *floatspan_bucket_list(const Span *bounds, double size, double origin, int *newcount); -extern int int_bucket(int value, int size, int origin); -extern Span *intspan_bucket_list(const Span *bounds, int size, int origin, int *newcount); -extern Span *period_bucket_list(const Span *bounds, const Interval *duration, TimestampTz origin, int *newcount); -extern STBox *stbox_tile_list(const STBox *bounds, double xsize, double ysize, double zsize, const Interval *duration, GSERIALIZED *sorigin, TimestampTz torigin, int **cellcount); -extern TBox *tbox_tile_list(const TBox *bounds, double xsize, const Interval *duration, double xorigin, TimestampTz torigin, int *rows, int *columns); -extern Temporal **temporal_time_split(Temporal *temp, Interval *duration, TimestampTz torigin, int *newcount); -extern Temporal **tfloat_value_split(Temporal *temp, double size, double origin, int *newcount); -extern Temporal **tfloat_value_time_split(Temporal *temp, double size, double vorigin, Interval *duration, TimestampTz torigin, int *newcount); -extern TimestampTz timestamptz_bucket(TimestampTz timestamp, const Interval *duration, TimestampTz origin); -extern Temporal **tint_value_split(Temporal *temp, int size, int origin, int *newcount); -extern Temporal **tint_value_time_split(Temporal *temp, int size, int vorigin, Interval *duration, TimestampTz torigin, int *newcount); + +extern Temporal *temporal_tprecision(const Temporal *temp, const Interval *duration, TimestampTz origin); +extern Temporal *temporal_tsample(const Temporal *temp, const Interval *duration, TimestampTz origin); @@ -1885,13 +2052,23 @@ extern double temporal_hausdorff_distance(const Temporal *temp1, const Temporal -Temporal *geo_to_tpoint(const GSERIALIZED *geo); -Temporal *temporal_simplify_min_dist(const Temporal *temp, double dist); -Temporal *temporal_simplify_min_tdelta(const Temporal *temp, const Interval *mint); -Temporal *temporal_simplify_dp(const Temporal *temp, double eps_dist, bool synchronized); -Temporal *temporal_simplify_max_dist(const Temporal *temp, double eps_dist, bool synchronized); -bool tpoint_AsMVTGeom(const Temporal *temp, const STBox *bounds, int32_t extent, int32_t buffer, bool clip_geom, GSERIALIZED **geom, int64 **timesarr, int *count); -bool tpoint_to_geo_meas(const Temporal *tpoint, const Temporal *measure, bool segmentize, GSERIALIZED **result); +extern double float_bucket(double value, double size, double origin); +extern Span *floatspan_bucket_list(const Span *bounds, double size, double origin, int *count); +extern int int_bucket(int value, int size, int origin); +extern Span *intspan_bucket_list(const Span *bounds, int size, int origin, int *count); +extern Span *period_bucket_list(const Span *bounds, const Interval *duration, TimestampTz origin, int *count); +extern STBox *stbox_tile_list(const STBox *bounds, double xsize, double ysize, double zsize, const Interval *duration, GSERIALIZED *sorigin, TimestampTz torigin, int *count); +extern TBox *tintbox_tile_list(const TBox *box, int xsize, const Interval *duration, int xorigin, TimestampTz torigin, int *count); +extern TBox *tfloatbox_tile_list(const TBox *box, double xsize, const Interval *duration, double xorigin, TimestampTz torigin, int *count); +extern Temporal **temporal_time_split(Temporal *temp, Interval *duration, TimestampTz torigin, TimestampTz **time_buckets, int *count); +extern Temporal **tfloat_value_split(Temporal *temp, double size, double origin, double **value_buckets, int *count); +extern Temporal **tfloat_value_time_split(Temporal *temp, double size, Interval *duration, double vorigin, TimestampTz torigin, double **value_buckets, TimestampTz **time_buckets, int *count); +extern TimestampTz timestamptz_bucket(TimestampTz timestamp, const Interval *duration, TimestampTz origin); +extern Temporal **tint_value_split(Temporal *temp, int size, int origin, int **value_buckets, int *count); +extern Temporal **tint_value_time_split(Temporal *temp, int size, Interval *duration, int vorigin, TimestampTz torigin, int **value_buckets, TimestampTz **time_buckets, int *count); +extern Temporal **tpoint_space_split(Temporal *temp, float xsize, float ysize, float zsize, GSERIALIZED *sorigin, bool bitmatrix, GSERIALIZED ***space_buckets, int *count); +extern Temporal **tpoint_space_time_split(Temporal *temp, float xsize, float ysize, float zsize, Interval *duration, GSERIALIZED *sorigin, TimestampTz torigin, bool bitmatrix, GSERIALIZED ***space_buckets, TimestampTz **time_buckets, int *count); + diff --git a/pymeos_cffi/pymeos_cffi/builder/templates/functions.py b/pymeos_cffi/pymeos_cffi/builder/templates/functions.py index f91e622f..1167eb28 100644 --- a/pymeos_cffi/pymeos_cffi/builder/templates/functions.py +++ b/pymeos_cffi/pymeos_cffi/builder/templates/functions.py @@ -1,14 +1,13 @@ +import os from datetime import datetime, timedelta from typing import Any, Tuple, Optional, List, Union import _meos_cffi from .errors import raise_meos_exception -import postgis as pg import shapely.geometry as spg from dateutil.parser import parse -from shapely import wkt, wkb, get_srid +from shapely import wkt, get_srid, set_srid from shapely.geometry.base import BaseGeometry -from spans.types import floatrange, intrange _ffi = _meos_cffi.ffi _lib = _meos_cffi.lib @@ -17,6 +16,13 @@ _error_level: Optional[int] = None _error_message: Optional[str] = None +_debug = os.environ.get('MEOS_DEBUG', '0') == '1' + + +def meos_set_debug(debug: bool) -> None: + global _debug + _debug = debug + def _check_error() -> None: global _error, _error_level, _error_message @@ -36,6 +42,8 @@ def py_error_handler(error_level, error_code, error_msg): _error = error_code _error_level = error_level _error_message = _ffi.string(error_msg).decode('utf-8') + if _debug: + print(f'ERROR Handler called: Level: {_error} | Code: {_error_level} | Message: {_error_message}') def create_pointer(object: 'Any', type: str) -> 'Any *': @@ -63,47 +71,45 @@ def interval_to_timedelta(interval: Any) -> timedelta: return timedelta(days=interval.day, microseconds=interval.time) -def geometry_to_gserialized(geom: Union[pg.Geometry, BaseGeometry], geodetic: Optional[bool] = None) -> 'GSERIALIZED *': - if isinstance(geom, pg.Geometry): - text = geom.to_ewkb() - # if geom.has_srid(): - # text = f'SRID={geom.srid};{text}' - elif isinstance(geom, BaseGeometry): - text = wkb.dumps(geom, hex=True) - if get_srid(geom) > 0: - text = f'SRID={get_srid(geom)};{text}' +def geo_to_gserialized(geom: BaseGeometry, geodetic: bool) -> 'GSERIALIZED *': + if geodetic: + return geography_to_gserialized(geom) else: - raise TypeError('Parameter geom must be either a PostGIS Geometry or a Shapely BaseGeometry') - gs = gserialized_in(text, -1) - if geodetic is not None: - # GFlags is an 8-bit integer, where the 4th bit is the geodetic flag (0x80) - # If geodetic is True, then set the 4th bit to 1, otherwise set it to 0 - gs.gflags = (gs.gflags | 0x08) if geodetic else (gs.gflags & 0xF7) - return gs - - -def gserialized_to_shapely_point(geom: 'const GSERIALIZED *', precision: int = 6) -> spg.Point: - return wkt.loads(gserialized_as_text(geom, precision)) + return geometry_to_gserialized(geom) -def gserialized_to_shapely_geometry(geom: 'const GSERIALIZED *', precision: int = 6) -> BaseGeometry: - return wkt.loads(gserialized_as_text(geom, precision)) - - -def intrange_to_intspan(irange: intrange) -> 'Span *': - return intspan_make(irange.lower, irange.upper, irange.lower_inc, irange.upper_inc) +def geometry_to_gserialized(geom: BaseGeometry) -> 'GSERIALIZED *': + text = wkt.dumps(geom) + if get_srid(geom) > 0: + text = f'SRID={get_srid(geom)};{text}' + gs = pgis_geometry_in(text, -1) + return gs -def intspan_to_intrange(ispan: 'Span *') -> intrange: - return intrange(intspan_lower(ispan), intspan_upper(ispan), ispan.lower_inc, ispan.upper_inc) +def geography_to_gserialized(geom: BaseGeometry) -> 'GSERIALIZED *': + text = wkt.dumps(geom) + if get_srid(geom) > 0: + text = f'SRID={get_srid(geom)};{text}' + gs = pgis_geography_in(text, -1) + return gs -def floatrange_to_floatspan(frange: floatrange) -> 'Span *': - return floatspan_make(frange.lower, frange.upper, frange.lower_inc, frange.upper_inc) +def gserialized_to_shapely_point(geom: 'const GSERIALIZED *', precision: int = 15) -> spg.Point: + text = gserialized_as_text(geom, precision) + geometry = wkt.loads(text) + srid = lwgeom_get_srid(geom) + if srid > 0: + geometry = set_srid(geometry, srid) + return geometry -def floatspan_to_floatrange(fspan: 'Span *') -> floatrange: - return floatrange(floatspan_lower(fspan), floatspan_upper(fspan), fspan.lower_inc, fspan.upper_inc) +def gserialized_to_shapely_geometry(geom: 'const GSERIALIZED *', precision: int = 15) -> BaseGeometry: + text = gserialized_as_text(geom, precision) + geometry = wkt.loads(text) + srid = lwgeom_get_srid(geom) + if srid > 0: + geometry = set_srid(geometry, srid) + return geometry def as_tinstant(temporal: 'Temporal *') -> 'TInstant *': @@ -117,8 +123,6 @@ def as_tsequence(temporal: 'Temporal *') -> 'TSequence *': def as_tsequenceset(temporal: 'Temporal *') -> 'TSequenceSet *': return _ffi.cast('TSequenceSet *', temporal) - # ----------------------------------------------------------------------------- # ----------------------End of manually-defined functions---------------------- # ----------------------------------------------------------------------------- - diff --git a/pymeos_cffi/pymeos_cffi/errors.py b/pymeos_cffi/pymeos_cffi/errors.py index 8d2fd78e..b3896b6b 100644 --- a/pymeos_cffi/pymeos_cffi/errors.py +++ b/pymeos_cffi/pymeos_cffi/errors.py @@ -1,40 +1,17 @@ -from enum import IntEnum - - -class MEOSCode(IntEnum): - MEOS_SUCCESS = 0, # Successful operation - - MEOS_ERR_INTERNAL_ERROR = 1, # Unspecified internal error - MEOS_ERR_INTERNAL_TYPE_ERROR = 2, # Internal type error - MEOS_ERR_VALUE_OUT_OF_RANGE = 3, # Internal out of range error - MEOS_ERR_DIVISION_BY_ZERO = 4, # Internal division by zero error - MEOS_ERR_MEMORY_ALLOC_ERROR = 5, # Internal malloc error - MEOS_ERR_AGGREGATION_ERROR = 6, # Internal aggregation error - MEOS_ERR_DIRECTORY_ERROR = 7, # Internal directory error - MEOS_ERR_FILE_ERROR = 8, # Internal file error - - MEOS_ERR_INVALID_ARG = 10, # Invalid argument - MEOS_ERR_INVALID_ARG_TYPE = 11, # Invalid argument type - MEOS_ERR_INVALID_ARG_VALUE = 12, # Invalid argument value - - MEOS_ERR_MFJSON_INPUT = 20, # MFJSON input error - MEOS_ERR_MFJSON_OUTPUT = 21, # MFJSON output error - MEOS_ERR_TEXT_INPUT = 22, # Text input error - MEOS_ERR_TEXT_OUTPUT = 23, # Text output error - MEOS_ERR_WKB_INPUT = 24, # WKB input error - MEOS_ERR_WKB_OUTPUT = 25, # WKB output error - MEOS_ERR_GEOJSON_INPUT = 26, # GEOJSON input error - MEOS_ERR_GEOJSON_OUTPUT = 27, # GEOJSON output error +from _meos_cffi import lib as _lib class MeosException(Exception): """Base class for all MEOS errors.""" def __init__(self, code: int, message: str): - super().__init__(f'{message}\nThe MEOS error code ({code}) has not been recognized. ' - f'Please, report this to the MEOS developers.') + super().__init__(message) + self.message = message self.code = code + def __str__(self): + return f'{self.__class__.__name__} ({self.code}): {self.message}' + class MeosInternalError(MeosException): """Superclass for internal errors.""" @@ -141,44 +118,29 @@ class MeosGeoJsonOutputError(MeosIoError): pass +_exception_map = { + _lib.MEOS_ERR_INTERNAL_ERROR: MeosInternalError, + _lib.MEOS_ERR_INTERNAL_TYPE_ERROR: MeosInternalTypeError, + _lib.MEOS_ERR_VALUE_OUT_OF_RANGE: MeosValueOutOfRangeError, + _lib.MEOS_ERR_DIVISION_BY_ZERO: MeosDivisionByZeroError, + _lib.MEOS_ERR_MEMORY_ALLOC_ERROR: MeosMemoryAllocError, + _lib.MEOS_ERR_AGGREGATION_ERROR: MeosAggregationError, + _lib.MEOS_ERR_DIRECTORY_ERROR: MeosDirectoryError, + _lib.MEOS_ERR_FILE_ERROR: MeosFileError, + _lib.MEOS_ERR_INVALID_ARG: MeosInvalidArgError, + _lib.MEOS_ERR_INVALID_ARG_TYPE: MeosInvalidArgTypeError, + _lib.MEOS_ERR_INVALID_ARG_VALUE: MeosInvalidArgValueError, + _lib.MEOS_ERR_MFJSON_INPUT: MeosMfJsonInputError, + _lib.MEOS_ERR_MFJSON_OUTPUT: MeosMfJsonOutputError, + _lib.MEOS_ERR_TEXT_INPUT: MeosTextInputError, + _lib.MEOS_ERR_TEXT_OUTPUT: MeosTextOutputError, + _lib.MEOS_ERR_WKB_INPUT: MeosWkbInputError, + _lib.MEOS_ERR_WKB_OUTPUT: MeosWkbOutputError, + _lib.MEOS_ERR_GEOJSON_INPUT: MeosGeoJsonInputError, + _lib.MEOS_ERR_GEOJSON_OUTPUT: MeosGeoJsonOutputError, +} + + def raise_meos_exception(level: int, code: int, message: str): - if code == MEOSCode.MEOS_ERR_INTERNAL_ERROR: - raise MeosInternalError(code, message) - elif code == MEOSCode.MEOS_ERR_INTERNAL_TYPE_ERROR: - raise MeosInternalTypeError(code, message) - elif code == MEOSCode.MEOS_ERR_VALUE_OUT_OF_RANGE: - raise MeosValueOutOfRangeError(code, message) - elif code == MEOSCode.MEOS_ERR_DIVISION_BY_ZERO: - raise MeosDivisionByZeroError(code, message) - elif code == MEOSCode.MEOS_ERR_MEMORY_ALLOC_ERROR: - raise MeosMemoryAllocError(code, message) - elif code == MEOSCode.MEOS_ERR_AGGREGATION_ERROR: - raise MeosAggregationError(code, message) - elif code == MEOSCode.MEOS_ERR_DIRECTORY_ERROR: - raise MeosDirectoryError(code, message) - elif code == MEOSCode.MEOS_ERR_FILE_ERROR: - raise MeosFileError(code, message) - elif code == MEOSCode.MEOS_ERR_INVALID_ARG: - raise MeosInvalidArgError(code, message) - elif code == MEOSCode.MEOS_ERR_INVALID_ARG_TYPE: - raise MeosInvalidArgTypeError(code, message) - elif code == MEOSCode.MEOS_ERR_INVALID_ARG_VALUE: - raise MeosInvalidArgValueError(code, message) - elif code == MEOSCode.MEOS_ERR_MFJSON_INPUT: - raise MeosMfJsonInputError(code, message) - elif code == MEOSCode.MEOS_ERR_MFJSON_OUTPUT: - raise MeosMfJsonOutputError(code, message) - elif code == MEOSCode.MEOS_ERR_TEXT_INPUT: - raise MeosTextInputError(code, message) - elif code == MEOSCode.MEOS_ERR_TEXT_OUTPUT: - raise MeosTextOutputError(code, message) - elif code == MEOSCode.MEOS_ERR_WKB_INPUT: - raise MeosWkbInputError(code, message) - elif code == MEOSCode.MEOS_ERR_WKB_OUTPUT: - raise MeosWkbOutputError(code, message) - elif code == MEOSCode.MEOS_ERR_GEOJSON_INPUT: - raise MeosGeoJsonInputError(code, message) - elif code == MEOSCode.MEOS_ERR_GEOJSON_OUTPUT: - raise MeosGeoJsonOutputError(code, message) - else: - raise MeosException(code, message) + exception_class = _exception_map.get(code, MeosException) + raise exception_class(code, message) diff --git a/pymeos_cffi/pymeos_cffi/functions.py b/pymeos_cffi/pymeos_cffi/functions.py index e02f8f1d..9937d985 100644 --- a/pymeos_cffi/pymeos_cffi/functions.py +++ b/pymeos_cffi/pymeos_cffi/functions.py @@ -1,14 +1,13 @@ +import os from datetime import datetime, timedelta from typing import Any, Tuple, Optional, List, Union import _meos_cffi from .errors import raise_meos_exception -import postgis as pg import shapely.geometry as spg from dateutil.parser import parse -from shapely import wkt, wkb, get_srid +from shapely import wkt, get_srid, set_srid from shapely.geometry.base import BaseGeometry -from spans.types import floatrange, intrange _ffi = _meos_cffi.ffi _lib = _meos_cffi.lib @@ -17,6 +16,13 @@ _error_level: Optional[int] = None _error_message: Optional[str] = None +_debug = os.environ.get('MEOS_DEBUG', '0') == '1' + + +def meos_set_debug(debug: bool) -> None: + global _debug + _debug = debug + def _check_error() -> None: global _error, _error_level, _error_message @@ -36,6 +42,8 @@ def py_error_handler(error_level, error_code, error_msg): _error = error_code _error_level = error_level _error_message = _ffi.string(error_msg).decode('utf-8') + if _debug: + print(f'ERROR Handler called: Level: {_error} | Code: {_error_level} | Message: {_error_message}') def create_pointer(object: 'Any', type: str) -> 'Any *': @@ -63,47 +71,45 @@ def interval_to_timedelta(interval: Any) -> timedelta: return timedelta(days=interval.day, microseconds=interval.time) -def geometry_to_gserialized(geom: Union[pg.Geometry, BaseGeometry], geodetic: Optional[bool] = None) -> 'GSERIALIZED *': - if isinstance(geom, pg.Geometry): - text = geom.to_ewkb() - # if geom.has_srid(): - # text = f'SRID={geom.srid};{text}' - elif isinstance(geom, BaseGeometry): - text = wkb.dumps(geom, hex=True) - if get_srid(geom) > 0: - text = f'SRID={get_srid(geom)};{text}' +def geo_to_gserialized(geom: BaseGeometry, geodetic: bool) -> 'GSERIALIZED *': + if geodetic: + return geography_to_gserialized(geom) else: - raise TypeError('Parameter geom must be either a PostGIS Geometry or a Shapely BaseGeometry') - gs = gserialized_in(text, -1) - if geodetic is not None: - # GFlags is an 8-bit integer, where the 4th bit is the geodetic flag (0x80) - # If geodetic is True, then set the 4th bit to 1, otherwise set it to 0 - gs.gflags = (gs.gflags | 0x08) if geodetic else (gs.gflags & 0xF7) - return gs - - -def gserialized_to_shapely_point(geom: 'const GSERIALIZED *', precision: int = 6) -> spg.Point: - return wkt.loads(gserialized_as_text(geom, precision)) - - -def gserialized_to_shapely_geometry(geom: 'const GSERIALIZED *', precision: int = 6) -> BaseGeometry: - return wkt.loads(gserialized_as_text(geom, precision)) + return geometry_to_gserialized(geom) -def intrange_to_intspan(irange: intrange) -> 'Span *': - return intspan_make(irange.lower, irange.upper, irange.lower_inc, irange.upper_inc) +def geometry_to_gserialized(geom: BaseGeometry) -> 'GSERIALIZED *': + text = wkt.dumps(geom) + if get_srid(geom) > 0: + text = f'SRID={get_srid(geom)};{text}' + gs = pgis_geometry_in(text, -1) + return gs -def intspan_to_intrange(ispan: 'Span *') -> intrange: - return intrange(intspan_lower(ispan), intspan_upper(ispan), ispan.lower_inc, ispan.upper_inc) +def geography_to_gserialized(geom: BaseGeometry) -> 'GSERIALIZED *': + text = wkt.dumps(geom) + if get_srid(geom) > 0: + text = f'SRID={get_srid(geom)};{text}' + gs = pgis_geography_in(text, -1) + return gs -def floatrange_to_floatspan(frange: floatrange) -> 'Span *': - return floatspan_make(frange.lower, frange.upper, frange.lower_inc, frange.upper_inc) +def gserialized_to_shapely_point(geom: 'const GSERIALIZED *', precision: int = 15) -> spg.Point: + text = gserialized_as_text(geom, precision) + geometry = wkt.loads(text) + srid = lwgeom_get_srid(geom) + if srid > 0: + geometry = set_srid(geometry, srid) + return geometry -def floatspan_to_floatrange(fspan: 'Span *') -> floatrange: - return floatrange(floatspan_lower(fspan), floatspan_upper(fspan), fspan.lower_inc, fspan.upper_inc) +def gserialized_to_shapely_geometry(geom: 'const GSERIALIZED *', precision: int = 15) -> BaseGeometry: + text = gserialized_as_text(geom, precision) + geometry = wkt.loads(text) + srid = lwgeom_get_srid(geom) + if srid > 0: + geometry = set_srid(geometry, srid) + return geometry def as_tinstant(temporal: 'Temporal *') -> 'TInstant *': @@ -117,11 +123,9 @@ def as_tsequence(temporal: 'Temporal *') -> 'TSequence *': def as_tsequenceset(temporal: 'Temporal *') -> 'TSequenceSet *': return _ffi.cast('TSequenceSet *', temporal) - # ----------------------------------------------------------------------------- # ----------------------End of manually-defined functions---------------------- # ----------------------------------------------------------------------------- - def lwpoint_make(srid: 'int32_t', hasz: int, hasm: int, p: 'const POINT4D *') -> 'LWPOINT *': srid_converted = _ffi.cast('int32_t', srid) p_converted = _ffi.cast('const POINT4D *', p) @@ -194,21 +198,28 @@ def lwgeom_has_m(geom: 'const LWGEOM *') -> 'int': return result if result != _ffi.NULL else None -def meos_initialize_timezone(name: str) -> None: - name_converted = name.encode('utf-8') - _lib.meos_initialize_timezone(name_converted) +def meos_errno() -> 'int': + result = _lib.meos_errno() + _check_error() + return result if result != _ffi.NULL else None + + +def meos_errno_set(err: int) -> 'int': + result = _lib.meos_errno_set(err) _check_error() + return result if result != _ffi.NULL else None -def meos_initialize_error_handler(err_handler: 'error_handler_fn') -> None: - err_handler_converted = _ffi.cast('error_handler_fn', err_handler) - _lib.meos_initialize_error_handler(err_handler_converted) +def meos_errno_restore(err: int) -> 'int': + result = _lib.meos_errno_restore(err) _check_error() + return result if result != _ffi.NULL else None -def meos_finalize_timezone() -> None: - _lib.meos_finalize_timezone() +def meos_errno_reset() -> 'int': + result = _lib.meos_errno_reset() _check_error() + return result if result != _ffi.NULL else None def meos_initialize(tz_str: "Optional[str]") -> None: @@ -299,6 +310,15 @@ def pg_interval_out(span: 'const Interval *') -> str: return result if result != _ffi.NULL else None +def pg_interval_to_char(it: 'Interval *', fmt: str) -> str: + it_converted = _ffi.cast('Interval *', it) + fmt_converted = cstring2text(fmt) + result = _lib.pg_interval_to_char(it_converted, fmt_converted) + _check_error() + result = text2cstring(result) + return result if result != _ffi.NULL else None + + def pg_interval_pl(span1: 'const Interval *', span2: 'const Interval *') -> 'Interval *': span1_converted = _ffi.cast('const Interval *', span1) span2_converted = _ffi.cast('const Interval *', span2) @@ -363,6 +383,15 @@ def pg_timestamp_pl_interval(timestamp: int, span: 'const Interval *') -> 'Times return result if result != _ffi.NULL else None +def pg_timestamp_to_char(dt: int, fmt: str) -> str: + dt_converted = _ffi.cast('Timestamp', dt) + fmt_converted = cstring2text(fmt) + result = _lib.pg_timestamp_to_char(dt_converted, fmt_converted) + _check_error() + result = text2cstring(result) + return result if result != _ffi.NULL else None + + def pg_timestamptz_in(string: str, typmod: int) -> 'TimestampTz': string_converted = string.encode('utf-8') typmod_converted = _ffi.cast('int32', typmod) @@ -379,21 +408,6 @@ def pg_timestamptz_out(dt: int) -> str: return result if result != _ffi.NULL else None -def text2cstring(textptr: 'text *') -> str: - result = _lib.text2cstring(textptr) - result = _ffi.string(result).decode('utf-8') - return result - - -def pg_timestamp_to_char(dt: int, fmt: str) -> str: - dt_converted = _ffi.cast('Timestamp', dt) - fmt_converted = cstring2text(fmt) - result = _lib.pg_timestamp_to_char(dt_converted, fmt_converted) - _check_error() - result = text2cstring(result) - return result if result != _ffi.NULL else None - - def pg_timestamptz_to_char(dt: int, fmt: str) -> str: dt_converted = _ffi.cast('TimestampTz', dt) fmt_converted = cstring2text(fmt) @@ -403,12 +417,11 @@ def pg_timestamptz_to_char(dt: int, fmt: str) -> str: return result if result != _ffi.NULL else None -def pg_interval_to_char(it: 'Interval *', fmt: str) -> str: - it_converted = _ffi.cast('Interval *', it) +def pg_to_date(date_txt: str, fmt: str) -> 'DateADT': + date_txt_converted = cstring2text(date_txt) fmt_converted = cstring2text(fmt) - result = _lib.pg_interval_to_char(it_converted, fmt_converted) + result = _lib.pg_to_date(date_txt_converted, fmt_converted) _check_error() - result = text2cstring(result) return result if result != _ffi.NULL else None @@ -420,12 +433,10 @@ def pg_to_timestamp(date_txt: str, fmt: str) -> 'TimestampTz': return result if result != _ffi.NULL else None -def pg_to_date(date_txt: str, fmt: str) -> 'DateADT': - date_txt_converted = cstring2text(date_txt) - fmt_converted = cstring2text(fmt) - result = _lib.pg_to_date(date_txt_converted, fmt_converted) - _check_error() - return result if result != _ffi.NULL else None +def text2cstring(textptr: 'text *') -> str: + result = _lib.text2cstring(textptr) + result = _ffi.string(result).decode('utf-8') + return result def geography_from_hexewkb(wkt: str) -> 'GSERIALIZED *': @@ -435,6 +446,13 @@ def geography_from_hexewkb(wkt: str) -> 'GSERIALIZED *': return result if result != _ffi.NULL else None +def geography_from_text(wkt: str, srid: int) -> 'GSERIALIZED *': + wkt_converted = wkt.encode('utf-8') + result = _lib.geography_from_text(wkt_converted, srid) + _check_error() + return result if result != _ffi.NULL else None + + def geometry_from_hexewkb(wkt: str) -> 'GSERIALIZED *': wkt_converted = wkt.encode('utf-8') result = _lib.geometry_from_hexewkb(wkt_converted) @@ -442,6 +460,13 @@ def geometry_from_hexewkb(wkt: str) -> 'GSERIALIZED *': return result if result != _ffi.NULL else None +def geometry_from_text(wkt: str, srid: int) -> 'GSERIALIZED *': + wkt_converted = wkt.encode('utf-8') + result = _lib.geometry_from_text(wkt_converted, srid) + _check_error() + return result if result != _ffi.NULL else None + + def gserialized_as_ewkb(gs: 'const GSERIALIZED *', type: str) -> 'bytea *': gs_converted = _ffi.cast('const GSERIALIZED *', gs) type_converted = type.encode('utf-8') @@ -507,20 +532,6 @@ def gserialized_out(gs: 'const GSERIALIZED *') -> str: return result if result != _ffi.NULL else None -def geometry_from_text(wkt: str, srid: int) -> 'GSERIALIZED *': - wkt_converted = wkt.encode('utf-8') - result = _lib.geometry_from_text(wkt_converted, srid) - _check_error() - return result if result != _ffi.NULL else None - - -def geography_from_text(wkt: str, srid: int) -> 'GSERIALIZED *': - wkt_converted = wkt.encode('utf-8') - result = _lib.geography_from_text(wkt_converted, srid) - _check_error() - return result if result != _ffi.NULL else None - - def pgis_geography_in(input: str, geom_typmod: int) -> 'GSERIALIZED *': input_converted = input.encode('utf-8') geom_typmod_converted = _ffi.cast('int32', geom_typmod) @@ -537,10 +548,10 @@ def pgis_geometry_in(input: str, geom_typmod: int) -> 'GSERIALIZED *': return result if result != _ffi.NULL else None -def pgis_gserialized_same(gs1: 'const GSERIALIZED *', geom2: 'const GSERIALIZED *') -> 'bool': +def pgis_gserialized_same(gs1: 'const GSERIALIZED *', gs2: 'const GSERIALIZED *') -> 'bool': gs1_converted = _ffi.cast('const GSERIALIZED *', gs1) - geom2_converted = _ffi.cast('const GSERIALIZED *', geom2) - result = _lib.pgis_gserialized_same(gs1_converted, geom2_converted) + gs2_converted = _ffi.cast('const GSERIALIZED *', gs2) + result = _lib.pgis_gserialized_same(gs1_converted, gs2_converted) _check_error() return result if result != _ffi.NULL else None @@ -642,14 +653,6 @@ def geogset_in(string: str) -> 'Set *': return result if result != _ffi.NULL else None -def geoset_out(set: 'const Set *', maxdd: int) -> str: - set_converted = _ffi.cast('const Set *', set) - result = _lib.geoset_out(set_converted, maxdd) - _check_error() - result = _ffi.string(result).decode('utf-8') - return result if result != _ffi.NULL else None - - def geomset_in(string: str) -> 'Set *': string_converted = string.encode('utf-8') result = _lib.geomset_in(string_converted) @@ -673,6 +676,14 @@ def geoset_as_text(set: 'const Set *', maxdd: int) -> str: return result if result != _ffi.NULL else None +def geoset_out(set: 'const Set *', maxdd: int) -> str: + set_converted = _ffi.cast('const Set *', set) + result = _lib.geoset_out(set_converted, maxdd) + _check_error() + result = _ffi.string(result).decode('utf-8') + return result if result != _ffi.NULL else None + + def intset_in(string: str) -> 'Set *': string_converted = string.encode('utf-8') result = _lib.intset_in(string_converted) @@ -781,12 +792,14 @@ def set_from_wkb(wkb: bytes) -> 'Set *': return result if result != _ffi.NULL else None -def set_out(s: 'const Set *', maxdd: int) -> str: - s_converted = _ffi.cast('const Set *', s) - result = _lib.set_out(s_converted, maxdd) +def span_as_hexwkb(s: 'const Span *', variant: int) -> "Tuple[str, 'size_t *']": + s_converted = _ffi.cast('const Span *', s) + variant_converted = _ffi.cast('uint8_t', variant) + size_out = _ffi.new('size_t *') + result = _lib.span_as_hexwkb(s_converted, variant_converted, size_out) _check_error() result = _ffi.string(result).decode('utf-8') - return result if result != _ffi.NULL else None + return result if result != _ffi.NULL else None, size_out[0] def span_as_wkb(s: 'const Span *', variant: int) -> bytes: @@ -799,16 +812,6 @@ def span_as_wkb(s: 'const Span *', variant: int) -> bytes: return result_converted -def span_as_hexwkb(s: 'const Span *', variant: int) -> "Tuple[str, 'size_t *']": - s_converted = _ffi.cast('const Span *', s) - variant_converted = _ffi.cast('uint8_t', variant) - size_out = _ffi.new('size_t *') - result = _lib.span_as_hexwkb(s_converted, variant_converted, size_out) - _check_error() - result = _ffi.string(result).decode('utf-8') - return result if result != _ffi.NULL else None, size_out[0] - - def span_from_hexwkb(hexwkb: str) -> 'Span *': hexwkb_converted = hexwkb.encode('utf-8') result = _lib.span_from_hexwkb(hexwkb_converted) @@ -822,12 +825,14 @@ def span_from_wkb(wkb: bytes) -> 'Span *': return result if result != _ffi.NULL else None -def span_out(s: 'const Span *', maxdd: int) -> str: - s_converted = _ffi.cast('const Span *', s) - result = _lib.span_out(s_converted, maxdd) +def spanset_as_hexwkb(ss: 'const SpanSet *', variant: int) -> "Tuple[str, 'size_t *']": + ss_converted = _ffi.cast('const SpanSet *', ss) + variant_converted = _ffi.cast('uint8_t', variant) + size_out = _ffi.new('size_t *') + result = _lib.spanset_as_hexwkb(ss_converted, variant_converted, size_out) _check_error() result = _ffi.string(result).decode('utf-8') - return result if result != _ffi.NULL else None + return result if result != _ffi.NULL else None, size_out[0] def spanset_as_wkb(ss: 'const SpanSet *', variant: int) -> bytes: @@ -840,16 +845,6 @@ def spanset_as_wkb(ss: 'const SpanSet *', variant: int) -> bytes: return result_converted -def spanset_as_hexwkb(ss: 'const SpanSet *', variant: int) -> "Tuple[str, 'size_t *']": - ss_converted = _ffi.cast('const SpanSet *', ss) - variant_converted = _ffi.cast('uint8_t', variant) - size_out = _ffi.new('size_t *') - result = _lib.spanset_as_hexwkb(ss_converted, variant_converted, size_out) - _check_error() - result = _ffi.string(result).decode('utf-8') - return result if result != _ffi.NULL else None, size_out[0] - - def spanset_from_hexwkb(hexwkb: str) -> 'SpanSet *': hexwkb_converted = hexwkb.encode('utf-8') result = _lib.spanset_from_hexwkb(hexwkb_converted) @@ -863,14 +858,6 @@ def spanset_from_wkb(wkb: bytes) -> 'SpanSet *': return result if result != _ffi.NULL else None -def spanset_out(ss: 'const SpanSet *', maxdd: int) -> str: - ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.spanset_out(ss_converted, maxdd) - _check_error() - result = _ffi.string(result).decode('utf-8') - return result if result != _ffi.NULL else None - - def textset_in(string: str) -> 'Set *': string_converted = string.encode('utf-8') result = _lib.textset_in(string_converted) @@ -901,9 +888,9 @@ def timestampset_out(set: 'const Set *') -> str: return result if result != _ffi.NULL else None -def bigintset_make(values: 'const int64 *', count: int) -> 'Set *': - values_converted = _ffi.cast('const int64 *', values) - result = _lib.bigintset_make(values_converted, count) +def bigintset_make(values: 'List[const int64]') -> 'Set *': + values_converted = _ffi.new('const int64 []', values) + result = _lib.bigintset_make(values_converted, len(values)) _check_error() return result if result != _ffi.NULL else None @@ -916,9 +903,9 @@ def bigintspan_make(lower: int, upper: int, lower_inc: bool, upper_inc: bool) -> return result if result != _ffi.NULL else None -def floatset_make(values: 'const double *', count: int) -> 'Set *': - values_converted = _ffi.cast('const double *', values) - result = _lib.floatset_make(values_converted, count) +def floatset_make(values: 'List[const double]') -> 'Set *': + values_converted = _ffi.new('const double []', values) + result = _lib.floatset_make(values_converted, len(values)) _check_error() return result if result != _ffi.NULL else None @@ -929,16 +916,16 @@ def floatspan_make(lower: float, upper: float, lower_inc: bool, upper_inc: bool) return result if result != _ffi.NULL else None -def geoset_make(values: 'const GSERIALIZED **', count: int) -> 'Set *': +def geoset_make(values: 'const GSERIALIZED **') -> 'Set *': values_converted = [_ffi.cast('const GSERIALIZED *', x) for x in values] - result = _lib.geoset_make(values_converted, count) + result = _lib.geoset_make(values_converted, len(values)) _check_error() return result if result != _ffi.NULL else None -def intset_make(values: 'const int *', count: int) -> 'Set *': - values_converted = _ffi.cast('const int *', values) - result = _lib.intset_make(values_converted, count) +def intset_make(values: 'List[const int]') -> 'Set *': + values_converted = _ffi.new('const int []', values) + result = _lib.intset_make(values_converted, len(values)) _check_error() return result if result != _ffi.NULL else None @@ -985,23 +972,16 @@ def spanset_make(spans: 'List[Span *]', normalize: bool) -> 'SpanSet *': return result if result != _ffi.NULL else None -def spanset_make_exp(spans: 'Span *', count: int, maxcount: int, normalize: bool, ordered: bool) -> 'SpanSet *': - spans_converted = _ffi.cast('Span *', spans) - result = _lib.spanset_make_exp(spans_converted, count, maxcount, normalize, ordered) - _check_error() - return result if result != _ffi.NULL else None - - -def textset_make(values: 'const text **', count: int) -> 'Set *': - values_converted = [_ffi.cast('const text *', x) for x in values] - result = _lib.textset_make(values_converted, count) +def textset_make(values: List[str]) -> 'Set *': + values_converted = [cstring2text(x) for x in values] + result = _lib.textset_make(values_converted, len(values)) _check_error() return result if result != _ffi.NULL else None -def timestampset_make(values: List[int], count: int) -> 'Set *': +def timestampset_make(values: List[int]) -> 'Set *': values_converted = [_ffi.cast('const TimestampTz', x) for x in values] - result = _lib.timestampset_make(values_converted, count) + result = _lib.timestampset_make(values_converted, len(values)) _check_error() return result if result != _ffi.NULL else None @@ -1421,13 +1401,6 @@ def set_hash_extended(s: 'const Set *', seed: int) -> 'uint64': return result if result != _ffi.NULL else None -def set_mem_size(s: 'const Set *') -> 'int': - s_converted = _ffi.cast('const Set *', s) - result = _lib.set_mem_size(s_converted) - _check_error() - return result if result != _ffi.NULL else None - - def set_num_values(s: 'const Set *') -> 'int': s_converted = _ffi.cast('const Set *', s) result = _lib.set_num_values(s_converted) @@ -1507,13 +1480,6 @@ def spanset_lower_inc(ss: 'const SpanSet *') -> 'bool': return result if result != _ffi.NULL else None -def spanset_mem_size(ss: 'const SpanSet *') -> 'int': - ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.spanset_mem_size(ss_converted) - _check_error() - return result if result != _ffi.NULL else None - - def spanset_num_spans(ss: 'const SpanSet *') -> 'int': ss_converted = _ffi.cast('const SpanSet *', ss) result = _lib.spanset_num_spans(ss_converted) @@ -1556,9 +1522,9 @@ def spanset_upper_inc(ss: 'const SpanSet *') -> 'bool': return result if result != _ffi.NULL else None -def spanset_width(ss: 'const SpanSet *') -> 'double': +def spanset_width(ss: 'const SpanSet *', boundspan: bool) -> 'double': ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.spanset_width(ss_converted) + result = _lib.spanset_width(ss_converted, boundspan) _check_error() return result if result != _ffi.NULL else None @@ -1634,150 +1600,873 @@ def timestampset_values(ts: 'const Set *') -> 'TimestampTz *': return result if result != _ffi.NULL else None -def floatset_round(s: 'const Set *', maxdd: int) -> 'Set *': +def bigintset_shift_scale(s: 'const Set *', shift: int, width: int, hasshift: bool, haswidth: bool) -> 'Set *': s_converted = _ffi.cast('const Set *', s) - result = _lib.floatset_round(s_converted, maxdd) + shift_converted = _ffi.cast('int64', shift) + width_converted = _ffi.cast('int64', width) + result = _lib.bigintset_shift_scale(s_converted, shift_converted, width_converted, hasshift, haswidth) _check_error() return result if result != _ffi.NULL else None -def floatspan_round(s: 'const Span *', maxdd: int) -> 'Span *': +def bigintspan_shift_scale(s: 'const Span *', shift: int, width: int, hasshift: bool, haswidth: bool) -> 'Span *': s_converted = _ffi.cast('const Span *', s) - result = _lib.floatspan_round(s_converted, maxdd) + shift_converted = _ffi.cast('int64', shift) + width_converted = _ffi.cast('int64', width) + result = _lib.bigintspan_shift_scale(s_converted, shift_converted, width_converted, hasshift, haswidth) _check_error() return result if result != _ffi.NULL else None -def floatspanset_round(ss: 'const SpanSet *', maxdd: int) -> 'SpanSet *': +def bigintspanset_shift_scale(ss: 'const SpanSet *', shift: int, width: int, hasshift: bool, haswidth: bool) -> 'SpanSet *': ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.floatspanset_round(ss_converted, maxdd) + shift_converted = _ffi.cast('int64', shift) + width_converted = _ffi.cast('int64', width) + result = _lib.bigintspanset_shift_scale(ss_converted, shift_converted, width_converted, hasshift, haswidth) _check_error() return result if result != _ffi.NULL else None -def geoset_round(s: 'const Set *', maxdd: int) -> 'Set *': +def floatset_round(s: 'const Set *', maxdd: int) -> 'Set *': s_converted = _ffi.cast('const Set *', s) - result = _lib.geoset_round(s_converted, maxdd) + result = _lib.floatset_round(s_converted, maxdd) _check_error() return result if result != _ffi.NULL else None -def period_tprecision(s: 'const Span *', duration: 'const Interval *', torigin: int) -> 'Span *': +def floatset_shift_scale(s: 'const Set *', shift: float, width: float, hasshift: bool, haswidth: bool) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.floatset_shift_scale(s_converted, shift, width, hasshift, haswidth) + _check_error() + return result if result != _ffi.NULL else None + + +def floatspan_intspan(s: 'const Span *') -> 'Span *': s_converted = _ffi.cast('const Span *', s) - duration_converted = _ffi.cast('const Interval *', duration) - torigin_converted = _ffi.cast('TimestampTz', torigin) - result = _lib.period_tprecision(s_converted, duration_converted, torigin_converted) + result = _lib.floatspan_intspan(s_converted) _check_error() return result if result != _ffi.NULL else None -def periodset_tprecision(ss: 'const SpanSet *', duration: 'const Interval *', torigin: int) -> 'SpanSet *': - ss_converted = _ffi.cast('const SpanSet *', ss) - duration_converted = _ffi.cast('const Interval *', duration) - torigin_converted = _ffi.cast('TimestampTz', torigin) - result = _lib.periodset_tprecision(ss_converted, duration_converted, torigin_converted) +def floatspan_round(s: 'const Span *', maxdd: int) -> 'Span *': + s_converted = _ffi.cast('const Span *', s) + result = _lib.floatspan_round(s_converted, maxdd) _check_error() return result if result != _ffi.NULL else None -def period_shift_tscale(p: 'const Span *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'Span *': - p_converted = _ffi.cast('const Span *', p) - shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL - duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL - result = _lib.period_shift_tscale(p_converted, shift_converted, duration_converted) +def floatspan_shift_scale(s: 'const Span *', shift: float, width: float, hasshift: bool, haswidth: bool) -> 'Span *': + s_converted = _ffi.cast('const Span *', s) + result = _lib.floatspan_shift_scale(s_converted, shift, width, hasshift, haswidth) _check_error() return result if result != _ffi.NULL else None -def periodset_shift_tscale(ps: 'const SpanSet *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'SpanSet *': - ps_converted = _ffi.cast('const SpanSet *', ps) - shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL - duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL - result = _lib.periodset_shift_tscale(ps_converted, shift_converted, duration_converted) +def floatspanset_intspanset(ss: 'const SpanSet *') -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.floatspanset_intspanset(ss_converted) _check_error() return result if result != _ffi.NULL else None -def timestamp_tprecision(t: int, duration: 'const Interval *', torigin: int) -> 'TimestampTz': - t_converted = _ffi.cast('TimestampTz', t) - duration_converted = _ffi.cast('const Interval *', duration) - torigin_converted = _ffi.cast('TimestampTz', torigin) - result = _lib.timestamp_tprecision(t_converted, duration_converted, torigin_converted) +def floatspanset_round(ss: 'const SpanSet *', maxdd: int) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.floatspanset_round(ss_converted, maxdd) _check_error() return result if result != _ffi.NULL else None -def timestampset_shift_tscale(ts: 'const Set *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'Set *': - ts_converted = _ffi.cast('const Set *', ts) - shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL - duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL - result = _lib.timestampset_shift_tscale(ts_converted, shift_converted, duration_converted) +def floatspanset_shift_scale(ss: 'const SpanSet *', shift: float, width: float, hasshift: bool, haswidth: bool) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.floatspanset_shift_scale(ss_converted, shift, width, hasshift, haswidth) _check_error() return result if result != _ffi.NULL else None -def adjacent_bigintspan_bigint(s: 'const Span *', i: int) -> 'bool': - s_converted = _ffi.cast('const Span *', s) - i_converted = _ffi.cast('int64', i) - result = _lib.adjacent_bigintspan_bigint(s_converted, i_converted) +def geoset_round(s: 'const Set *', maxdd: int) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.geoset_round(s_converted, maxdd) _check_error() return result if result != _ffi.NULL else None -def adjacent_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'bool': - ss_converted = _ffi.cast('const SpanSet *', ss) - i_converted = _ffi.cast('int64', i) - result = _lib.adjacent_bigintspanset_bigint(ss_converted, i_converted) +def intset_shift_scale(s: 'const Set *', shift: int, width: int, hasshift: bool, haswidth: bool) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.intset_shift_scale(s_converted, shift, width, hasshift, haswidth) _check_error() return result if result != _ffi.NULL else None -def adjacent_floatspan_float(s: 'const Span *', d: float) -> 'bool': +def intspan_floatspan(s: 'const Span *') -> 'Span *': s_converted = _ffi.cast('const Span *', s) - result = _lib.adjacent_floatspan_float(s_converted, d) + result = _lib.intspan_floatspan(s_converted) _check_error() return result if result != _ffi.NULL else None -def adjacent_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'bool': - ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.adjacent_floatspanset_float(ss_converted, d) +def intspan_shift_scale(s: 'const Span *', shift: int, width: int, hasshift: bool, haswidth: bool) -> 'Span *': + s_converted = _ffi.cast('const Span *', s) + result = _lib.intspan_shift_scale(s_converted, shift, width, hasshift, haswidth) _check_error() return result if result != _ffi.NULL else None -def adjacent_intspan_int(s: 'const Span *', i: int) -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.adjacent_intspan_int(s_converted, i) +def intspanset_floatspanset(ss: 'const SpanSet *') -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.intspanset_floatspanset(ss_converted) _check_error() return result if result != _ffi.NULL else None -def adjacent_intspanset_int(ss: 'const SpanSet *', i: int) -> 'bool': +def intspanset_shift_scale(ss: 'const SpanSet *', shift: int, width: int, hasshift: bool, haswidth: bool) -> 'SpanSet *': ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.adjacent_intspanset_int(ss_converted, i) + result = _lib.intspanset_shift_scale(ss_converted, shift, width, hasshift, haswidth) _check_error() return result if result != _ffi.NULL else None -def adjacent_period_timestamp(p: 'const Span *', t: int) -> 'bool': +def period_shift_scale(p: 'const Span *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'Span *': p_converted = _ffi.cast('const Span *', p) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.adjacent_period_timestamp(p_converted, t_converted) + shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL + duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL + result = _lib.period_shift_scale(p_converted, shift_converted, duration_converted) _check_error() return result if result != _ffi.NULL else None -def adjacent_periodset_timestamp(ps: 'const SpanSet *', t: int) -> 'bool': - ps_converted = _ffi.cast('const SpanSet *', ps) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.adjacent_periodset_timestamp(ps_converted, t_converted) +def period_tprecision(s: 'const Span *', duration: 'const Interval *', torigin: int) -> 'Span *': + s_converted = _ffi.cast('const Span *', s) + duration_converted = _ffi.cast('const Interval *', duration) + torigin_converted = _ffi.cast('TimestampTz', torigin) + result = _lib.period_tprecision(s_converted, duration_converted, torigin_converted) _check_error() return result if result != _ffi.NULL else None -def adjacent_span_span(s1: 'const Span *', s2: 'const Span *') -> 'bool': - s1_converted = _ffi.cast('const Span *', s1) +def periodset_shift_scale(ss: 'const SpanSet *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL + duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL + result = _lib.periodset_shift_scale(ss_converted, shift_converted, duration_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def periodset_tprecision(ss: 'const SpanSet *', duration: 'const Interval *', torigin: int) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + duration_converted = _ffi.cast('const Interval *', duration) + torigin_converted = _ffi.cast('TimestampTz', torigin) + result = _lib.periodset_tprecision(ss_converted, duration_converted, torigin_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def textset_lower(s: 'const Set *') -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.textset_lower(s_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def textset_upper(s: 'const Set *') -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.textset_upper(s_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def timestamp_tprecision(t: int, duration: 'const Interval *', torigin: int) -> 'TimestampTz': + t_converted = _ffi.cast('TimestampTz', t) + duration_converted = _ffi.cast('const Interval *', duration) + torigin_converted = _ffi.cast('TimestampTz', torigin) + result = _lib.timestamp_tprecision(t_converted, duration_converted, torigin_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def timestampset_shift_scale(ts: 'const Set *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'Set *': + ts_converted = _ffi.cast('const Set *', ts) + shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL + duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL + result = _lib.timestampset_shift_scale(ts_converted, shift_converted, duration_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def intersection_bigintset_bigint(s: 'const Set *', i: int) -> 'int64': + s_converted = _ffi.cast('const Set *', s) + i_converted = _ffi.cast('int64', i) + out_result = _ffi.new('int64 *') + result = _lib.intersection_bigintset_bigint(s_converted, i_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_bigintspan_bigint(s: 'const Span *', i: int) -> 'int64': + s_converted = _ffi.cast('const Span *', s) + i_converted = _ffi.cast('int64', i) + out_result = _ffi.new('int64 *') + result = _lib.intersection_bigintspan_bigint(s_converted, i_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'int64': + ss_converted = _ffi.cast('const SpanSet *', ss) + i_converted = _ffi.cast('int64', i) + out_result = _ffi.new('int64 *') + result = _lib.intersection_bigintspanset_bigint(ss_converted, i_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_floatset_float(s: 'const Set *', d: float) -> 'double': + s_converted = _ffi.cast('const Set *', s) + out_result = _ffi.new('double *') + result = _lib.intersection_floatset_float(s_converted, d, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_floatspan_float(s: 'const Span *', d: float) -> 'double': + s_converted = _ffi.cast('const Span *', s) + out_result = _ffi.new('double *') + result = _lib.intersection_floatspan_float(s_converted, d, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'double': + ss_converted = _ffi.cast('const SpanSet *', ss) + out_result = _ffi.new('double *') + result = _lib.intersection_floatspanset_float(ss_converted, d, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_geoset_geo(s: 'const Set *', gs: 'const GSERIALIZED *') -> 'GSERIALIZED **': + s_converted = _ffi.cast('const Set *', s) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + out_result = _ffi.new('GSERIALIZED **') + result = _lib.intersection_geoset_geo(s_converted, gs_converted, out_result) + _check_error() + if result: + return out_result if out_result != _ffi.NULL else None + return None + + +def intersection_intset_int(s: 'const Set *', i: int) -> 'int': + s_converted = _ffi.cast('const Set *', s) + out_result = _ffi.new('int *') + result = _lib.intersection_intset_int(s_converted, i, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_intspan_int(s: 'const Span *', i: int) -> 'int': + s_converted = _ffi.cast('const Span *', s) + out_result = _ffi.new('int *') + result = _lib.intersection_intspan_int(s_converted, i, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_intspanset_int(ss: 'const SpanSet *', i: int) -> 'int': + ss_converted = _ffi.cast('const SpanSet *', ss) + out_result = _ffi.new('int *') + result = _lib.intersection_intspanset_int(ss_converted, i, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_period_timestamp(s: 'const Span *', t: int) -> int: + s_converted = _ffi.cast('const Span *', s) + t_converted = _ffi.cast('TimestampTz', t) + out_result = _ffi.new('TimestampTz *') + result = _lib.intersection_period_timestamp(s_converted, t_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_periodset_timestamp(ss: 'const SpanSet *', t: int) -> int: + ss_converted = _ffi.cast('const SpanSet *', ss) + t_converted = _ffi.cast('TimestampTz', t) + out_result = _ffi.new('TimestampTz *') + result = _lib.intersection_periodset_timestamp(ss_converted, t_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def intersection_set_set(s1: 'const Set *', s2: 'const Set *') -> 'Set *': + s1_converted = _ffi.cast('const Set *', s1) + s2_converted = _ffi.cast('const Set *', s2) + result = _lib.intersection_set_set(s1_converted, s2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def intersection_span_span(s1: 'const Span *', s2: 'const Span *') -> 'Span *': + s1_converted = _ffi.cast('const Span *', s1) + s2_converted = _ffi.cast('const Span *', s2) + result = _lib.intersection_span_span(s1_converted, s2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def intersection_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + s_converted = _ffi.cast('const Span *', s) + result = _lib.intersection_spanset_span(ss_converted, s_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def intersection_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'SpanSet *': + ss1_converted = _ffi.cast('const SpanSet *', ss1) + ss2_converted = _ffi.cast('const SpanSet *', ss2) + result = _lib.intersection_spanset_spanset(ss1_converted, ss2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def intersection_textset_text(s: 'const Set *', txt: str) -> 'text **': + s_converted = _ffi.cast('const Set *', s) + txt_converted = cstring2text(txt) + out_result = _ffi.new('text **') + result = _lib.intersection_textset_text(s_converted, txt_converted, out_result) + _check_error() + if result: + return out_result if out_result != _ffi.NULL else None + return None + + +def intersection_timestampset_timestamp(s: 'const Set *', t: int) -> int: + s_converted = _ffi.cast('const Set *', s) + t_converted = _ffi.cast('TimestampTz', t) + out_result = _ffi.new('TimestampTz *') + result = _lib.intersection_timestampset_timestamp(s_converted, t_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_bigint_bigintset(i: int, s: 'const Set *') -> 'int64': + i_converted = _ffi.cast('int64', i) + s_converted = _ffi.cast('const Set *', s) + out_result = _ffi.new('int64 *') + result = _lib.minus_bigint_bigintset(i_converted, s_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_bigint_bigintspan(i: int, s: 'const Span *') -> 'int64': + i_converted = _ffi.cast('int64', i) + s_converted = _ffi.cast('const Span *', s) + out_result = _ffi.new('int64 *') + result = _lib.minus_bigint_bigintspan(i_converted, s_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_bigint_bigintspanset(i: int, ss: 'const SpanSet *') -> 'int64': + i_converted = _ffi.cast('int64', i) + ss_converted = _ffi.cast('const SpanSet *', ss) + out_result = _ffi.new('int64 *') + result = _lib.minus_bigint_bigintspanset(i_converted, ss_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_bigintset_bigint(s: 'const Set *', i: int) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + i_converted = _ffi.cast('int64', i) + result = _lib.minus_bigintset_bigint(s_converted, i_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_bigintspan_bigint(s: 'const Span *', i: int) -> 'SpanSet *': + s_converted = _ffi.cast('const Span *', s) + i_converted = _ffi.cast('int64', i) + result = _lib.minus_bigintspan_bigint(s_converted, i_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + i_converted = _ffi.cast('int64', i) + result = _lib.minus_bigintspanset_bigint(ss_converted, i_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_float_floatset(d: float, s: 'const Set *') -> 'double': + s_converted = _ffi.cast('const Set *', s) + out_result = _ffi.new('double *') + result = _lib.minus_float_floatset(d, s_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_float_floatspan(d: float, s: 'const Span *') -> 'double': + s_converted = _ffi.cast('const Span *', s) + out_result = _ffi.new('double *') + result = _lib.minus_float_floatspan(d, s_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_float_floatspanset(d: float, ss: 'const SpanSet *') -> 'double': + ss_converted = _ffi.cast('const SpanSet *', ss) + out_result = _ffi.new('double *') + result = _lib.minus_float_floatspanset(d, ss_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_floatset_float(s: 'const Set *', d: float) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.minus_floatset_float(s_converted, d) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_floatspan_float(s: 'const Span *', d: float) -> 'SpanSet *': + s_converted = _ffi.cast('const Span *', s) + result = _lib.minus_floatspan_float(s_converted, d) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.minus_floatspanset_float(ss_converted, d) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_geo_geoset(gs: 'const GSERIALIZED *', s: 'const Set *') -> 'GSERIALIZED **': + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + s_converted = _ffi.cast('const Set *', s) + out_result = _ffi.new('GSERIALIZED **') + result = _lib.minus_geo_geoset(gs_converted, s_converted, out_result) + _check_error() + if result: + return out_result if out_result != _ffi.NULL else None + return None + + +def minus_geoset_geo(s: 'const Set *', gs: 'const GSERIALIZED *') -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.minus_geoset_geo(s_converted, gs_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_int_intset(i: int, s: 'const Set *') -> 'int': + s_converted = _ffi.cast('const Set *', s) + out_result = _ffi.new('int *') + result = _lib.minus_int_intset(i, s_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_int_intspan(i: int, s: 'const Span *') -> 'int': + s_converted = _ffi.cast('const Span *', s) + out_result = _ffi.new('int *') + result = _lib.minus_int_intspan(i, s_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_int_intspanset(i: int, ss: 'const SpanSet *') -> 'int': + ss_converted = _ffi.cast('const SpanSet *', ss) + out_result = _ffi.new('int *') + result = _lib.minus_int_intspanset(i, ss_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_intset_int(s: 'const Set *', i: int) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.minus_intset_int(s_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_intspan_int(s: 'const Span *', i: int) -> 'SpanSet *': + s_converted = _ffi.cast('const Span *', s) + result = _lib.minus_intspan_int(s_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_intspanset_int(ss: 'const SpanSet *', i: int) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.minus_intspanset_int(ss_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_period_timestamp(s: 'const Span *', t: int) -> 'SpanSet *': + s_converted = _ffi.cast('const Span *', s) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.minus_period_timestamp(s_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_periodset_timestamp(ss: 'const SpanSet *', t: int) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.minus_periodset_timestamp(ss_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_set_set(s1: 'const Set *', s2: 'const Set *') -> 'Set *': + s1_converted = _ffi.cast('const Set *', s1) + s2_converted = _ffi.cast('const Set *', s2) + result = _lib.minus_set_set(s1_converted, s2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_span_span(s1: 'const Span *', s2: 'const Span *') -> 'SpanSet *': + s1_converted = _ffi.cast('const Span *', s1) + s2_converted = _ffi.cast('const Span *', s2) + result = _lib.minus_span_span(s1_converted, s2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_span_spanset(s: 'const Span *', ss: 'const SpanSet *') -> 'SpanSet *': + s_converted = _ffi.cast('const Span *', s) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.minus_span_spanset(s_converted, ss_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + s_converted = _ffi.cast('const Span *', s) + result = _lib.minus_spanset_span(ss_converted, s_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'SpanSet *': + ss1_converted = _ffi.cast('const SpanSet *', ss1) + ss2_converted = _ffi.cast('const SpanSet *', ss2) + result = _lib.minus_spanset_spanset(ss1_converted, ss2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_text_textset(txt: str, s: 'const Set *') -> 'text **': + txt_converted = cstring2text(txt) + s_converted = _ffi.cast('const Set *', s) + out_result = _ffi.new('text **') + result = _lib.minus_text_textset(txt_converted, s_converted, out_result) + _check_error() + if result: + return out_result if out_result != _ffi.NULL else None + return None + + +def minus_textset_text(s: 'const Set *', txt: str) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + txt_converted = cstring2text(txt) + result = _lib.minus_textset_text(s_converted, txt_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def minus_timestamp_period(t: int, s: 'const Span *') -> int: + t_converted = _ffi.cast('TimestampTz', t) + s_converted = _ffi.cast('const Span *', s) + out_result = _ffi.new('TimestampTz *') + result = _lib.minus_timestamp_period(t_converted, s_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_timestamp_periodset(t: int, ss: 'const SpanSet *') -> int: + t_converted = _ffi.cast('TimestampTz', t) + ss_converted = _ffi.cast('const SpanSet *', ss) + out_result = _ffi.new('TimestampTz *') + result = _lib.minus_timestamp_periodset(t_converted, ss_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_timestamp_timestampset(t: int, s: 'const Set *') -> int: + t_converted = _ffi.cast('TimestampTz', t) + s_converted = _ffi.cast('const Set *', s) + out_result = _ffi.new('TimestampTz *') + result = _lib.minus_timestamp_timestampset(t_converted, s_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def minus_timestampset_timestamp(s: 'const Set *', t: int) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.minus_timestampset_timestamp(s_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_bigintset_bigint(s: 'const Set *', i: int) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + i_converted = _ffi.cast('int64', i) + result = _lib.union_bigintset_bigint(s_converted, i_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_bigintspan_bigint(s: 'const Span *', i: int) -> 'SpanSet *': + s_converted = _ffi.cast('const Span *', s) + i_converted = _ffi.cast('int64', i) + result = _lib.union_bigintspan_bigint(s_converted, i_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + i_converted = _ffi.cast('int64', i) + result = _lib.union_bigintspanset_bigint(ss_converted, i_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_floatset_float(s: 'const Set *', d: float) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.union_floatset_float(s_converted, d) + _check_error() + return result if result != _ffi.NULL else None + + +def union_floatspan_float(s: 'const Span *', d: float) -> 'SpanSet *': + s_converted = _ffi.cast('const Span *', s) + result = _lib.union_floatspan_float(s_converted, d) + _check_error() + return result if result != _ffi.NULL else None + + +def union_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.union_floatspanset_float(ss_converted, d) + _check_error() + return result if result != _ffi.NULL else None + + +def union_geoset_geo(s: 'const Set *', gs: 'const GSERIALIZED *') -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.union_geoset_geo(s_converted, gs_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_intset_int(s: 'const Set *', i: int) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.union_intset_int(s_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def union_intspan_int(s: 'const Span *', i: int) -> 'SpanSet *': + s_converted = _ffi.cast('const Span *', s) + result = _lib.union_intspan_int(s_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def union_intspanset_int(ss: 'const SpanSet *', i: int) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.union_intspanset_int(ss_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def union_period_timestamp(s: 'const Span *', t: int) -> 'SpanSet *': + s_converted = _ffi.cast('const Span *', s) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.union_period_timestamp(s_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_periodset_timestamp(ss: 'SpanSet *', t: int) -> 'SpanSet *': + ss_converted = _ffi.cast('SpanSet *', ss) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.union_periodset_timestamp(ss_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_set_set(s1: 'const Set *', s2: 'const Set *') -> 'Set *': + s1_converted = _ffi.cast('const Set *', s1) + s2_converted = _ffi.cast('const Set *', s2) + result = _lib.union_set_set(s1_converted, s2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_span_span(s1: 'const Span *', s2: 'const Span *') -> 'SpanSet *': + s1_converted = _ffi.cast('const Span *', s1) + s2_converted = _ffi.cast('const Span *', s2) + result = _lib.union_span_span(s1_converted, s2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + s_converted = _ffi.cast('const Span *', s) + result = _lib.union_spanset_span(ss_converted, s_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'SpanSet *': + ss1_converted = _ffi.cast('const SpanSet *', ss1) + ss2_converted = _ffi.cast('const SpanSet *', ss2) + result = _lib.union_spanset_spanset(ss1_converted, ss2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_textset_text(s: 'const Set *', txt: str) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + txt_converted = cstring2text(txt) + result = _lib.union_textset_text(s_converted, txt_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_timestampset_timestamp(s: 'const Set *', t: int) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + t_converted = _ffi.cast('const TimestampTz', t) + result = _lib.union_timestampset_timestamp(s_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def adjacent_bigintspan_bigint(s: 'const Span *', i: int) -> 'bool': + s_converted = _ffi.cast('const Span *', s) + i_converted = _ffi.cast('int64', i) + result = _lib.adjacent_bigintspan_bigint(s_converted, i_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def adjacent_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + i_converted = _ffi.cast('int64', i) + result = _lib.adjacent_bigintspanset_bigint(ss_converted, i_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def adjacent_floatspan_float(s: 'const Span *', d: float) -> 'bool': + s_converted = _ffi.cast('const Span *', s) + result = _lib.adjacent_floatspan_float(s_converted, d) + _check_error() + return result if result != _ffi.NULL else None + + +def adjacent_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.adjacent_floatspanset_float(ss_converted, d) + _check_error() + return result if result != _ffi.NULL else None + + +def adjacent_intspan_int(s: 'const Span *', i: int) -> 'bool': + s_converted = _ffi.cast('const Span *', s) + result = _lib.adjacent_intspan_int(s_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def adjacent_intspanset_int(ss: 'const SpanSet *', i: int) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.adjacent_intspanset_int(ss_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def adjacent_period_timestamp(p: 'const Span *', t: int) -> 'bool': + p_converted = _ffi.cast('const Span *', p) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.adjacent_period_timestamp(p_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def adjacent_periodset_timestamp(ps: 'const SpanSet *', t: int) -> 'bool': + ps_converted = _ffi.cast('const SpanSet *', ps) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.adjacent_periodset_timestamp(ps_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def adjacent_span_span(s1: 'const Span *', s2: 'const Span *') -> 'bool': + s1_converted = _ffi.cast('const Span *', s1) s2_converted = _ffi.cast('const Span *', s2) result = _lib.adjacent_span_span(s1_converted, s2_converted) _check_error() @@ -2108,6 +2797,38 @@ def overlaps_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> return result if result != _ffi.NULL else None +def after_period_timestamp(s: 'const Span *', t: int) -> 'bool': + s_converted = _ffi.cast('const Span *', s) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.after_period_timestamp(s_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def after_periodset_timestamp(ss: 'const SpanSet *', t: int) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.after_periodset_timestamp(ss_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def after_timestamp_period(t: int, s: 'const Span *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) + s_converted = _ffi.cast('const Span *', s) + result = _lib.after_timestamp_period(t_converted, s_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def after_timestamp_periodset(t: int, ss: 'const SpanSet *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.after_timestamp_periodset(t_converted, ss_converted) + _check_error() + return result if result != _ffi.NULL else None + + def after_timestamp_timestampset(t: int, ts: 'const Set *') -> 'bool': t_converted = _ffi.cast('TimestampTz', t) ts_converted = _ffi.cast('const Set *', ts) @@ -2116,1165 +2837,1028 @@ def after_timestamp_timestampset(t: int, ts: 'const Set *') -> 'bool': return result if result != _ffi.NULL else None -def before_periodset_timestamp(ps: 'const SpanSet *', t: int) -> 'bool': - ps_converted = _ffi.cast('const SpanSet *', ps) +def after_timestampset_timestamp(s: 'const Set *', t: int) -> 'bool': + s_converted = _ffi.cast('const Set *', s) t_converted = _ffi.cast('TimestampTz', t) - result = _lib.before_periodset_timestamp(ps_converted, t_converted) + result = _lib.after_timestampset_timestamp(s_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def before_timestamp_timestampset(t: int, ts: 'const Set *') -> 'bool': +def before_period_timestamp(s: 'const Span *', t: int) -> 'bool': + s_converted = _ffi.cast('const Span *', s) t_converted = _ffi.cast('TimestampTz', t) - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.before_timestamp_timestampset(t_converted, ts_converted) + result = _lib.before_period_timestamp(s_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def left_float_floatspan(d: float, s: 'const Span *') -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.left_float_floatspan(d, s_converted) +def before_periodset_timestamp(ss: 'const SpanSet *', t: int) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.before_periodset_timestamp(ss_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def left_floatspan_float(s: 'const Span *', d: float) -> 'bool': +def before_timestamp_period(t: int, s: 'const Span *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) s_converted = _ffi.cast('const Span *', s) - result = _lib.left_floatspan_float(s_converted, d) + result = _lib.before_timestamp_period(t_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def left_int_intspan(i: int, s: 'const Span *') -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.left_int_intspan(i, s_converted) +def before_timestamp_periodset(t: int, ss: 'const SpanSet *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.before_timestamp_periodset(t_converted, ss_converted) _check_error() return result if result != _ffi.NULL else None -def left_intspan_int(s: 'const Span *', i: int) -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.left_intspan_int(s_converted, i) +def before_timestamp_timestampset(t: int, ts: 'const Set *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.before_timestamp_timestampset(t_converted, ts_converted) _check_error() return result if result != _ffi.NULL else None -def left_set_set(s1: 'const Set *', s2: 'const Set *') -> 'bool': - s1_converted = _ffi.cast('const Set *', s1) - s2_converted = _ffi.cast('const Set *', s2) - result = _lib.left_set_set(s1_converted, s2_converted) +def before_timestampset_timestamp(s: 'const Set *', t: int) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.before_timestampset_timestamp(s_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def left_span_span(s1: 'const Span *', s2: 'const Span *') -> 'bool': - s1_converted = _ffi.cast('const Span *', s1) - s2_converted = _ffi.cast('const Span *', s2) - result = _lib.left_span_span(s1_converted, s2_converted) +def left_bigint_bigintset(i: int, s: 'const Set *') -> 'bool': + i_converted = _ffi.cast('int64', i) + s_converted = _ffi.cast('const Set *', s) + result = _lib.left_bigint_bigintset(i_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def left_span_spanset(s: 'const Span *', ss: 'const SpanSet *') -> 'bool': +def left_bigint_bigintspan(i: int, s: 'const Span *') -> 'bool': + i_converted = _ffi.cast('int64', i) s_converted = _ffi.cast('const Span *', s) - ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.left_span_spanset(s_converted, ss_converted) + result = _lib.left_bigint_bigintspan(i_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def left_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'bool': +def left_bigint_bigintspanset(i: int, ss: 'const SpanSet *') -> 'bool': + i_converted = _ffi.cast('int64', i) ss_converted = _ffi.cast('const SpanSet *', ss) - s_converted = _ffi.cast('const Span *', s) - result = _lib.left_spanset_span(ss_converted, s_converted) + result = _lib.left_bigint_bigintspanset(i_converted, ss_converted) _check_error() return result if result != _ffi.NULL else None -def left_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'bool': - ss1_converted = _ffi.cast('const SpanSet *', ss1) - ss2_converted = _ffi.cast('const SpanSet *', ss2) - result = _lib.left_spanset_spanset(ss1_converted, ss2_converted) +def left_bigintset_bigint(s: 'const Set *', i: int) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + i_converted = _ffi.cast('int64', i) + result = _lib.left_bigintset_bigint(s_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def overafter_period_timestamp(p: 'const Span *', t: int) -> 'bool': - p_converted = _ffi.cast('const Span *', p) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.overafter_period_timestamp(p_converted, t_converted) +def left_bigintspan_bigint(s: 'const Span *', i: int) -> 'bool': + s_converted = _ffi.cast('const Span *', s) + i_converted = _ffi.cast('int64', i) + result = _lib.left_bigintspan_bigint(s_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def overafter_periodset_timestamp(ps: 'const SpanSet *', t: int) -> 'bool': - ps_converted = _ffi.cast('const SpanSet *', ps) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.overafter_periodset_timestamp(ps_converted, t_converted) +def left_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + i_converted = _ffi.cast('int64', i) + result = _lib.left_bigintspanset_bigint(ss_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def overafter_timestamp_period(t: int, p: 'const Span *') -> 'bool': - t_converted = _ffi.cast('TimestampTz', t) - p_converted = _ffi.cast('const Span *', p) - result = _lib.overafter_timestamp_period(t_converted, p_converted) +def left_float_floatset(d: float, s: 'const Set *') -> 'bool': + s_converted = _ffi.cast('const Set *', s) + result = _lib.left_float_floatset(d, s_converted) _check_error() return result if result != _ffi.NULL else None -def overafter_timestamp_periodset(t: int, ps: 'const SpanSet *') -> 'bool': - t_converted = _ffi.cast('TimestampTz', t) - ps_converted = _ffi.cast('const SpanSet *', ps) - result = _lib.overafter_timestamp_periodset(t_converted, ps_converted) +def left_float_floatspan(d: float, s: 'const Span *') -> 'bool': + s_converted = _ffi.cast('const Span *', s) + result = _lib.left_float_floatspan(d, s_converted) _check_error() return result if result != _ffi.NULL else None -def overafter_timestamp_timestampset(t: int, ts: 'const Set *') -> 'bool': - t_converted = _ffi.cast('TimestampTz', t) - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.overafter_timestamp_timestampset(t_converted, ts_converted) +def left_float_floatspanset(d: float, ss: 'const SpanSet *') -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.left_float_floatspanset(d, ss_converted) _check_error() return result if result != _ffi.NULL else None -def overbefore_period_timestamp(p: 'const Span *', t: int) -> 'bool': - p_converted = _ffi.cast('const Span *', p) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.overbefore_period_timestamp(p_converted, t_converted) +def left_floatset_float(s: 'const Set *', d: float) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + result = _lib.left_floatset_float(s_converted, d) _check_error() return result if result != _ffi.NULL else None -def overbefore_periodset_timestamp(ps: 'const SpanSet *', t: int) -> 'bool': - ps_converted = _ffi.cast('const SpanSet *', ps) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.overbefore_periodset_timestamp(ps_converted, t_converted) +def left_floatspan_float(s: 'const Span *', d: float) -> 'bool': + s_converted = _ffi.cast('const Span *', s) + result = _lib.left_floatspan_float(s_converted, d) _check_error() return result if result != _ffi.NULL else None -def overbefore_timestamp_period(t: int, p: 'const Span *') -> 'bool': - t_converted = _ffi.cast('TimestampTz', t) - p_converted = _ffi.cast('const Span *', p) - result = _lib.overbefore_timestamp_period(t_converted, p_converted) +def left_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.left_floatspanset_float(ss_converted, d) _check_error() return result if result != _ffi.NULL else None -def overbefore_timestamp_periodset(t: int, ps: 'const SpanSet *') -> 'bool': - t_converted = _ffi.cast('TimestampTz', t) - ps_converted = _ffi.cast('const SpanSet *', ps) - result = _lib.overbefore_timestamp_periodset(t_converted, ps_converted) +def left_int_intset(i: int, s: 'const Set *') -> 'bool': + s_converted = _ffi.cast('const Set *', s) + result = _lib.left_int_intset(i, s_converted) _check_error() return result if result != _ffi.NULL else None -def overbefore_timestamp_timestampset(t: int, ts: 'const Set *') -> 'bool': - t_converted = _ffi.cast('TimestampTz', t) - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.overbefore_timestamp_timestampset(t_converted, ts_converted) +def left_int_intspan(i: int, s: 'const Span *') -> 'bool': + s_converted = _ffi.cast('const Span *', s) + result = _lib.left_int_intspan(i, s_converted) _check_error() return result if result != _ffi.NULL else None -def overleft_float_floatspan(d: float, s: 'const Span *') -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.overleft_float_floatspan(d, s_converted) +def left_int_intspanset(i: int, ss: 'const SpanSet *') -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.left_int_intspanset(i, ss_converted) _check_error() return result if result != _ffi.NULL else None -def overleft_floatspan_float(s: 'const Span *', d: float) -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.overleft_floatspan_float(s_converted, d) +def left_intset_int(s: 'const Set *', i: int) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + result = _lib.left_intset_int(s_converted, i) _check_error() return result if result != _ffi.NULL else None -def overleft_int_intspan(i: int, s: 'const Span *') -> 'bool': +def left_intspan_int(s: 'const Span *', i: int) -> 'bool': s_converted = _ffi.cast('const Span *', s) - result = _lib.overleft_int_intspan(i, s_converted) + result = _lib.left_intspan_int(s_converted, i) _check_error() return result if result != _ffi.NULL else None -def overleft_intspan_int(s: 'const Span *', i: int) -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.overleft_intspan_int(s_converted, i) +def left_intspanset_int(ss: 'const SpanSet *', i: int) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.left_intspanset_int(ss_converted, i) _check_error() return result if result != _ffi.NULL else None -def overleft_set_set(s1: 'const Set *', s2: 'const Set *') -> 'bool': +def left_set_set(s1: 'const Set *', s2: 'const Set *') -> 'bool': s1_converted = _ffi.cast('const Set *', s1) s2_converted = _ffi.cast('const Set *', s2) - result = _lib.overleft_set_set(s1_converted, s2_converted) + result = _lib.left_set_set(s1_converted, s2_converted) _check_error() return result if result != _ffi.NULL else None -def overleft_span_span(s1: 'const Span *', s2: 'const Span *') -> 'bool': +def left_span_span(s1: 'const Span *', s2: 'const Span *') -> 'bool': s1_converted = _ffi.cast('const Span *', s1) s2_converted = _ffi.cast('const Span *', s2) - result = _lib.overleft_span_span(s1_converted, s2_converted) + result = _lib.left_span_span(s1_converted, s2_converted) _check_error() return result if result != _ffi.NULL else None -def overleft_span_spanset(s: 'const Span *', ss: 'const SpanSet *') -> 'bool': +def left_span_spanset(s: 'const Span *', ss: 'const SpanSet *') -> 'bool': s_converted = _ffi.cast('const Span *', s) ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.overleft_span_spanset(s_converted, ss_converted) + result = _lib.left_span_spanset(s_converted, ss_converted) _check_error() return result if result != _ffi.NULL else None -def overleft_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'bool': +def left_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) s_converted = _ffi.cast('const Span *', s) - result = _lib.overleft_spanset_span(ss_converted, s_converted) + result = _lib.left_spanset_span(ss_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def overleft_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'bool': +def left_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'bool': ss1_converted = _ffi.cast('const SpanSet *', ss1) ss2_converted = _ffi.cast('const SpanSet *', ss2) - result = _lib.overleft_spanset_spanset(ss1_converted, ss2_converted) + result = _lib.left_spanset_spanset(ss1_converted, ss2_converted) _check_error() return result if result != _ffi.NULL else None -def overright_float_floatspan(d: float, s: 'const Span *') -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.overright_float_floatspan(d, s_converted) +def left_text_textset(txt: str, s: 'const Set *') -> 'bool': + txt_converted = cstring2text(txt) + s_converted = _ffi.cast('const Set *', s) + result = _lib.left_text_textset(txt_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def overright_floatspan_float(s: 'const Span *', d: float) -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.overright_floatspan_float(s_converted, d) +def left_textset_text(s: 'const Set *', txt: str) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + txt_converted = cstring2text(txt) + result = _lib.left_textset_text(s_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def overright_int_intspan(i: int, s: 'const Span *') -> 'bool': +def overafter_period_timestamp(s: 'const Span *', t: int) -> 'bool': s_converted = _ffi.cast('const Span *', s) - result = _lib.overright_int_intspan(i, s_converted) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.overafter_period_timestamp(s_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def overright_intspan_int(s: 'const Span *', i: int) -> 'bool': +def overafter_periodset_timestamp(ss: 'const SpanSet *', t: int) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.overafter_periodset_timestamp(ss_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def overafter_timestamp_period(t: int, s: 'const Span *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) s_converted = _ffi.cast('const Span *', s) - result = _lib.overright_intspan_int(s_converted, i) + result = _lib.overafter_timestamp_period(t_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def overright_set_set(s1: 'const Set *', s2: 'const Set *') -> 'bool': - s1_converted = _ffi.cast('const Set *', s1) - s2_converted = _ffi.cast('const Set *', s2) - result = _lib.overright_set_set(s1_converted, s2_converted) +def overafter_timestamp_periodset(t: int, ss: 'const SpanSet *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.overafter_timestamp_periodset(t_converted, ss_converted) _check_error() return result if result != _ffi.NULL else None -def overright_span_span(s1: 'const Span *', s2: 'const Span *') -> 'bool': - s1_converted = _ffi.cast('const Span *', s1) - s2_converted = _ffi.cast('const Span *', s2) - result = _lib.overright_span_span(s1_converted, s2_converted) +def overafter_timestamp_timestampset(t: int, ts: 'const Set *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.overafter_timestamp_timestampset(t_converted, ts_converted) _check_error() return result if result != _ffi.NULL else None -def overright_span_spanset(s: 'const Span *', ss: 'const SpanSet *') -> 'bool': - s_converted = _ffi.cast('const Span *', s) - ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.overright_span_spanset(s_converted, ss_converted) +def overafter_timestampset_timestamp(s: 'const Set *', t: int) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.overafter_timestampset_timestamp(s_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def overright_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'bool': - ss_converted = _ffi.cast('const SpanSet *', ss) +def overbefore_period_timestamp(s: 'const Span *', t: int) -> 'bool': s_converted = _ffi.cast('const Span *', s) - result = _lib.overright_spanset_span(ss_converted, s_converted) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.overbefore_period_timestamp(s_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def overright_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'bool': - ss1_converted = _ffi.cast('const SpanSet *', ss1) - ss2_converted = _ffi.cast('const SpanSet *', ss2) - result = _lib.overright_spanset_spanset(ss1_converted, ss2_converted) +def overbefore_periodset_timestamp(ss: 'const SpanSet *', t: int) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.overbefore_periodset_timestamp(ss_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def right_float_floatspan(d: float, s: 'const Span *') -> 'bool': +def overbefore_timestamp_period(t: int, s: 'const Span *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) s_converted = _ffi.cast('const Span *', s) - result = _lib.right_float_floatspan(d, s_converted) + result = _lib.overbefore_timestamp_period(t_converted, s_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def overbefore_timestamp_periodset(t: int, ss: 'const SpanSet *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.overbefore_timestamp_periodset(t_converted, ss_converted) _check_error() return result if result != _ffi.NULL else None -def right_floatspan_float(s: 'const Span *', d: float) -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.right_floatspan_float(s_converted, d) +def overbefore_timestamp_timestampset(t: int, ts: 'const Set *') -> 'bool': + t_converted = _ffi.cast('TimestampTz', t) + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.overbefore_timestamp_timestampset(t_converted, ts_converted) _check_error() return result if result != _ffi.NULL else None -def right_int_intspan(i: int, s: 'const Span *') -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.right_int_intspan(i, s_converted) +def overbefore_timestampset_timestamp(s: 'const Set *', t: int) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.overbefore_timestampset_timestamp(s_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def right_intspan_int(s: 'const Span *', i: int) -> 'bool': - s_converted = _ffi.cast('const Span *', s) - result = _lib.right_intspan_int(s_converted, i) +def overleft_bigint_bigintset(i: int, s: 'const Set *') -> 'bool': + i_converted = _ffi.cast('int64', i) + s_converted = _ffi.cast('const Set *', s) + result = _lib.overleft_bigint_bigintset(i_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def right_set_set(s1: 'const Set *', s2: 'const Set *') -> 'bool': - s1_converted = _ffi.cast('const Set *', s1) - s2_converted = _ffi.cast('const Set *', s2) - result = _lib.right_set_set(s1_converted, s2_converted) +def overleft_bigint_bigintspan(i: int, s: 'const Span *') -> 'bool': + i_converted = _ffi.cast('int64', i) + s_converted = _ffi.cast('const Span *', s) + result = _lib.overleft_bigint_bigintspan(i_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def right_span_span(s1: 'const Span *', s2: 'const Span *') -> 'bool': - s1_converted = _ffi.cast('const Span *', s1) - s2_converted = _ffi.cast('const Span *', s2) - result = _lib.right_span_span(s1_converted, s2_converted) +def overleft_bigint_bigintspanset(i: int, ss: 'const SpanSet *') -> 'bool': + i_converted = _ffi.cast('int64', i) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.overleft_bigint_bigintspanset(i_converted, ss_converted) _check_error() return result if result != _ffi.NULL else None -def right_span_spanset(s: 'const Span *', ss: 'const SpanSet *') -> 'bool': - s_converted = _ffi.cast('const Span *', s) - ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.right_span_spanset(s_converted, ss_converted) +def overleft_bigintset_bigint(s: 'const Set *', i: int) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + i_converted = _ffi.cast('int64', i) + result = _lib.overleft_bigintset_bigint(s_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def right_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'bool': - ss_converted = _ffi.cast('const SpanSet *', ss) +def overleft_bigintspan_bigint(s: 'const Span *', i: int) -> 'bool': s_converted = _ffi.cast('const Span *', s) - result = _lib.right_spanset_span(ss_converted, s_converted) + i_converted = _ffi.cast('int64', i) + result = _lib.overleft_bigintspan_bigint(s_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def right_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'bool': - ss1_converted = _ffi.cast('const SpanSet *', ss1) - ss2_converted = _ffi.cast('const SpanSet *', ss2) - result = _lib.right_spanset_spanset(ss1_converted, ss2_converted) +def overleft_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + i_converted = _ffi.cast('int64', i) + result = _lib.overleft_bigintspanset_bigint(ss_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def intersection_bigintset_bigint(s: 'const Set *', i: int) -> 'int64': +def overleft_float_floatset(d: float, s: 'const Set *') -> 'bool': s_converted = _ffi.cast('const Set *', s) - i_converted = _ffi.cast('int64', i) - out_result = _ffi.new('int64 *') - result = _lib.intersection_bigintset_bigint(s_converted, i_converted, out_result) + result = _lib.overleft_float_floatset(d, s_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_bigintspan_bigint(s: 'const Span *', i: int) -> 'int64': +def overleft_float_floatspan(d: float, s: 'const Span *') -> 'bool': s_converted = _ffi.cast('const Span *', s) - i_converted = _ffi.cast('int64', i) - out_result = _ffi.new('int64 *') - result = _lib.intersection_bigintspan_bigint(s_converted, i_converted, out_result) + result = _lib.overleft_float_floatspan(d, s_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'int64': +def overleft_float_floatspanset(d: float, ss: 'const SpanSet *') -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) - i_converted = _ffi.cast('int64', i) - out_result = _ffi.new('int64 *') - result = _lib.intersection_bigintspanset_bigint(ss_converted, i_converted, out_result) + result = _lib.overleft_float_floatspanset(d, ss_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_floatset_float(s: 'const Set *', d: float) -> 'double': +def overleft_floatset_float(s: 'const Set *', d: float) -> 'bool': s_converted = _ffi.cast('const Set *', s) - out_result = _ffi.new('double *') - result = _lib.intersection_floatset_float(s_converted, d, out_result) + result = _lib.overleft_floatset_float(s_converted, d) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_floatspan_float(s: 'const Span *', d: float) -> 'double': +def overleft_floatspan_float(s: 'const Span *', d: float) -> 'bool': s_converted = _ffi.cast('const Span *', s) - out_result = _ffi.new('double *') - result = _lib.intersection_floatspan_float(s_converted, d, out_result) + result = _lib.overleft_floatspan_float(s_converted, d) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'double': +def overleft_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) - out_result = _ffi.new('double *') - result = _lib.intersection_floatspanset_float(ss_converted, d, out_result) + result = _lib.overleft_floatspanset_float(ss_converted, d) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_intset_int(s: 'const Set *', i: int) -> 'int': +def overleft_int_intset(i: int, s: 'const Set *') -> 'bool': s_converted = _ffi.cast('const Set *', s) - out_result = _ffi.new('int *') - result = _lib.intersection_intset_int(s_converted, i, out_result) + result = _lib.overleft_int_intset(i, s_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_intspan_int(s: 'const Span *', i: int) -> 'int': +def overleft_int_intspan(i: int, s: 'const Span *') -> 'bool': s_converted = _ffi.cast('const Span *', s) - out_result = _ffi.new('int *') - result = _lib.intersection_intspan_int(s_converted, i, out_result) + result = _lib.overleft_int_intspan(i, s_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_intspanset_int(ss: 'const SpanSet *', i: int) -> 'int': +def overleft_int_intspanset(i: int, ss: 'const SpanSet *') -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) - out_result = _ffi.new('int *') - result = _lib.intersection_intspanset_int(ss_converted, i, out_result) + result = _lib.overleft_int_intspanset(i, ss_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_period_timestamp(s: 'const Span *', t: int) -> int: +def overleft_intset_int(s: 'const Set *', i: int) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + result = _lib.overleft_intset_int(s_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def overleft_intspan_int(s: 'const Span *', i: int) -> 'bool': s_converted = _ffi.cast('const Span *', s) - t_converted = _ffi.cast('TimestampTz', t) - out_result = _ffi.new('TimestampTz *') - result = _lib.intersection_period_timestamp(s_converted, t_converted, out_result) + result = _lib.overleft_intspan_int(s_converted, i) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_periodset_timestamp(ss: 'const SpanSet *', t: int) -> int: +def overleft_intspanset_int(ss: 'const SpanSet *', i: int) -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) - t_converted = _ffi.cast('TimestampTz', t) - out_result = _ffi.new('TimestampTz *') - result = _lib.intersection_periodset_timestamp(ss_converted, t_converted, out_result) + result = _lib.overleft_intspanset_int(ss_converted, i) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_set_set(s1: 'const Set *', s2: 'const Set *') -> 'Set *': +def overleft_set_set(s1: 'const Set *', s2: 'const Set *') -> 'bool': s1_converted = _ffi.cast('const Set *', s1) s2_converted = _ffi.cast('const Set *', s2) - result = _lib.intersection_set_set(s1_converted, s2_converted) + result = _lib.overleft_set_set(s1_converted, s2_converted) _check_error() return result if result != _ffi.NULL else None -def intersection_span_span(s1: 'const Span *', s2: 'const Span *') -> 'Span *': +def overleft_span_span(s1: 'const Span *', s2: 'const Span *') -> 'bool': s1_converted = _ffi.cast('const Span *', s1) s2_converted = _ffi.cast('const Span *', s2) - result = _lib.intersection_span_span(s1_converted, s2_converted) + result = _lib.overleft_span_span(s1_converted, s2_converted) _check_error() return result if result != _ffi.NULL else None -def intersection_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'SpanSet *': +def overleft_span_spanset(s: 'const Span *', ss: 'const SpanSet *') -> 'bool': + s_converted = _ffi.cast('const Span *', s) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.overleft_span_spanset(s_converted, ss_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def overleft_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) s_converted = _ffi.cast('const Span *', s) - result = _lib.intersection_spanset_span(ss_converted, s_converted) + result = _lib.overleft_spanset_span(ss_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def intersection_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'SpanSet *': +def overleft_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'bool': ss1_converted = _ffi.cast('const SpanSet *', ss1) ss2_converted = _ffi.cast('const SpanSet *', ss2) - result = _lib.intersection_spanset_spanset(ss1_converted, ss2_converted) + result = _lib.overleft_spanset_spanset(ss1_converted, ss2_converted) _check_error() return result if result != _ffi.NULL else None -def intersection_textset_text(s: 'const Set *', txt: str) -> 'text **': - s_converted = _ffi.cast('const Set *', s) +def overleft_text_textset(txt: str, s: 'const Set *') -> 'bool': txt_converted = cstring2text(txt) - out_result = _ffi.new('text **') - result = _lib.intersection_textset_text(s_converted, txt_converted, out_result) + s_converted = _ffi.cast('const Set *', s) + result = _lib.overleft_text_textset(txt_converted, s_converted) _check_error() - if result: - return out_result if out_result != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_timestampset_timestamp(s: 'const Set *', t: int) -> int: +def overleft_textset_text(s: 'const Set *', txt: str) -> 'bool': s_converted = _ffi.cast('const Set *', s) - t_converted = _ffi.cast('TimestampTz', t) - out_result = _ffi.new('TimestampTz *') - result = _lib.intersection_timestampset_timestamp(s_converted, t_converted, out_result) + txt_converted = cstring2text(txt) + result = _lib.overleft_textset_text(s_converted, txt_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def minus_bigint_bigintset(i: int, s: 'const Set *') -> 'int64': +def overright_bigint_bigintset(i: int, s: 'const Set *') -> 'bool': i_converted = _ffi.cast('int64', i) s_converted = _ffi.cast('const Set *', s) - out_result = _ffi.new('int64 *') - result = _lib.minus_bigint_bigintset(i_converted, s_converted, out_result) + result = _lib.overright_bigint_bigintset(i_converted, s_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def minus_bigint_bigintspan(i: int, s: 'const Span *') -> 'int64': +def overright_bigint_bigintspan(i: int, s: 'const Span *') -> 'bool': i_converted = _ffi.cast('int64', i) s_converted = _ffi.cast('const Span *', s) - out_result = _ffi.new('int64 *') - result = _lib.minus_bigint_bigintspan(i_converted, s_converted, out_result) + result = _lib.overright_bigint_bigintspan(i_converted, s_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def minus_bigint_bigintspanset(i: int, ss: 'const SpanSet *') -> 'int64': +def overright_bigint_bigintspanset(i: int, ss: 'const SpanSet *') -> 'bool': i_converted = _ffi.cast('int64', i) ss_converted = _ffi.cast('const SpanSet *', ss) - out_result = _ffi.new('int64 *') - result = _lib.minus_bigint_bigintspanset(i_converted, ss_converted, out_result) + result = _lib.overright_bigint_bigintspanset(i_converted, ss_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def minus_bigintset_bigint(s: 'const Set *', i: int) -> 'Set *': +def overright_bigintset_bigint(s: 'const Set *', i: int) -> 'bool': s_converted = _ffi.cast('const Set *', s) i_converted = _ffi.cast('int64', i) - result = _lib.minus_bigintset_bigint(s_converted, i_converted) + result = _lib.overright_bigintset_bigint(s_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def minus_bigintspan_bigint(s: 'const Span *', i: int) -> 'SpanSet *': +def overright_bigintspan_bigint(s: 'const Span *', i: int) -> 'bool': s_converted = _ffi.cast('const Span *', s) i_converted = _ffi.cast('int64', i) - result = _lib.minus_bigintspan_bigint(s_converted, i_converted) + result = _lib.overright_bigintspan_bigint(s_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def minus_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'SpanSet *': +def overright_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) i_converted = _ffi.cast('int64', i) - result = _lib.minus_bigintspanset_bigint(ss_converted, i_converted) + result = _lib.overright_bigintspanset_bigint(ss_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def minus_float_floatset(d: float, s: 'const Set *') -> 'double': - s_converted = _ffi.cast('const Set *', s) - out_result = _ffi.new('double *') - result = _lib.minus_float_floatset(d, s_converted, out_result) - _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None - - -def minus_float_floatspan(d: float, s: 'const Span *') -> 'double': - s_converted = _ffi.cast('const Span *', s) - out_result = _ffi.new('double *') - result = _lib.minus_float_floatspan(d, s_converted, out_result) - _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None - - -def minus_float_floatspanset(d: float, ss: 'const SpanSet *') -> 'double': - ss_converted = _ffi.cast('const SpanSet *', ss) - out_result = _ffi.new('double *') - result = _lib.minus_float_floatspanset(d, ss_converted, out_result) - _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None - - -def minus_floatset_float(s: 'const Set *', d: float) -> 'Set *': +def overright_float_floatset(d: float, s: 'const Set *') -> 'bool': s_converted = _ffi.cast('const Set *', s) - result = _lib.minus_floatset_float(s_converted, d) + result = _lib.overright_float_floatset(d, s_converted) _check_error() return result if result != _ffi.NULL else None -def minus_floatspan_float(s: 'const Span *', d: float) -> 'SpanSet *': +def overright_float_floatspan(d: float, s: 'const Span *') -> 'bool': s_converted = _ffi.cast('const Span *', s) - result = _lib.minus_floatspan_float(s_converted, d) + result = _lib.overright_float_floatspan(d, s_converted) _check_error() return result if result != _ffi.NULL else None -def minus_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'SpanSet *': +def overright_float_floatspanset(d: float, ss: 'const SpanSet *') -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.minus_floatspanset_float(ss_converted, d) + result = _lib.overright_float_floatspanset(d, ss_converted) _check_error() return result if result != _ffi.NULL else None -def minus_int_intset(i: int, s: 'const Set *') -> 'int': +def overright_floatset_float(s: 'const Set *', d: float) -> 'bool': s_converted = _ffi.cast('const Set *', s) - out_result = _ffi.new('int *') - result = _lib.minus_int_intset(i, s_converted, out_result) + result = _lib.overright_floatset_float(s_converted, d) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def minus_int_intspan(i: int, s: 'const Span *') -> 'int': +def overright_floatspan_float(s: 'const Span *', d: float) -> 'bool': s_converted = _ffi.cast('const Span *', s) - out_result = _ffi.new('int *') - result = _lib.minus_int_intspan(i, s_converted, out_result) + result = _lib.overright_floatspan_float(s_converted, d) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def minus_int_intspanset(i: int, ss: 'const SpanSet *') -> 'int': +def overright_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) - out_result = _ffi.new('int *') - result = _lib.minus_int_intspanset(i, ss_converted, out_result) + result = _lib.overright_floatspanset_float(ss_converted, d) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def minus_intset_int(s: 'const Set *', i: int) -> 'Set *': +def overright_int_intset(i: int, s: 'const Set *') -> 'bool': s_converted = _ffi.cast('const Set *', s) - result = _lib.minus_intset_int(s_converted, i) + result = _lib.overright_int_intset(i, s_converted) _check_error() return result if result != _ffi.NULL else None -def minus_intspan_int(s: 'const Span *', i: int) -> 'SpanSet *': +def overright_int_intspan(i: int, s: 'const Span *') -> 'bool': s_converted = _ffi.cast('const Span *', s) - result = _lib.minus_intspan_int(s_converted, i) + result = _lib.overright_int_intspan(i, s_converted) _check_error() return result if result != _ffi.NULL else None -def minus_intspanset_int(ss: 'const SpanSet *', i: int) -> 'SpanSet *': +def overright_int_intspanset(i: int, ss: 'const SpanSet *') -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.minus_intspanset_int(ss_converted, i) + result = _lib.overright_int_intspanset(i, ss_converted) _check_error() return result if result != _ffi.NULL else None -def minus_period_timestamp(s: 'const Span *', t: int) -> 'SpanSet *': +def overright_intset_int(s: 'const Set *', i: int) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + result = _lib.overright_intset_int(s_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def overright_intspan_int(s: 'const Span *', i: int) -> 'bool': s_converted = _ffi.cast('const Span *', s) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.minus_period_timestamp(s_converted, t_converted) + result = _lib.overright_intspan_int(s_converted, i) _check_error() return result if result != _ffi.NULL else None -def minus_periodset_timestamp(ss: 'const SpanSet *', t: int) -> 'SpanSet *': +def overright_intspanset_int(ss: 'const SpanSet *', i: int) -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.minus_periodset_timestamp(ss_converted, t_converted) + result = _lib.overright_intspanset_int(ss_converted, i) _check_error() return result if result != _ffi.NULL else None -def minus_set_set(s1: 'const Set *', s2: 'const Set *') -> 'Set *': +def overright_set_set(s1: 'const Set *', s2: 'const Set *') -> 'bool': s1_converted = _ffi.cast('const Set *', s1) s2_converted = _ffi.cast('const Set *', s2) - result = _lib.minus_set_set(s1_converted, s2_converted) + result = _lib.overright_set_set(s1_converted, s2_converted) _check_error() return result if result != _ffi.NULL else None -def minus_span_span(s1: 'const Span *', s2: 'const Span *') -> 'SpanSet *': +def overright_span_span(s1: 'const Span *', s2: 'const Span *') -> 'bool': s1_converted = _ffi.cast('const Span *', s1) s2_converted = _ffi.cast('const Span *', s2) - result = _lib.minus_span_span(s1_converted, s2_converted) + result = _lib.overright_span_span(s1_converted, s2_converted) _check_error() return result if result != _ffi.NULL else None -def minus_span_spanset(s: 'const Span *', ss: 'const SpanSet *') -> 'SpanSet *': +def overright_span_spanset(s: 'const Span *', ss: 'const SpanSet *') -> 'bool': s_converted = _ffi.cast('const Span *', s) ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.minus_span_spanset(s_converted, ss_converted) + result = _lib.overright_span_spanset(s_converted, ss_converted) _check_error() return result if result != _ffi.NULL else None -def minus_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'SpanSet *': +def overright_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) s_converted = _ffi.cast('const Span *', s) - result = _lib.minus_spanset_span(ss_converted, s_converted) + result = _lib.overright_spanset_span(ss_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def minus_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'SpanSet *': +def overright_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'bool': ss1_converted = _ffi.cast('const SpanSet *', ss1) ss2_converted = _ffi.cast('const SpanSet *', ss2) - result = _lib.minus_spanset_spanset(ss1_converted, ss2_converted) + result = _lib.overright_spanset_spanset(ss1_converted, ss2_converted) _check_error() return result if result != _ffi.NULL else None -def minus_text_textset(txt: str, s: 'const Set *') -> 'text **': +def overright_text_textset(txt: str, s: 'const Set *') -> 'bool': txt_converted = cstring2text(txt) s_converted = _ffi.cast('const Set *', s) - out_result = _ffi.new('text **') - result = _lib.minus_text_textset(txt_converted, s_converted, out_result) + result = _lib.overright_text_textset(txt_converted, s_converted) _check_error() - if result: - return out_result if out_result != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def minus_textset_text(s: 'const Set *', txt: str) -> 'Set *': +def overright_textset_text(s: 'const Set *', txt: str) -> 'bool': s_converted = _ffi.cast('const Set *', s) txt_converted = cstring2text(txt) - result = _lib.minus_textset_text(s_converted, txt_converted) + result = _lib.overright_textset_text(s_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def minus_timestamp_period(t: int, s: 'const Span *') -> int: - t_converted = _ffi.cast('TimestampTz', t) - s_converted = _ffi.cast('const Span *', s) - out_result = _ffi.new('TimestampTz *') - result = _lib.minus_timestamp_period(t_converted, s_converted, out_result) - _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None - - -def minus_timestamp_periodset(t: int, ss: 'const SpanSet *') -> int: - t_converted = _ffi.cast('TimestampTz', t) - ss_converted = _ffi.cast('const SpanSet *', ss) - out_result = _ffi.new('TimestampTz *') - result = _lib.minus_timestamp_periodset(t_converted, ss_converted, out_result) - _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None - - -def minus_timestampset_timestamp(s: 'const Set *', t: int) -> 'Set *': +def right_bigint_bigintset(i: int, s: 'const Set *') -> 'bool': + i_converted = _ffi.cast('int64', i) s_converted = _ffi.cast('const Set *', s) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.minus_timestampset_timestamp(s_converted, t_converted) + result = _lib.right_bigint_bigintset(i_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def union_bigintset_bigint(s: 'const Set *', i: int) -> 'Set *': - s_converted = _ffi.cast('const Set *', s) +def right_bigint_bigintspan(i: int, s: 'const Span *') -> 'bool': i_converted = _ffi.cast('int64', i) - result = _lib.union_bigintset_bigint(s_converted, i_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def union_bigintspan_bigint(s: 'const Span *', i: int) -> 'SpanSet *': s_converted = _ffi.cast('const Span *', s) - i_converted = _ffi.cast('int64', i) - result = _lib.union_bigintspan_bigint(s_converted, i_converted) + result = _lib.right_bigint_bigintspan(i_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def union_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'SpanSet *': - ss_converted = _ffi.cast('const SpanSet *', ss) +def right_bigint_bigintspanset(i: int, ss: 'const SpanSet *') -> 'bool': i_converted = _ffi.cast('int64', i) - result = _lib.union_bigintspanset_bigint(ss_converted, i_converted) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.right_bigint_bigintspanset(i_converted, ss_converted) _check_error() return result if result != _ffi.NULL else None -def union_floatset_float(s: 'const Set *', d: float) -> 'Set *': +def right_bigintset_bigint(s: 'const Set *', i: int) -> 'bool': s_converted = _ffi.cast('const Set *', s) - result = _lib.union_floatset_float(s_converted, d) + i_converted = _ffi.cast('int64', i) + result = _lib.right_bigintset_bigint(s_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def union_floatspan_float(s: 'const Span *', d: float) -> 'SpanSet *': +def right_bigintspan_bigint(s: 'const Span *', i: int) -> 'bool': s_converted = _ffi.cast('const Span *', s) - result = _lib.union_floatspan_float(s_converted, d) + i_converted = _ffi.cast('int64', i) + result = _lib.right_bigintspan_bigint(s_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def union_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'SpanSet *': +def right_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.union_floatspanset_float(ss_converted, d) + i_converted = _ffi.cast('int64', i) + result = _lib.right_bigintspanset_bigint(ss_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def union_intset_int(s: 'const Set *', i: int) -> 'Set *': +def right_float_floatset(d: float, s: 'const Set *') -> 'bool': s_converted = _ffi.cast('const Set *', s) - result = _lib.union_intset_int(s_converted, i) + result = _lib.right_float_floatset(d, s_converted) _check_error() return result if result != _ffi.NULL else None -def union_intspan_int(s: 'const Span *', i: int) -> 'SpanSet *': +def right_float_floatspan(d: float, s: 'const Span *') -> 'bool': s_converted = _ffi.cast('const Span *', s) - result = _lib.union_intspan_int(s_converted, i) + result = _lib.right_float_floatspan(d, s_converted) _check_error() return result if result != _ffi.NULL else None -def union_intspanset_int(ss: 'const SpanSet *', i: int) -> 'SpanSet *': +def right_float_floatspanset(d: float, ss: 'const SpanSet *') -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.union_intspanset_int(ss_converted, i) + result = _lib.right_float_floatspanset(d, ss_converted) _check_error() return result if result != _ffi.NULL else None -def union_period_timestamp(s: 'const Span *', t: int) -> 'SpanSet *': - s_converted = _ffi.cast('const Span *', s) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.union_period_timestamp(s_converted, t_converted) +def right_floatset_float(s: 'const Set *', d: float) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + result = _lib.right_floatset_float(s_converted, d) _check_error() return result if result != _ffi.NULL else None -def union_periodset_timestamp(ss: 'SpanSet *', t: int) -> 'SpanSet *': - ss_converted = _ffi.cast('SpanSet *', ss) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.union_periodset_timestamp(ss_converted, t_converted) +def right_floatspan_float(s: 'const Span *', d: float) -> 'bool': + s_converted = _ffi.cast('const Span *', s) + result = _lib.right_floatspan_float(s_converted, d) _check_error() return result if result != _ffi.NULL else None -def union_set_set(s1: 'const Set *', s2: 'const Set *') -> 'Set *': - s1_converted = _ffi.cast('const Set *', s1) - s2_converted = _ffi.cast('const Set *', s2) - result = _lib.union_set_set(s1_converted, s2_converted) +def right_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.right_floatspanset_float(ss_converted, d) _check_error() return result if result != _ffi.NULL else None -def union_span_span(s1: 'const Span *', s2: 'const Span *') -> 'SpanSet *': - s1_converted = _ffi.cast('const Span *', s1) - s2_converted = _ffi.cast('const Span *', s2) - result = _lib.union_span_span(s1_converted, s2_converted) +def right_int_intset(i: int, s: 'const Set *') -> 'bool': + s_converted = _ffi.cast('const Set *', s) + result = _lib.right_int_intset(i, s_converted) _check_error() return result if result != _ffi.NULL else None -def union_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'SpanSet *': - ss_converted = _ffi.cast('const SpanSet *', ss) +def right_int_intspan(i: int, s: 'const Span *') -> 'bool': s_converted = _ffi.cast('const Span *', s) - result = _lib.union_spanset_span(ss_converted, s_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def union_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'SpanSet *': - ss1_converted = _ffi.cast('const SpanSet *', ss1) - ss2_converted = _ffi.cast('const SpanSet *', ss2) - result = _lib.union_spanset_spanset(ss1_converted, ss2_converted) + result = _lib.right_int_intspan(i, s_converted) _check_error() return result if result != _ffi.NULL else None -def union_textset_text(s: 'const Set *', txt: str) -> 'Set *': - s_converted = _ffi.cast('const Set *', s) - txt_converted = cstring2text(txt) - result = _lib.union_textset_text(s_converted, txt_converted) +def right_int_intspanset(i: int, ss: 'const SpanSet *') -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.right_int_intspanset(i, ss_converted) _check_error() return result if result != _ffi.NULL else None -def union_timestampset_timestamp(s: 'const Set *', t: int) -> 'Set *': +def right_intset_int(s: 'const Set *', i: int) -> 'bool': s_converted = _ffi.cast('const Set *', s) - t_converted = _ffi.cast('const TimestampTz', t) - result = _lib.union_timestampset_timestamp(s_converted, t_converted) + result = _lib.right_intset_int(s_converted, i) _check_error() return result if result != _ffi.NULL else None -def distance_floatspan_float(s: 'const Span *', d: float) -> 'double': +def right_intspan_int(s: 'const Span *', i: int) -> 'bool': s_converted = _ffi.cast('const Span *', s) - result = _lib.distance_floatspan_float(s_converted, d) + result = _lib.right_intspan_int(s_converted, i) _check_error() return result if result != _ffi.NULL else None -def distance_intspan_int(s: 'const Span *', i: int) -> 'double': - s_converted = _ffi.cast('const Span *', s) - result = _lib.distance_intspan_int(s_converted, i) +def right_intspanset_int(ss: 'const SpanSet *', i: int) -> 'bool': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.right_intspanset_int(ss_converted, i) _check_error() return result if result != _ffi.NULL else None -def distance_set_set(s1: 'const Set *', s2: 'const Set *') -> 'double': +def right_set_set(s1: 'const Set *', s2: 'const Set *') -> 'bool': s1_converted = _ffi.cast('const Set *', s1) s2_converted = _ffi.cast('const Set *', s2) - result = _lib.distance_set_set(s1_converted, s2_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def distance_period_timestamp(p: 'const Span *', t: int) -> 'double': - p_converted = _ffi.cast('const Span *', p) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.distance_period_timestamp(p_converted, t_converted) + result = _lib.right_set_set(s1_converted, s2_converted) _check_error() return result if result != _ffi.NULL else None -def distance_periodset_timestamp(ps: 'const SpanSet *', t: int) -> 'double': - ps_converted = _ffi.cast('const SpanSet *', ps) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.distance_periodset_timestamp(ps_converted, t_converted) +def right_span_span(s1: 'const Span *', s2: 'const Span *') -> 'bool': + s1_converted = _ffi.cast('const Span *', s1) + s2_converted = _ffi.cast('const Span *', s2) + result = _lib.right_span_span(s1_converted, s2_converted) _check_error() return result if result != _ffi.NULL else None -def distance_span_span(s1: 'const Span *', s2: 'const Span *') -> 'double': - s1_converted = _ffi.cast('const Span *', s1) - s2_converted = _ffi.cast('const Span *', s2) - result = _lib.distance_span_span(s1_converted, s2_converted) +def right_span_spanset(s: 'const Span *', ss: 'const SpanSet *') -> 'bool': + s_converted = _ffi.cast('const Span *', s) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.right_span_spanset(s_converted, ss_converted) _check_error() return result if result != _ffi.NULL else None -def distance_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'double': +def right_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'bool': ss_converted = _ffi.cast('const SpanSet *', ss) s_converted = _ffi.cast('const Span *', s) - result = _lib.distance_spanset_span(ss_converted, s_converted) + result = _lib.right_spanset_span(ss_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def distance_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'double': +def right_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'bool': ss1_converted = _ffi.cast('const SpanSet *', ss1) ss2_converted = _ffi.cast('const SpanSet *', ss2) - result = _lib.distance_spanset_spanset(ss1_converted, ss2_converted) + result = _lib.right_spanset_spanset(ss1_converted, ss2_converted) _check_error() return result if result != _ffi.NULL else None -def distance_timestampset_timestamp(ts: 'const Set *', t: int) -> 'double': - ts_converted = _ffi.cast('const Set *', ts) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.distance_timestampset_timestamp(ts_converted, t_converted) +def right_text_textset(txt: str, s: 'const Set *') -> 'bool': + txt_converted = cstring2text(txt) + s_converted = _ffi.cast('const Set *', s) + result = _lib.right_text_textset(txt_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def bigint_extent_transfn(s: 'Span *', i: int) -> 'Span *': - s_converted = _ffi.cast('Span *', s) - i_converted = _ffi.cast('int64', i) - result = _lib.bigint_extent_transfn(s_converted, i_converted) +def right_textset_text(s: 'const Set *', txt: str) -> 'bool': + s_converted = _ffi.cast('const Set *', s) + txt_converted = cstring2text(txt) + result = _lib.right_textset_text(s_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def bigint_union_transfn(state: 'Set *', i: int) -> 'Set *': - state_converted = _ffi.cast('Set *', state) +def distance_bigintset_bigint(s: 'const Set *', i: int) -> 'double': + s_converted = _ffi.cast('const Set *', s) i_converted = _ffi.cast('int64', i) - result = _lib.bigint_union_transfn(state_converted, i_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def float_extent_transfn(s: 'Span *', d: float) -> 'Span *': - s_converted = _ffi.cast('Span *', s) - result = _lib.float_extent_transfn(s_converted, d) - _check_error() - return result if result != _ffi.NULL else None - - -def float_union_transfn(state: 'Set *', d: float) -> 'Set *': - state_converted = _ffi.cast('Set *', state) - result = _lib.float_union_transfn(state_converted, d) - _check_error() - return result if result != _ffi.NULL else None - - -def int_extent_transfn(s: 'Span *', i: int) -> 'Span *': - s_converted = _ffi.cast('Span *', s) - result = _lib.int_extent_transfn(s_converted, i) - _check_error() - return result if result != _ffi.NULL else None - - -def int_union_transfn(state: 'Set *', i: int) -> 'Set *': - state_converted = _ffi.cast('Set *', state) - result = _lib.int_union_transfn(state_converted, i) + result = _lib.distance_bigintset_bigint(s_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def period_tcount_transfn(state: "Optional['SkipList *']", p: 'const Span *') -> 'SkipList *': - state_converted = _ffi.cast('SkipList *', state) if state is not None else _ffi.NULL - p_converted = _ffi.cast('const Span *', p) - result = _lib.period_tcount_transfn(state_converted, p_converted) +def distance_bigintspan_bigint(s: 'const Span *', i: int) -> 'double': + s_converted = _ffi.cast('const Span *', s) + i_converted = _ffi.cast('int64', i) + result = _lib.distance_bigintspan_bigint(s_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def periodset_tcount_transfn(state: "Optional['SkipList *']", ps: 'const SpanSet *') -> 'SkipList *': - state_converted = _ffi.cast('SkipList *', state) if state is not None else _ffi.NULL - ps_converted = _ffi.cast('const SpanSet *', ps) - result = _lib.periodset_tcount_transfn(state_converted, ps_converted) +def distance_bigintspanset_bigint(ss: 'const SpanSet *', i: int) -> 'double': + ss_converted = _ffi.cast('const SpanSet *', ss) + i_converted = _ffi.cast('int64', i) + result = _lib.distance_bigintspanset_bigint(ss_converted, i_converted) _check_error() return result if result != _ffi.NULL else None -def set_extent_transfn(span: 'Span *', set: 'const Set *') -> 'Span *': - span_converted = _ffi.cast('Span *', span) - set_converted = _ffi.cast('const Set *', set) - result = _lib.set_extent_transfn(span_converted, set_converted) +def distance_floatset_float(s: 'const Set *', d: float) -> 'double': + s_converted = _ffi.cast('const Set *', s) + result = _lib.distance_floatset_float(s_converted, d) _check_error() return result if result != _ffi.NULL else None -def set_union_finalfn(state: 'Set *') -> 'Set *': - state_converted = _ffi.cast('Set *', state) - result = _lib.set_union_finalfn(state_converted) +def distance_floatspan_float(s: 'const Span *', d: float) -> 'double': + s_converted = _ffi.cast('const Span *', s) + result = _lib.distance_floatspan_float(s_converted, d) _check_error() return result if result != _ffi.NULL else None -def set_union_transfn(state: 'Set *', set: 'Set *') -> 'Set *': - state_converted = _ffi.cast('Set *', state) - set_converted = _ffi.cast('Set *', set) - result = _lib.set_union_transfn(state_converted, set_converted) +def distance_floatspanset_float(ss: 'const SpanSet *', d: float) -> 'double': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.distance_floatspanset_float(ss_converted, d) _check_error() return result if result != _ffi.NULL else None -def span_extent_transfn(s1: 'Span *', s2: 'const Span *') -> 'Span *': - s1_converted = _ffi.cast('Span *', s1) - s2_converted = _ffi.cast('const Span *', s2) - result = _lib.span_extent_transfn(s1_converted, s2_converted) +def distance_intset_int(s: 'const Set *', i: int) -> 'double': + s_converted = _ffi.cast('const Set *', s) + result = _lib.distance_intset_int(s_converted, i) _check_error() return result if result != _ffi.NULL else None -def span_union_transfn(state: 'SpanSet *', span: 'const Span *') -> 'SpanSet *': - state_converted = _ffi.cast('SpanSet *', state) - span_converted = _ffi.cast('const Span *', span) - result = _lib.span_union_transfn(state_converted, span_converted) +def distance_intspan_int(s: 'const Span *', i: int) -> 'double': + s_converted = _ffi.cast('const Span *', s) + result = _lib.distance_intspan_int(s_converted, i) _check_error() return result if result != _ffi.NULL else None -def spanset_extent_transfn(s: 'Span *', ss: 'const SpanSet *') -> 'Span *': - s_converted = _ffi.cast('Span *', s) +def distance_intspanset_int(ss: 'const SpanSet *', i: int) -> 'double': ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.spanset_extent_transfn(s_converted, ss_converted) + result = _lib.distance_intspanset_int(ss_converted, i) _check_error() return result if result != _ffi.NULL else None -def spanset_union_finalfn(state: 'SpanSet *') -> 'SpanSet *': - state_converted = _ffi.cast('SpanSet *', state) - result = _lib.spanset_union_finalfn(state_converted) +def distance_period_timestamp(s: 'const Span *', t: int) -> 'double': + s_converted = _ffi.cast('const Span *', s) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.distance_period_timestamp(s_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def spanset_union_transfn(state: 'SpanSet *', ss: 'const SpanSet *') -> 'SpanSet *': - state_converted = _ffi.cast('SpanSet *', state) +def distance_periodset_timestamp(ss: 'const SpanSet *', t: int) -> 'double': ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.spanset_union_transfn(state_converted, ss_converted) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.distance_periodset_timestamp(ss_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def text_union_transfn(state: 'Set *', txt: str) -> 'Set *': - state_converted = _ffi.cast('Set *', state) - txt_converted = cstring2text(txt) - result = _lib.text_union_transfn(state_converted, txt_converted) +def distance_set_set(s1: 'const Set *', s2: 'const Set *') -> 'double': + s1_converted = _ffi.cast('const Set *', s1) + s2_converted = _ffi.cast('const Set *', s2) + result = _lib.distance_set_set(s1_converted, s2_converted) _check_error() return result if result != _ffi.NULL else None -def timestamp_extent_transfn(p: "Optional['Span *']", t: int) -> 'Span *': - p_converted = _ffi.cast('Span *', p) if p is not None else _ffi.NULL - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.timestamp_extent_transfn(p_converted, t_converted) +def distance_span_span(s1: 'const Span *', s2: 'const Span *') -> 'double': + s1_converted = _ffi.cast('const Span *', s1) + s2_converted = _ffi.cast('const Span *', s2) + result = _lib.distance_span_span(s1_converted, s2_converted) _check_error() return result if result != _ffi.NULL else None -def timestamp_tcount_transfn(state: "Optional['SkipList *']", t: int) -> 'SkipList *': - state_converted = _ffi.cast('SkipList *', state) if state is not None else _ffi.NULL - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.timestamp_tcount_transfn(state_converted, t_converted) +def distance_spanset_span(ss: 'const SpanSet *', s: 'const Span *') -> 'double': + ss_converted = _ffi.cast('const SpanSet *', ss) + s_converted = _ffi.cast('const Span *', s) + result = _lib.distance_spanset_span(ss_converted, s_converted) _check_error() return result if result != _ffi.NULL else None -def timestamp_union_transfn(state: 'Set *', t: int) -> 'Set *': - state_converted = _ffi.cast('Set *', state) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.timestamp_union_transfn(state_converted, t_converted) +def distance_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'double': + ss1_converted = _ffi.cast('const SpanSet *', ss1) + ss2_converted = _ffi.cast('const SpanSet *', ss2) + result = _lib.distance_spanset_spanset(ss1_converted, ss2_converted) _check_error() return result if result != _ffi.NULL else None -def timestampset_tcount_transfn(state: "Optional['SkipList *']", ts: 'const Set *') -> 'SkipList *': - state_converted = _ffi.cast('SkipList *', state) if state is not None else _ffi.NULL - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.timestampset_tcount_transfn(state_converted, ts_converted) +def distance_timestampset_timestamp(s: 'const Set *', t: int) -> 'double': + s_converted = _ffi.cast('const Set *', s) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.distance_timestampset_timestamp(s_converted, t_converted) _check_error() return result if result != _ffi.NULL else None @@ -3447,6 +4031,168 @@ def spanset_ne(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'bool': return result if result != _ffi.NULL else None +def bigint_extent_transfn(s: 'Span *', i: int) -> 'Span *': + s_converted = _ffi.cast('Span *', s) + i_converted = _ffi.cast('int64', i) + result = _lib.bigint_extent_transfn(s_converted, i_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def bigint_union_transfn(state: 'Set *', i: int) -> 'Set *': + state_converted = _ffi.cast('Set *', state) + i_converted = _ffi.cast('int64', i) + result = _lib.bigint_union_transfn(state_converted, i_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def float_extent_transfn(s: 'Span *', d: float) -> 'Span *': + s_converted = _ffi.cast('Span *', s) + result = _lib.float_extent_transfn(s_converted, d) + _check_error() + return result if result != _ffi.NULL else None + + +def float_union_transfn(state: 'Set *', d: float) -> 'Set *': + state_converted = _ffi.cast('Set *', state) + result = _lib.float_union_transfn(state_converted, d) + _check_error() + return result if result != _ffi.NULL else None + + +def int_extent_transfn(s: 'Span *', i: int) -> 'Span *': + s_converted = _ffi.cast('Span *', s) + result = _lib.int_extent_transfn(s_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def int_union_transfn(state: 'Set *', i: int) -> 'Set *': + state_converted = _ffi.cast('Set *', state) + result = _lib.int_union_transfn(state_converted, i) + _check_error() + return result if result != _ffi.NULL else None + + +def period_tcount_transfn(state: "Optional['SkipList *']", p: 'const Span *') -> 'SkipList *': + state_converted = _ffi.cast('SkipList *', state) if state is not None else _ffi.NULL + p_converted = _ffi.cast('const Span *', p) + result = _lib.period_tcount_transfn(state_converted, p_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def periodset_tcount_transfn(state: "Optional['SkipList *']", ps: 'const SpanSet *') -> 'SkipList *': + state_converted = _ffi.cast('SkipList *', state) if state is not None else _ffi.NULL + ps_converted = _ffi.cast('const SpanSet *', ps) + result = _lib.periodset_tcount_transfn(state_converted, ps_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def set_extent_transfn(span: 'Span *', set: 'const Set *') -> 'Span *': + span_converted = _ffi.cast('Span *', span) + set_converted = _ffi.cast('const Set *', set) + result = _lib.set_extent_transfn(span_converted, set_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def set_union_finalfn(state: 'Set *') -> 'Set *': + state_converted = _ffi.cast('Set *', state) + result = _lib.set_union_finalfn(state_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def set_union_transfn(state: 'Set *', set: 'Set *') -> 'Set *': + state_converted = _ffi.cast('Set *', state) + set_converted = _ffi.cast('Set *', set) + result = _lib.set_union_transfn(state_converted, set_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def span_extent_transfn(s1: 'Span *', s2: 'const Span *') -> 'Span *': + s1_converted = _ffi.cast('Span *', s1) + s2_converted = _ffi.cast('const Span *', s2) + result = _lib.span_extent_transfn(s1_converted, s2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def span_union_transfn(state: 'SpanSet *', span: 'const Span *') -> 'SpanSet *': + state_converted = _ffi.cast('SpanSet *', state) + span_converted = _ffi.cast('const Span *', span) + result = _lib.span_union_transfn(state_converted, span_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def spanset_extent_transfn(s: 'Span *', ss: 'const SpanSet *') -> 'Span *': + s_converted = _ffi.cast('Span *', s) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.spanset_extent_transfn(s_converted, ss_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def spanset_union_finalfn(state: 'SpanSet *') -> 'SpanSet *': + state_converted = _ffi.cast('SpanSet *', state) + result = _lib.spanset_union_finalfn(state_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def spanset_union_transfn(state: 'SpanSet *', ss: 'const SpanSet *') -> 'SpanSet *': + state_converted = _ffi.cast('SpanSet *', state) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.spanset_union_transfn(state_converted, ss_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def text_union_transfn(state: 'Set *', txt: str) -> 'Set *': + state_converted = _ffi.cast('Set *', state) + txt_converted = cstring2text(txt) + result = _lib.text_union_transfn(state_converted, txt_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def timestamp_extent_transfn(p: "Optional['Span *']", t: int) -> 'Span *': + p_converted = _ffi.cast('Span *', p) if p is not None else _ffi.NULL + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.timestamp_extent_transfn(p_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def timestamp_tcount_transfn(state: "Optional['SkipList *']", t: int) -> 'SkipList *': + state_converted = _ffi.cast('SkipList *', state) if state is not None else _ffi.NULL + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.timestamp_tcount_transfn(state_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def timestamp_union_transfn(state: 'Set *', t: int) -> 'Set *': + state_converted = _ffi.cast('Set *', state) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.timestamp_union_transfn(state_converted, t_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def timestampset_tcount_transfn(state: "Optional['SkipList *']", ts: 'const Set *') -> 'SkipList *': + state_converted = _ffi.cast('SkipList *', state) if state is not None else _ffi.NULL + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.timestampset_tcount_transfn(state_converted, ts_converted) + _check_error() + return result if result != _ffi.NULL else None + + def tbox_in(string: str) -> 'TBox *': string_converted = string.encode('utf-8') result = _lib.tbox_in(string_converted) @@ -3543,123 +4289,118 @@ def stbox_out(box: 'const STBox *', maxdd: int) -> str: return result if result != _ffi.NULL else None -def stbox_make(hasx: bool, hasz: bool, geodetic: bool, srid: int, xmin: float, xmax: float, ymin: float, ymax: float, zmin: float, zmax: float, p: "Optional['const Span *']") -> 'STBox *': - srid_converted = _ffi.cast('int32', srid) - p_converted = _ffi.cast('const Span *', p) if p is not None else _ffi.NULL - result = _lib.stbox_make(hasx, hasz, geodetic, srid_converted, xmin, xmax, ymin, ymax, zmin, zmax, p_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def stbox_copy(box: 'const STBox *') -> 'STBox *': - box_converted = _ffi.cast('const STBox *', box) - result = _lib.stbox_copy(box_converted) +def float_period_to_tbox(d: float, p: 'const Span *') -> 'TBox *': + p_converted = _ffi.cast('const Span *', p) + result = _lib.float_period_to_tbox(d, p_converted) _check_error() return result if result != _ffi.NULL else None -def tbox_make(s: "Optional['const Span *']", p: "Optional['const Span *']") -> 'TBox *': - s_converted = _ffi.cast('const Span *', s) if s is not None else _ffi.NULL - p_converted = _ffi.cast('const Span *', p) if p is not None else _ffi.NULL - result = _lib.tbox_make(s_converted, p_converted) +def float_timestamp_to_tbox(d: float, t: int) -> 'TBox *': + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.float_timestamp_to_tbox(d, t_converted) _check_error() return result if result != _ffi.NULL else None -def tbox_copy(box: 'const TBox *') -> 'TBox *': - box_converted = _ffi.cast('const TBox *', box) - result = _lib.tbox_copy(box_converted) +def geo_period_to_stbox(gs: 'const GSERIALIZED *', p: 'const Span *') -> 'STBox *': + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + p_converted = _ffi.cast('const Span *', p) + result = _lib.geo_period_to_stbox(gs_converted, p_converted) _check_error() return result if result != _ffi.NULL else None -def int_to_tbox(i: int) -> 'TBox *': - result = _lib.int_to_tbox(i) +def geo_timestamp_to_stbox(gs: 'const GSERIALIZED *', t: int) -> 'STBox *': + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.geo_timestamp_to_stbox(gs_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def float_to_tbox(d: float) -> 'TBox *': - result = _lib.float_to_tbox(d) +def int_period_to_tbox(i: int, p: 'const Span *') -> 'TBox *': + p_converted = _ffi.cast('const Span *', p) + result = _lib.int_period_to_tbox(i, p_converted) _check_error() return result if result != _ffi.NULL else None -def timestamp_to_tbox(t: int) -> 'TBox *': +def int_timestamp_to_tbox(i: int, t: int) -> 'TBox *': t_converted = _ffi.cast('TimestampTz', t) - result = _lib.timestamp_to_tbox(t_converted) + result = _lib.int_timestamp_to_tbox(i, t_converted) _check_error() return result if result != _ffi.NULL else None -def timestampset_to_tbox(ss: 'const Set *') -> 'TBox *': - ss_converted = _ffi.cast('const Set *', ss) - result = _lib.timestampset_to_tbox(ss_converted) +def span_period_to_tbox(span: 'const Span *', p: 'const Span *') -> 'TBox *': + span_converted = _ffi.cast('const Span *', span) + p_converted = _ffi.cast('const Span *', p) + result = _lib.span_period_to_tbox(span_converted, p_converted) _check_error() return result if result != _ffi.NULL else None -def period_to_tbox(p: 'const Span *') -> 'TBox *': - p_converted = _ffi.cast('const Span *', p) - result = _lib.period_to_tbox(p_converted) +def span_timestamp_to_tbox(span: 'const Span *', t: int) -> 'TBox *': + span_converted = _ffi.cast('const Span *', span) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.span_timestamp_to_tbox(span_converted, t_converted) _check_error() return result if result != _ffi.NULL else None -def periodset_to_tbox(ps: 'const SpanSet *') -> 'TBox *': - ps_converted = _ffi.cast('const SpanSet *', ps) - result = _lib.periodset_to_tbox(ps_converted) +def stbox_copy(box: 'const STBox *') -> 'STBox *': + box_converted = _ffi.cast('const STBox *', box) + result = _lib.stbox_copy(box_converted) _check_error() return result if result != _ffi.NULL else None -def int_timestamp_to_tbox(i: int, t: int) -> 'TBox *': - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.int_timestamp_to_tbox(i, t_converted) +def stbox_make(hasx: bool, hasz: bool, geodetic: bool, srid: int, xmin: float, xmax: float, ymin: float, ymax: float, zmin: float, zmax: float, p: "Optional['const Span *']") -> 'STBox *': + srid_converted = _ffi.cast('int32', srid) + p_converted = _ffi.cast('const Span *', p) if p is not None else _ffi.NULL + result = _lib.stbox_make(hasx, hasz, geodetic, srid_converted, xmin, xmax, ymin, ymax, zmin, zmax, p_converted) _check_error() return result if result != _ffi.NULL else None -def float_period_to_tbox(d: float, p: 'const Span *') -> 'TBox *': - p_converted = _ffi.cast('const Span *', p) - result = _lib.float_period_to_tbox(d, p_converted) +def tbox_copy(box: 'const TBox *') -> 'TBox *': + box_converted = _ffi.cast('const TBox *', box) + result = _lib.tbox_copy(box_converted) _check_error() return result if result != _ffi.NULL else None -def float_timestamp_to_tbox(d: float, t: int) -> 'TBox *': - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.float_timestamp_to_tbox(d, t_converted) +def tbox_make(s: "Optional['const Span *']", p: "Optional['const Span *']") -> 'TBox *': + s_converted = _ffi.cast('const Span *', s) if s is not None else _ffi.NULL + p_converted = _ffi.cast('const Span *', p) if p is not None else _ffi.NULL + result = _lib.tbox_make(s_converted, p_converted) _check_error() return result if result != _ffi.NULL else None -def geo_period_to_stbox(gs: 'const GSERIALIZED *', p: 'const Span *') -> 'STBox *': - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - p_converted = _ffi.cast('const Span *', p) - result = _lib.geo_period_to_stbox(gs_converted, p_converted) +def float_to_tbox(d: float) -> 'TBox *': + result = _lib.float_to_tbox(d) _check_error() return result if result != _ffi.NULL else None -def geo_timestamp_to_stbox(gs: 'const GSERIALIZED *', t: int) -> 'STBox *': +def geo_to_stbox(gs: 'const GSERIALIZED *') -> 'STBox *': gs_converted = _ffi.cast('const GSERIALIZED *', gs) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.geo_timestamp_to_stbox(gs_converted, t_converted) + result = _lib.geo_to_stbox(gs_converted) _check_error() return result if result != _ffi.NULL else None -def geo_to_stbox(gs: 'const GSERIALIZED *') -> 'STBox *': - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - result = _lib.geo_to_stbox(gs_converted) +def int_to_tbox(i: int) -> 'TBox *': + result = _lib.int_to_tbox(i) _check_error() return result if result != _ffi.NULL else None -def int_period_to_tbox(i: int, p: 'const Span *') -> 'TBox *': - p_converted = _ffi.cast('const Span *', p) - result = _lib.int_period_to_tbox(i, p_converted) +def numset_to_tbox(s: 'const Set *') -> 'TBox *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.numset_to_tbox(s_converted) _check_error() return result if result != _ffi.NULL else None @@ -3671,212 +4412,194 @@ def numspan_to_tbox(s: 'const Span *') -> 'TBox *': return result if result != _ffi.NULL else None -def span_timestamp_to_tbox(span: 'const Span *', t: int) -> 'TBox *': - span_converted = _ffi.cast('const Span *', span) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.span_timestamp_to_tbox(span_converted, t_converted) +def numspanset_to_tbox(ss: 'const SpanSet *') -> 'TBox *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.numspanset_to_tbox(ss_converted) _check_error() return result if result != _ffi.NULL else None -def span_period_to_tbox(span: 'const Span *', p: 'const Span *') -> 'TBox *': - span_converted = _ffi.cast('const Span *', span) +def period_to_stbox(p: 'const Span *') -> 'STBox *': p_converted = _ffi.cast('const Span *', p) - result = _lib.span_period_to_tbox(span_converted, p_converted) + result = _lib.period_to_stbox(p_converted) _check_error() return result if result != _ffi.NULL else None -def tbox_to_floatspan(box: 'const TBox *') -> 'Span *': - box_converted = _ffi.cast('const TBox *', box) - result = _lib.tbox_to_floatspan(box_converted) +def period_to_tbox(p: 'const Span *') -> 'TBox *': + p_converted = _ffi.cast('const Span *', p) + result = _lib.period_to_tbox(p_converted) _check_error() return result if result != _ffi.NULL else None -def tbox_to_period(box: 'const TBox *') -> 'Span *': - box_converted = _ffi.cast('const TBox *', box) - result = _lib.tbox_to_period(box_converted) +def periodset_to_stbox(ps: 'const SpanSet *') -> 'STBox *': + ps_converted = _ffi.cast('const SpanSet *', ps) + result = _lib.periodset_to_stbox(ps_converted) _check_error() return result if result != _ffi.NULL else None -def stbox_to_period(box: 'const STBox *') -> 'Span *': - box_converted = _ffi.cast('const STBox *', box) - result = _lib.stbox_to_period(box_converted) +def periodset_to_tbox(ps: 'const SpanSet *') -> 'TBox *': + ps_converted = _ffi.cast('const SpanSet *', ps) + result = _lib.periodset_to_tbox(ps_converted) _check_error() return result if result != _ffi.NULL else None -def tnumber_to_tbox(temp: 'const Temporal *') -> 'TBox *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tnumber_to_tbox(temp_converted) +def stbox_to_geo(box: 'const STBox *') -> 'GSERIALIZED *': + box_converted = _ffi.cast('const STBox *', box) + result = _lib.stbox_to_geo(box_converted) _check_error() return result if result != _ffi.NULL else None -def stbox_to_geo(box: 'const STBox *') -> 'GSERIALIZED *': +def stbox_to_period(box: 'const STBox *') -> 'Span *': box_converted = _ffi.cast('const STBox *', box) - result = _lib.stbox_to_geo(box_converted) + result = _lib.stbox_to_period(box_converted) _check_error() return result if result != _ffi.NULL else None -def tpoint_to_stbox(temp: 'const Temporal *') -> 'STBox *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tpoint_to_stbox(temp_converted) +def tbox_to_floatspan(box: 'const TBox *') -> 'Span *': + box_converted = _ffi.cast('const TBox *', box) + result = _lib.tbox_to_floatspan(box_converted) _check_error() return result if result != _ffi.NULL else None -def timestamp_to_stbox(t: int) -> 'STBox *': - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.timestamp_to_stbox(t_converted) +def tbox_to_period(box: 'const TBox *') -> 'Span *': + box_converted = _ffi.cast('const TBox *', box) + result = _lib.tbox_to_period(box_converted) _check_error() return result if result != _ffi.NULL else None -def timestampset_to_stbox(ts: 'const Set *') -> 'STBox *': - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.timestampset_to_stbox(ts_converted) +def timestamp_to_stbox(t: int) -> 'STBox *': + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.timestamp_to_stbox(t_converted) _check_error() return result if result != _ffi.NULL else None -def period_to_stbox(p: 'const Span *') -> 'STBox *': - p_converted = _ffi.cast('const Span *', p) - result = _lib.period_to_stbox(p_converted) +def timestamp_to_tbox(t: int) -> 'TBox *': + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.timestamp_to_tbox(t_converted) _check_error() return result if result != _ffi.NULL else None -def periodset_to_stbox(ps: 'const SpanSet *') -> 'STBox *': - ps_converted = _ffi.cast('const SpanSet *', ps) - result = _lib.periodset_to_stbox(ps_converted) +def timestampset_to_stbox(ts: 'const Set *') -> 'STBox *': + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.timestampset_to_stbox(ts_converted) _check_error() return result if result != _ffi.NULL else None -def tbox_hasx(box: 'const TBox *') -> 'bool': - box_converted = _ffi.cast('const TBox *', box) - result = _lib.tbox_hasx(box_converted) +def timestampset_to_tbox(ss: 'const Set *') -> 'TBox *': + ss_converted = _ffi.cast('const Set *', ss) + result = _lib.timestampset_to_tbox(ss_converted) _check_error() return result if result != _ffi.NULL else None -def tbox_hast(box: 'const TBox *') -> 'bool': - box_converted = _ffi.cast('const TBox *', box) - result = _lib.tbox_hast(box_converted) +def tnumber_to_tbox(temp: 'const Temporal *') -> 'TBox *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tnumber_to_tbox(temp_converted) _check_error() return result if result != _ffi.NULL else None -def tbox_xmin(box: 'const TBox *') -> 'double': - box_converted = _ffi.cast('const TBox *', box) - out_result = _ffi.new('double *') - result = _lib.tbox_xmin(box_converted, out_result) +def tpoint_to_stbox(temp: 'const Temporal *') -> 'STBox *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tpoint_to_stbox(temp_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def tbox_xmin_inc(box: 'const TBox *') -> 'bool': - box_converted = _ffi.cast('const TBox *', box) - out_result = _ffi.new('bool *') - result = _lib.tbox_xmin_inc(box_converted, out_result) +def stbox_hast(box: 'const STBox *') -> 'bool': + box_converted = _ffi.cast('const STBox *', box) + result = _lib.stbox_hast(box_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def tbox_xmax(box: 'const TBox *') -> 'double': - box_converted = _ffi.cast('const TBox *', box) - out_result = _ffi.new('double *') - result = _lib.tbox_xmax(box_converted, out_result) +def stbox_hasx(box: 'const STBox *') -> 'bool': + box_converted = _ffi.cast('const STBox *', box) + result = _lib.stbox_hasx(box_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def tbox_xmax_inc(box: 'const TBox *') -> 'bool': - box_converted = _ffi.cast('const TBox *', box) - out_result = _ffi.new('bool *') - result = _lib.tbox_xmax_inc(box_converted, out_result) +def stbox_hasz(box: 'const STBox *') -> 'bool': + box_converted = _ffi.cast('const STBox *', box) + result = _lib.stbox_hasz(box_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def tbox_tmin(box: 'const TBox *') -> int: - box_converted = _ffi.cast('const TBox *', box) - out_result = _ffi.new('TimestampTz *') - result = _lib.tbox_tmin(box_converted, out_result) +def stbox_isgeodetic(box: 'const STBox *') -> 'bool': + box_converted = _ffi.cast('const STBox *', box) + result = _lib.stbox_isgeodetic(box_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def tbox_tmin_inc(box: 'const TBox *') -> 'bool': - box_converted = _ffi.cast('const TBox *', box) - out_result = _ffi.new('bool *') - result = _lib.tbox_tmin_inc(box_converted, out_result) +def stbox_srid(box: 'const STBox *') -> 'int32': + box_converted = _ffi.cast('const STBox *', box) + result = _lib.stbox_srid(box_converted) _check_error() - if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def tbox_tmax(box: 'const TBox *') -> int: - box_converted = _ffi.cast('const TBox *', box) +def stbox_tmax(box: 'const STBox *') -> int: + box_converted = _ffi.cast('const STBox *', box) out_result = _ffi.new('TimestampTz *') - result = _lib.tbox_tmax(box_converted, out_result) + result = _lib.stbox_tmax(box_converted, out_result) _check_error() if result: return out_result[0] if out_result[0] != _ffi.NULL else None return None -def tbox_tmax_inc(box: 'const TBox *') -> 'bool': - box_converted = _ffi.cast('const TBox *', box) +def stbox_tmax_inc(box: 'const STBox *') -> 'bool': + box_converted = _ffi.cast('const STBox *', box) out_result = _ffi.new('bool *') - result = _lib.tbox_tmax_inc(box_converted, out_result) + result = _lib.stbox_tmax_inc(box_converted, out_result) _check_error() if result: - return out_result[0] if out_result[0] != _ffi.NULL else None - return None - - -def stbox_hasx(box: 'const STBox *') -> 'bool': - box_converted = _ffi.cast('const STBox *', box) - result = _lib.stbox_hasx(box_converted) - _check_error() - return result if result != _ffi.NULL else None + return out_result[0] if out_result[0] != _ffi.NULL else None + return None -def stbox_hasz(box: 'const STBox *') -> 'bool': +def stbox_tmin(box: 'const STBox *') -> int: box_converted = _ffi.cast('const STBox *', box) - result = _lib.stbox_hasz(box_converted) + out_result = _ffi.new('TimestampTz *') + result = _lib.stbox_tmin(box_converted, out_result) _check_error() - return result if result != _ffi.NULL else None + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None -def stbox_hast(box: 'const STBox *') -> 'bool': +def stbox_tmin_inc(box: 'const STBox *') -> 'bool': box_converted = _ffi.cast('const STBox *', box) - result = _lib.stbox_hast(box_converted) + out_result = _ffi.new('bool *') + result = _lib.stbox_tmin_inc(box_converted, out_result) _check_error() - return result if result != _ffi.NULL else None + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None -def stbox_isgeodetic(box: 'const STBox *') -> 'bool': +def stbox_xmax(box: 'const STBox *') -> 'double': box_converted = _ffi.cast('const STBox *', box) - result = _lib.stbox_isgeodetic(box_converted) + out_result = _ffi.new('double *') + result = _lib.stbox_xmax(box_converted, out_result) _check_error() - return result if result != _ffi.NULL else None + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None def stbox_xmin(box: 'const STBox *') -> 'double': @@ -3889,10 +4612,10 @@ def stbox_xmin(box: 'const STBox *') -> 'double': return None -def stbox_xmax(box: 'const STBox *') -> 'double': +def stbox_ymax(box: 'const STBox *') -> 'double': box_converted = _ffi.cast('const STBox *', box) out_result = _ffi.new('double *') - result = _lib.stbox_xmax(box_converted, out_result) + result = _lib.stbox_ymax(box_converted, out_result) _check_error() if result: return out_result[0] if out_result[0] != _ffi.NULL else None @@ -3909,10 +4632,10 @@ def stbox_ymin(box: 'const STBox *') -> 'double': return None -def stbox_ymax(box: 'const STBox *') -> 'double': +def stbox_zmax(box: 'const STBox *') -> 'double': box_converted = _ffi.cast('const STBox *', box) out_result = _ffi.new('double *') - result = _lib.stbox_ymax(box_converted, out_result) + result = _lib.stbox_zmax(box_converted, out_result) _check_error() if result: return out_result[0] if out_result[0] != _ffi.NULL else None @@ -3929,61 +4652,98 @@ def stbox_zmin(box: 'const STBox *') -> 'double': return None -def stbox_zmax(box: 'const STBox *') -> 'double': - box_converted = _ffi.cast('const STBox *', box) - out_result = _ffi.new('double *') - result = _lib.stbox_zmax(box_converted, out_result) +def tbox_hast(box: 'const TBox *') -> 'bool': + box_converted = _ffi.cast('const TBox *', box) + result = _lib.tbox_hast(box_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def tbox_hasx(box: 'const TBox *') -> 'bool': + box_converted = _ffi.cast('const TBox *', box) + result = _lib.tbox_hasx(box_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def tbox_tmax(box: 'const TBox *') -> int: + box_converted = _ffi.cast('const TBox *', box) + out_result = _ffi.new('TimestampTz *') + result = _lib.tbox_tmax(box_converted, out_result) _check_error() if result: return out_result[0] if out_result[0] != _ffi.NULL else None return None -def stbox_tmin(box: 'const STBox *') -> int: - box_converted = _ffi.cast('const STBox *', box) +def tbox_tmax_inc(box: 'const TBox *') -> 'bool': + box_converted = _ffi.cast('const TBox *', box) + out_result = _ffi.new('bool *') + result = _lib.tbox_tmax_inc(box_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def tbox_tmin(box: 'const TBox *') -> int: + box_converted = _ffi.cast('const TBox *', box) out_result = _ffi.new('TimestampTz *') - result = _lib.stbox_tmin(box_converted, out_result) + result = _lib.tbox_tmin(box_converted, out_result) _check_error() if result: return out_result[0] if out_result[0] != _ffi.NULL else None return None -def stbox_tmin_inc(box: 'const STBox *') -> 'bool': - box_converted = _ffi.cast('const STBox *', box) +def tbox_tmin_inc(box: 'const TBox *') -> 'bool': + box_converted = _ffi.cast('const TBox *', box) out_result = _ffi.new('bool *') - result = _lib.stbox_tmin_inc(box_converted, out_result) + result = _lib.tbox_tmin_inc(box_converted, out_result) _check_error() if result: return out_result[0] if out_result[0] != _ffi.NULL else None return None -def stbox_tmax(box: 'const STBox *') -> int: - box_converted = _ffi.cast('const STBox *', box) - out_result = _ffi.new('TimestampTz *') - result = _lib.stbox_tmax(box_converted, out_result) +def tbox_xmax(box: 'const TBox *') -> 'double': + box_converted = _ffi.cast('const TBox *', box) + out_result = _ffi.new('double *') + result = _lib.tbox_xmax(box_converted, out_result) _check_error() if result: return out_result[0] if out_result[0] != _ffi.NULL else None return None -def stbox_tmax_inc(box: 'const STBox *') -> 'bool': - box_converted = _ffi.cast('const STBox *', box) +def tbox_xmax_inc(box: 'const TBox *') -> 'bool': + box_converted = _ffi.cast('const TBox *', box) out_result = _ffi.new('bool *') - result = _lib.stbox_tmax_inc(box_converted, out_result) + result = _lib.tbox_xmax_inc(box_converted, out_result) _check_error() if result: return out_result[0] if out_result[0] != _ffi.NULL else None return None -def stbox_srid(box: 'const STBox *') -> 'int32': - box_converted = _ffi.cast('const STBox *', box) - result = _lib.stbox_srid(box_converted) +def tbox_xmin(box: 'const TBox *') -> 'double': + box_converted = _ffi.cast('const TBox *', box) + out_result = _ffi.new('double *') + result = _lib.tbox_xmin(box_converted, out_result) _check_error() - return result if result != _ffi.NULL else None + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None + + +def tbox_xmin_inc(box: 'const TBox *') -> 'bool': + box_converted = _ffi.cast('const TBox *', box) + out_result = _ffi.new('bool *') + result = _lib.tbox_xmin_inc(box_converted, out_result) + _check_error() + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None def stbox_expand_space(box: 'const STBox *', d: float) -> 'STBox *': @@ -4023,10 +4783,11 @@ def stbox_set_srid(box: 'const STBox *', srid: int) -> 'STBox *': return result if result != _ffi.NULL else None -def tbox_expand_value(box: 'const TBox *', d: 'const double') -> 'TBox *': - box_converted = _ffi.cast('const TBox *', box) - d_converted = _ffi.cast('const double', d) - result = _lib.tbox_expand_value(box_converted, d_converted) +def stbox_shift_scale_time(box: 'const STBox *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'STBox *': + box_converted = _ffi.cast('const STBox *', box) + shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL + duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL + result = _lib.stbox_shift_scale_time(box_converted, shift_converted, duration_converted) _check_error() return result if result != _ffi.NULL else None @@ -4039,6 +4800,14 @@ def tbox_expand_time(box: 'const TBox *', interval: 'const Interval *') -> 'TBox return result if result != _ffi.NULL else None +def tbox_expand_value(box: 'const TBox *', d: 'const double') -> 'TBox *': + box_converted = _ffi.cast('const TBox *', box) + d_converted = _ffi.cast('const double', d) + result = _lib.tbox_expand_value(box_converted, d_converted) + _check_error() + return result if result != _ffi.NULL else None + + def tbox_round(box: 'const TBox *', maxdd: int) -> 'TBox *': box_converted = _ffi.cast('const TBox *', box) result = _lib.tbox_round(box_converted, maxdd) @@ -4046,6 +4815,83 @@ def tbox_round(box: 'const TBox *', maxdd: int) -> 'TBox *': return result if result != _ffi.NULL else None +def tbox_shift_scale_float(box: 'const TBox *', shift: float, width: float, hasshift: bool, haswidth: bool) -> 'TBox *': + box_converted = _ffi.cast('const TBox *', box) + result = _lib.tbox_shift_scale_float(box_converted, shift, width, hasshift, haswidth) + _check_error() + return result if result != _ffi.NULL else None + + +def tbox_shift_scale_int(box: 'const TBox *', shift: int, width: int, hasshift: bool, haswidth: bool) -> 'TBox *': + box_converted = _ffi.cast('const TBox *', box) + result = _lib.tbox_shift_scale_int(box_converted, shift, width, hasshift, haswidth) + _check_error() + return result if result != _ffi.NULL else None + + +def tbox_shift_scale_time(box: 'const TBox *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'TBox *': + box_converted = _ffi.cast('const TBox *', box) + shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL + duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL + result = _lib.tbox_shift_scale_time(box_converted, shift_converted, duration_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *', strict: bool) -> 'TBox *': + box1_converted = _ffi.cast('const TBox *', box1) + box2_converted = _ffi.cast('const TBox *', box2) + result = _lib.union_tbox_tbox(box1_converted, box2_converted, strict) + _check_error() + return result if result != _ffi.NULL else None + + +def inter_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *') -> 'TBox *': + box1_converted = _ffi.cast('const TBox *', box1) + box2_converted = _ffi.cast('const TBox *', box2) + out_result = _ffi.new('TBox *') + result = _lib.inter_tbox_tbox(box1_converted, box2_converted, out_result) + _check_error() + if result: + return out_result if out_result != _ffi.NULL else None + return None + + +def intersection_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *') -> 'TBox *': + box1_converted = _ffi.cast('const TBox *', box1) + box2_converted = _ffi.cast('const TBox *', box2) + result = _lib.intersection_tbox_tbox(box1_converted, box2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def union_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *', strict: bool) -> 'STBox *': + box1_converted = _ffi.cast('const STBox *', box1) + box2_converted = _ffi.cast('const STBox *', box2) + result = _lib.union_stbox_stbox(box1_converted, box2_converted, strict) + _check_error() + return result if result != _ffi.NULL else None + + +def inter_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'STBox *': + box1_converted = _ffi.cast('const STBox *', box1) + box2_converted = _ffi.cast('const STBox *', box2) + out_result = _ffi.new('STBox *') + result = _lib.inter_stbox_stbox(box1_converted, box2_converted, out_result) + _check_error() + if result: + return out_result if out_result != _ffi.NULL else None + return None + + +def intersection_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'STBox *': + box1_converted = _ffi.cast('const STBox *', box1) + box2_converted = _ffi.cast('const STBox *', box2) + result = _lib.intersection_stbox_stbox(box1_converted, box2_converted) + _check_error() + return result if result != _ffi.NULL else None + + def contains_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *') -> 'bool': box1_converted = _ffi.cast('const TBox *', box1) box2_converted = _ffi.cast('const TBox *', box2) @@ -4304,70 +5150,16 @@ def overbefore_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'boo def after_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'bool': box1_converted = _ffi.cast('const STBox *', box1) - box2_converted = _ffi.cast('const STBox *', box2) - result = _lib.after_stbox_stbox(box1_converted, box2_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def overafter_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'bool': - box1_converted = _ffi.cast('const STBox *', box1) - box2_converted = _ffi.cast('const STBox *', box2) - result = _lib.overafter_stbox_stbox(box1_converted, box2_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def union_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *', strict: bool) -> 'TBox *': - box1_converted = _ffi.cast('const TBox *', box1) - box2_converted = _ffi.cast('const TBox *', box2) - result = _lib.union_tbox_tbox(box1_converted, box2_converted, strict) - _check_error() - return result if result != _ffi.NULL else None - - -def inter_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *') -> 'TBox *': - box1_converted = _ffi.cast('const TBox *', box1) - box2_converted = _ffi.cast('const TBox *', box2) - out_result = _ffi.new('TBox *') - result = _lib.inter_tbox_tbox(box1_converted, box2_converted, out_result) - _check_error() - if result: - return out_result if out_result != _ffi.NULL else None - return None - - -def intersection_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *') -> 'TBox *': - box1_converted = _ffi.cast('const TBox *', box1) - box2_converted = _ffi.cast('const TBox *', box2) - result = _lib.intersection_tbox_tbox(box1_converted, box2_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def union_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *', strict: bool) -> 'STBox *': - box1_converted = _ffi.cast('const STBox *', box1) - box2_converted = _ffi.cast('const STBox *', box2) - result = _lib.union_stbox_stbox(box1_converted, box2_converted, strict) - _check_error() - return result if result != _ffi.NULL else None - - -def inter_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'STBox *': - box1_converted = _ffi.cast('const STBox *', box1) - box2_converted = _ffi.cast('const STBox *', box2) - out_result = _ffi.new('STBox *') - result = _lib.inter_stbox_stbox(box1_converted, box2_converted, out_result) + box2_converted = _ffi.cast('const STBox *', box2) + result = _lib.after_stbox_stbox(box1_converted, box2_converted) _check_error() - if result: - return out_result if out_result != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def intersection_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'STBox *': +def overafter_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'bool': box1_converted = _ffi.cast('const STBox *', box1) box2_converted = _ffi.cast('const STBox *', box2) - result = _lib.intersection_stbox_stbox(box1_converted, box2_converted) + result = _lib.overafter_stbox_stbox(box1_converted, box2_converted) _check_error() return result if result != _ffi.NULL else None @@ -4803,14 +5595,6 @@ def tsequence_make(instants: 'const TInstant **', count: int, lower_inc: bool, u return result if result != _ffi.NULL else None -def tsequence_make_exp(instants: 'const TInstant **', count: int, maxcount: int, lower_inc: bool, upper_inc: bool, interp: 'interpType', normalize: bool) -> 'TSequence *': - instants_converted = [_ffi.cast('const TInstant *', x) for x in instants] - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tsequence_make_exp(instants_converted, count, maxcount, lower_inc, upper_inc, interp_converted, normalize) - _check_error() - return result if result != _ffi.NULL else None - - def tsequenceset_make(sequences: 'const TSequence **', count: int, normalize: bool) -> 'TSequenceSet *': sequences_converted = [_ffi.cast('const TSequence *', x) for x in sequences] result = _lib.tsequenceset_make(sequences_converted, count, normalize) @@ -4818,13 +5602,6 @@ def tsequenceset_make(sequences: 'const TSequence **', count: int, normalize: bo return result if result != _ffi.NULL else None -def tsequenceset_make_exp(sequences: 'const TSequence **', count: int, maxcount: int, normalize: bool) -> 'TSequenceSet *': - sequences_converted = [_ffi.cast('const TSequence *', x) for x in sequences] - result = _lib.tsequenceset_make_exp(sequences_converted, count, maxcount, normalize) - _check_error() - return result if result != _ffi.NULL else None - - def tsequenceset_make_gaps(instants: 'const TInstant **', count: int, interp: 'interpType', maxt: 'Interval *', maxdist: float) -> 'TSequenceSet *': instants_converted = [_ffi.cast('const TInstant *', x) for x in instants] interp_converted = _ffi.cast('interpType', interp) @@ -5243,6 +6020,14 @@ def ttext_values(temp: 'const Temporal *') -> "Tuple['text **', 'int']": return result if result != _ffi.NULL else None, count[0] +def temporal_scale_time(temp: 'const Temporal *', duration: 'const Interval *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + duration_converted = _ffi.cast('const Interval *', duration) + result = _lib.temporal_scale_time(temp_converted, duration_converted) + _check_error() + return result if result != _ffi.NULL else None + + def temporal_set_interp(temp: 'const Temporal *', interp: 'interpType') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) interp_converted = _ffi.cast('interpType', interp) @@ -5251,19 +6036,19 @@ def temporal_set_interp(temp: 'const Temporal *', interp: 'interpType') -> 'Temp return result if result != _ffi.NULL else None -def temporal_shift(temp: 'const Temporal *', shift: 'const Interval *') -> 'Temporal *': +def temporal_shift_scale_time(temp: 'const Temporal *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - shift_converted = _ffi.cast('const Interval *', shift) - result = _lib.temporal_shift(temp_converted, shift_converted) + shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL + duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL + result = _lib.temporal_shift_scale_time(temp_converted, shift_converted, duration_converted) _check_error() return result if result != _ffi.NULL else None -def temporal_shift_tscale(temp: 'const Temporal *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'Temporal *': +def temporal_shift_time(temp: 'const Temporal *', shift: 'const Interval *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL - duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL - result = _lib.temporal_shift_tscale(temp_converted, shift_converted, duration_converted) + shift_converted = _ffi.cast('const Interval *', shift) + result = _lib.temporal_shift_time(temp_converted, shift_converted) _check_error() return result if result != _ffi.NULL else None @@ -5275,42 +6060,140 @@ def temporal_to_tinstant(temp: 'const Temporal *') -> 'Temporal *': return result if result != _ffi.NULL else None -def temporal_to_tsequence(temp: 'const Temporal *') -> 'Temporal *': +def temporal_to_tsequence(temp: 'const Temporal *', interp: 'interpType') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.temporal_to_tsequence(temp_converted) + interp_converted = _ffi.cast('interpType', interp) + result = _lib.temporal_to_tsequence(temp_converted, interp_converted) _check_error() return result if result != _ffi.NULL else None -def temporal_to_tsequenceset(temp: 'const Temporal *') -> 'Temporal *': +def temporal_to_tsequenceset(temp: 'const Temporal *', interp: 'interpType') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.temporal_to_tsequenceset(temp_converted) + interp_converted = _ffi.cast('interpType', interp) + result = _lib.temporal_to_tsequenceset(temp_converted, interp_converted) _check_error() return result if result != _ffi.NULL else None -def temporal_tprecision(temp: 'const Temporal *', duration: 'const Interval *', origin: int) -> 'Temporal *': +def tfloat_scale_value(temp: 'const Temporal *', width: float) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - duration_converted = _ffi.cast('const Interval *', duration) - origin_converted = _ffi.cast('TimestampTz', origin) - result = _lib.temporal_tprecision(temp_converted, duration_converted, origin_converted) + result = _lib.tfloat_scale_value(temp_converted, width) _check_error() return result if result != _ffi.NULL else None -def temporal_tsample(temp: 'const Temporal *', duration: 'const Interval *', origin: int) -> 'Temporal *': +def tfloat_shift_scale_value(temp: 'const Temporal *', shift: float, width: float) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - duration_converted = _ffi.cast('const Interval *', duration) - origin_converted = _ffi.cast('TimestampTz', origin) - result = _lib.temporal_tsample(temp_converted, duration_converted, origin_converted) + result = _lib.tfloat_shift_scale_value(temp_converted, shift, width) _check_error() return result if result != _ffi.NULL else None -def temporal_tscale(temp: 'const Temporal *', duration: 'const Interval *') -> 'Temporal *': +def tfloat_shift_value(temp: 'const Temporal *', shift: float) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - duration_converted = _ffi.cast('const Interval *', duration) - result = _lib.temporal_tscale(temp_converted, duration_converted) + result = _lib.tfloat_shift_value(temp_converted, shift) + _check_error() + return result if result != _ffi.NULL else None + + +def tint_scale_value(temp: 'const Temporal *', width: int) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tint_scale_value(temp_converted, width) + _check_error() + return result if result != _ffi.NULL else None + + +def tint_shift_scale_value(temp: 'const Temporal *', shift: int, width: int) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tint_shift_scale_value(temp_converted, shift, width) + _check_error() + return result if result != _ffi.NULL else None + + +def tint_shift_value(temp: 'const Temporal *', shift: int) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tint_shift_value(temp_converted, shift) + _check_error() + return result if result != _ffi.NULL else None + + +def temporal_append_tinstant(temp: 'Temporal *', inst: 'const TInstant *', maxdist: float, maxt: "Optional['Interval *']", expand: bool) -> 'Temporal *': + temp_converted = _ffi.cast('Temporal *', temp) + inst_converted = _ffi.cast('const TInstant *', inst) + maxt_converted = _ffi.cast('Interval *', maxt) if maxt is not None else _ffi.NULL + result = _lib.temporal_append_tinstant(temp_converted, inst_converted, maxdist, maxt_converted, expand) + _check_error() + return result if result != _ffi.NULL else None + + +def temporal_append_tsequence(temp: 'Temporal *', seq: 'const TSequence *', expand: bool) -> 'Temporal *': + temp_converted = _ffi.cast('Temporal *', temp) + seq_converted = _ffi.cast('const TSequence *', seq) + result = _lib.temporal_append_tsequence(temp_converted, seq_converted, expand) + _check_error() + return result if result != _ffi.NULL else None + + +def temporal_delete_period(temp: 'const Temporal *', p: 'const Span *', connect: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + p_converted = _ffi.cast('const Span *', p) + result = _lib.temporal_delete_period(temp_converted, p_converted, connect) + _check_error() + return result if result != _ffi.NULL else None + + +def temporal_delete_periodset(temp: 'const Temporal *', ps: 'const SpanSet *', connect: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + ps_converted = _ffi.cast('const SpanSet *', ps) + result = _lib.temporal_delete_periodset(temp_converted, ps_converted, connect) + _check_error() + return result if result != _ffi.NULL else None + + +def temporal_delete_timestamp(temp: 'const Temporal *', t: int, connect: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.temporal_delete_timestamp(temp_converted, t_converted, connect) + _check_error() + return result if result != _ffi.NULL else None + + +def temporal_delete_timestampset(temp: 'const Temporal *', ts: 'const Set *', connect: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.temporal_delete_timestampset(temp_converted, ts_converted, connect) + _check_error() + return result if result != _ffi.NULL else None + + +def temporal_insert(temp1: 'const Temporal *', temp2: 'const Temporal *', connect: bool) -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.temporal_insert(temp1_converted, temp2_converted, connect) + _check_error() + return result if result != _ffi.NULL else None + + +def temporal_merge(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.temporal_merge(temp1_converted, temp2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def temporal_merge_array(temparr: 'Temporal **', count: int) -> 'Temporal *': + temparr_converted = [_ffi.cast('Temporal *', x) for x in temparr] + result = _lib.temporal_merge_array(temparr_converted, count) + _check_error() + return result if result != _ffi.NULL else None + + +def temporal_update(temp1: 'const Temporal *', temp2: 'const Temporal *', connect: bool) -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.temporal_update(temp1_converted, temp2_converted, connect) _check_error() return result if result != _ffi.NULL else None @@ -5636,1138 +6519,1065 @@ def ttext_value_at_timestamp(temp: 'const Temporal *', t: int, strict: bool) -> return None -def temporal_append_tinstant(temp: 'Temporal *', inst: 'const TInstant *', maxdist: float, maxt: "Optional['Interval *']", expand: bool) -> 'Temporal *': - temp_converted = _ffi.cast('Temporal *', temp) - inst_converted = _ffi.cast('const TInstant *', inst) - maxt_converted = _ffi.cast('Interval *', maxt) if maxt is not None else _ffi.NULL - result = _lib.temporal_append_tinstant(temp_converted, inst_converted, maxdist, maxt_converted, expand) - _check_error() - return result if result != _ffi.NULL else None - - -def temporal_append_tsequence(temp: 'Temporal *', seq: 'const TSequence *', expand: bool) -> 'Temporal *': - temp_converted = _ffi.cast('Temporal *', temp) - seq_converted = _ffi.cast('const TSequence *', seq) - result = _lib.temporal_append_tsequence(temp_converted, seq_converted, expand) - _check_error() - return result if result != _ffi.NULL else None - - -def temporal_delete_period(temp: 'const Temporal *', p: 'const Span *', connect: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - p_converted = _ffi.cast('const Span *', p) - result = _lib.temporal_delete_period(temp_converted, p_converted, connect) - _check_error() - return result if result != _ffi.NULL else None - - -def temporal_delete_periodset(temp: 'const Temporal *', ps: 'const SpanSet *', connect: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - ps_converted = _ffi.cast('const SpanSet *', ps) - result = _lib.temporal_delete_periodset(temp_converted, ps_converted, connect) - _check_error() - return result if result != _ffi.NULL else None - - -def temporal_delete_timestamp(temp: 'const Temporal *', t: int, connect: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.temporal_delete_timestamp(temp_converted, t_converted, connect) - _check_error() - return result if result != _ffi.NULL else None - - -def temporal_delete_timestampset(temp: 'const Temporal *', ts: 'const Set *', connect: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.temporal_delete_timestampset(temp_converted, ts_converted, connect) - _check_error() - return result if result != _ffi.NULL else None - - -def temporal_insert(temp1: 'const Temporal *', temp2: 'const Temporal *', connect: bool) -> 'Temporal *': +def temporal_cmp(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'int': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_insert(temp1_converted, temp2_converted, connect) + result = _lib.temporal_cmp(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def temporal_merge(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': +def temporal_eq(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_merge(temp1_converted, temp2_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def temporal_merge_array(temparr: 'Temporal **', count: int) -> 'Temporal *': - temparr_converted = [_ffi.cast('Temporal *', x) for x in temparr] - result = _lib.temporal_merge_array(temparr_converted, count) + result = _lib.temporal_eq(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def temporal_update(temp1: 'const Temporal *', temp2: 'const Temporal *', connect: bool) -> 'Temporal *': +def temporal_ge(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_update(temp1_converted, temp2_converted, connect) - _check_error() - return result if result != _ffi.NULL else None - - -def tand_bool_tbool(b: bool, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tand_bool_tbool(b, temp_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def tand_tbool_bool(temp: 'const Temporal *', b: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tand_tbool_bool(temp_converted, b) + result = _lib.temporal_ge(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tand_tbool_tbool(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': +def temporal_gt(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.tand_tbool_tbool(temp1_converted, temp2_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def tbool_when_true(temp: 'const Temporal *') -> 'SpanSet *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tbool_when_true(temp_converted) - _check_error() - return result if result != _ffi.NULL else None - - -def tnot_tbool(temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tnot_tbool(temp_converted) + result = _lib.temporal_gt(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tor_bool_tbool(b: bool, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tor_bool_tbool(b, temp_converted) +def temporal_le(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.temporal_le(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tor_tbool_bool(temp: 'const Temporal *', b: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tor_tbool_bool(temp_converted, b) +def temporal_lt(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.temporal_lt(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tor_tbool_tbool(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': +def temporal_ne(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.tor_tbool_tbool(temp1_converted, temp2_converted) + result = _lib.temporal_ne(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def add_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.add_float_tfloat(d, tnumber_converted) +def tbool_always_eq(temp: 'const Temporal *', b: bool) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tbool_always_eq(temp_converted, b) _check_error() return result if result != _ffi.NULL else None -def add_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.add_int_tint(i, tnumber_converted) +def tbool_ever_eq(temp: 'const Temporal *', b: bool) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tbool_ever_eq(temp_converted, b) _check_error() return result if result != _ffi.NULL else None -def add_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.add_tfloat_float(tnumber_converted, d) +def tfloat_always_eq(temp: 'const Temporal *', d: float) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tfloat_always_eq(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def add_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.add_tint_int(tnumber_converted, i) +def tfloat_always_le(temp: 'const Temporal *', d: float) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tfloat_always_le(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def add_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal *') -> 'Temporal *': - tnumber1_converted = _ffi.cast('const Temporal *', tnumber1) - tnumber2_converted = _ffi.cast('const Temporal *', tnumber2) - result = _lib.add_tnumber_tnumber(tnumber1_converted, tnumber2_converted) +def tfloat_always_lt(temp: 'const Temporal *', d: float) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tfloat_always_lt(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def div_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.div_float_tfloat(d, tnumber_converted) +def tfloat_ever_eq(temp: 'const Temporal *', d: float) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tfloat_ever_eq(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def div_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.div_int_tint(i, tnumber_converted) +def tfloat_ever_le(temp: 'const Temporal *', d: float) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tfloat_ever_le(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def div_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.div_tfloat_float(tnumber_converted, d) +def tfloat_ever_lt(temp: 'const Temporal *', d: float) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tfloat_ever_lt(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def div_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.div_tint_int(tnumber_converted, i) +def tint_always_eq(temp: 'const Temporal *', i: int) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tint_always_eq(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def div_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal *') -> 'Temporal *': - tnumber1_converted = _ffi.cast('const Temporal *', tnumber1) - tnumber2_converted = _ffi.cast('const Temporal *', tnumber2) - result = _lib.div_tnumber_tnumber(tnumber1_converted, tnumber2_converted) +def tint_always_le(temp: 'const Temporal *', i: int) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tint_always_le(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def float_degrees(value: float, normalize: bool) -> 'double': - result = _lib.float_degrees(value, normalize) +def tint_always_lt(temp: 'const Temporal *', i: int) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tint_always_lt(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def mult_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.mult_float_tfloat(d, tnumber_converted) +def tint_ever_eq(temp: 'const Temporal *', i: int) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tint_ever_eq(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def mult_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.mult_int_tint(i, tnumber_converted) +def tint_ever_le(temp: 'const Temporal *', i: int) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tint_ever_le(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def mult_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.mult_tfloat_float(tnumber_converted, d) +def tint_ever_lt(temp: 'const Temporal *', i: int) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tint_ever_lt(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def mult_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.mult_tint_int(tnumber_converted, i) +def tpoint_always_eq(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.tpoint_always_eq(temp_converted, gs_converted) _check_error() return result if result != _ffi.NULL else None -def mult_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal *') -> 'Temporal *': - tnumber1_converted = _ffi.cast('const Temporal *', tnumber1) - tnumber2_converted = _ffi.cast('const Temporal *', tnumber2) - result = _lib.mult_tnumber_tnumber(tnumber1_converted, tnumber2_converted) +def tpoint_ever_eq(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.tpoint_ever_eq(temp_converted, gs_converted) _check_error() return result if result != _ffi.NULL else None -def sub_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.sub_float_tfloat(d, tnumber_converted) +def ttext_always_eq(temp: 'const Temporal *', txt: str) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + txt_converted = cstring2text(txt) + result = _lib.ttext_always_eq(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def sub_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.sub_int_tint(i, tnumber_converted) +def ttext_always_le(temp: 'const Temporal *', txt: str) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + txt_converted = cstring2text(txt) + result = _lib.ttext_always_le(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def sub_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.sub_tfloat_float(tnumber_converted, d) +def ttext_always_lt(temp: 'const Temporal *', txt: str) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + txt_converted = cstring2text(txt) + result = _lib.ttext_always_lt(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def sub_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': - tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.sub_tint_int(tnumber_converted, i) +def ttext_ever_eq(temp: 'const Temporal *', txt: str) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + txt_converted = cstring2text(txt) + result = _lib.ttext_ever_eq(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def sub_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal *') -> 'Temporal *': - tnumber1_converted = _ffi.cast('const Temporal *', tnumber1) - tnumber2_converted = _ffi.cast('const Temporal *', tnumber2) - result = _lib.sub_tnumber_tnumber(tnumber1_converted, tnumber2_converted) +def ttext_ever_le(temp: 'const Temporal *', txt: str) -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + txt_converted = cstring2text(txt) + result = _lib.ttext_ever_le(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def tfloat_round(temp: 'const Temporal *', maxdd: int) -> 'Temporal *': +def ttext_ever_lt(temp: 'const Temporal *', txt: str) -> 'bool': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_round(temp_converted, maxdd) + txt_converted = cstring2text(txt) + result = _lib.ttext_ever_lt(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def tfloat_degrees(temp: 'const Temporal *', normalize: bool) -> 'Temporal *': +def teq_bool_tbool(b: bool, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_degrees(temp_converted, normalize) + result = _lib.teq_bool_tbool(b, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tfloat_derivative(temp: 'const Temporal *') -> 'Temporal *': +def teq_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_derivative(temp_converted) + result = _lib.teq_float_tfloat(d, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tfloat_radians(temp: 'const Temporal *') -> 'Temporal *': +def teq_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_radians(temp_converted) + result = _lib.teq_int_tint(i, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tnumber_abs(temp: 'const Temporal *') -> 'Temporal *': +def teq_point_tpoint(gs: 'const GSERIALIZED *', temp: 'const Temporal *') -> 'Temporal *': + gs_converted = _ffi.cast('const GSERIALIZED *', gs) temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tnumber_abs(temp_converted) + result = _lib.teq_point_tpoint(gs_converted, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tnumber_angular_difference(temp: 'const Temporal *') -> 'Temporal *': +def teq_tbool_bool(temp: 'const Temporal *', b: bool) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tnumber_angular_difference(temp_converted) + result = _lib.teq_tbool_bool(temp_converted, b) _check_error() return result if result != _ffi.NULL else None -def tnumber_delta_value(temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tnumber_delta_value(temp_converted) +def teq_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.teq_temporal_temporal(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def textcat_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': +def teq_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': txt_converted = cstring2text(txt) temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.textcat_text_ttext(txt_converted, temp_converted) + result = _lib.teq_text_ttext(txt_converted, temp_converted) _check_error() return result if result != _ffi.NULL else None -def textcat_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': +def teq_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.textcat_ttext_text(temp_converted, txt_converted) + result = _lib.teq_tfloat_float(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def textcat_ttext_ttext(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.textcat_ttext_ttext(temp1_converted, temp2_converted) +def teq_tpoint_point(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.teq_tpoint_point(temp_converted, gs_converted) _check_error() return result if result != _ffi.NULL else None -def ttext_upper(temp: 'const Temporal *') -> 'Temporal *': +def teq_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.ttext_upper(temp_converted) + result = _lib.teq_tint_int(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def ttext_lower(temp: 'const Temporal *') -> 'Temporal *': +def teq_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.ttext_lower(temp_converted) + txt_converted = cstring2text(txt) + result = _lib.teq_ttext_text(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def distance_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': +def tge_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.distance_tfloat_float(temp_converted, d) + result = _lib.tge_float_tfloat(d, temp_converted) _check_error() return result if result != _ffi.NULL else None -def distance_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': +def tge_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.distance_tint_int(temp_converted, i) + result = _lib.tge_int_tint(i, temp_converted) _check_error() return result if result != _ffi.NULL else None -def distance_tnumber_tnumber(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': +def tge_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.distance_tnumber_tnumber(temp1_converted, temp2_converted) + result = _lib.tge_temporal_temporal(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def distance_tpoint_geo(temp: 'const Temporal *', geo: 'const GSERIALIZED *') -> 'Temporal *': +def tge_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': + txt_converted = cstring2text(txt) temp_converted = _ffi.cast('const Temporal *', temp) - geo_converted = _ffi.cast('const GSERIALIZED *', geo) - result = _lib.distance_tpoint_geo(temp_converted, geo_converted) + result = _lib.tge_text_ttext(txt_converted, temp_converted) _check_error() return result if result != _ffi.NULL else None -def distance_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.distance_tpoint_tpoint(temp1_converted, temp2_converted) +def tge_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tge_tfloat_float(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def nad_stbox_geo(box: 'const STBox *', gs: 'const GSERIALIZED *') -> 'double': - box_converted = _ffi.cast('const STBox *', box) - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - result = _lib.nad_stbox_geo(box_converted, gs_converted) +def tge_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tge_tint_int(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def nad_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'double': - box1_converted = _ffi.cast('const STBox *', box1) - box2_converted = _ffi.cast('const STBox *', box2) - result = _lib.nad_stbox_stbox(box1_converted, box2_converted) +def tge_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + txt_converted = cstring2text(txt) + result = _lib.tge_ttext_text(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def nad_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *') -> 'double': - box1_converted = _ffi.cast('const TBox *', box1) - box2_converted = _ffi.cast('const TBox *', box2) - result = _lib.nad_tbox_tbox(box1_converted, box2_converted) +def tgt_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tgt_float_tfloat(d, temp_converted) _check_error() return result if result != _ffi.NULL else None -def nad_tfloat_float(temp: 'const Temporal *', d: float) -> 'double': +def tgt_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.nad_tfloat_float(temp_converted, d) + result = _lib.tgt_int_tint(i, temp_converted) _check_error() return result if result != _ffi.NULL else None -def nad_tfloat_tfloat(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'double': +def tgt_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.nad_tfloat_tfloat(temp1_converted, temp2_converted) + result = _lib.tgt_temporal_temporal(temp1_converted, temp2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def tgt_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': + txt_converted = cstring2text(txt) + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tgt_text_ttext(txt_converted, temp_converted) _check_error() return result if result != _ffi.NULL else None -def nad_tint_int(temp: 'const Temporal *', i: int) -> 'int': +def tgt_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.nad_tint_int(temp_converted, i) + result = _lib.tgt_tfloat_float(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def nad_tint_tint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'int': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.nad_tint_tint(temp1_converted, temp2_converted) +def tgt_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tgt_tint_int(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def nad_tnumber_tbox(temp: 'const Temporal *', box: 'const TBox *') -> 'double': +def tgt_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - box_converted = _ffi.cast('const TBox *', box) - result = _lib.nad_tnumber_tbox(temp_converted, box_converted) + txt_converted = cstring2text(txt) + result = _lib.tgt_ttext_text(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def nad_tpoint_geo(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'double': +def tle_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - result = _lib.nad_tpoint_geo(temp_converted, gs_converted) + result = _lib.tle_float_tfloat(d, temp_converted) _check_error() return result if result != _ffi.NULL else None -def nad_tpoint_stbox(temp: 'const Temporal *', box: 'const STBox *') -> 'double': +def tle_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - box_converted = _ffi.cast('const STBox *', box) - result = _lib.nad_tpoint_stbox(temp_converted, box_converted) + result = _lib.tle_int_tint(i, temp_converted) _check_error() return result if result != _ffi.NULL else None -def nad_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'double': +def tle_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.nad_tpoint_tpoint(temp1_converted, temp2_converted) + result = _lib.tle_temporal_temporal(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def nai_tpoint_geo(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'TInstant *': +def tle_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': + txt_converted = cstring2text(txt) temp_converted = _ffi.cast('const Temporal *', temp) - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - result = _lib.nai_tpoint_geo(temp_converted, gs_converted) + result = _lib.tle_text_ttext(txt_converted, temp_converted) _check_error() return result if result != _ffi.NULL else None -def nai_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'TInstant *': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.nai_tpoint_tpoint(temp1_converted, temp2_converted) +def tle_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tle_tfloat_float(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def shortestline_tpoint_geo(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'GSERIALIZED **': +def tle_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - out_result = _ffi.new('GSERIALIZED **') - result = _lib.shortestline_tpoint_geo(temp_converted, gs_converted, out_result) + result = _lib.tle_tint_int(temp_converted, i) _check_error() - if result: - return out_result if out_result != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def shortestline_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'GSERIALIZED **': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - out_result = _ffi.new('GSERIALIZED **') - result = _lib.shortestline_tpoint_tpoint(temp1_converted, temp2_converted, out_result) +def tle_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + txt_converted = cstring2text(txt) + result = _lib.tle_ttext_text(temp_converted, txt_converted) _check_error() - if result: - return out_result if out_result != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None -def tbool_always_eq(temp: 'const Temporal *', b: bool) -> 'bool': +def tlt_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tbool_always_eq(temp_converted, b) + result = _lib.tlt_float_tfloat(d, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tbool_ever_eq(temp: 'const Temporal *', b: bool) -> 'bool': +def tlt_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tbool_ever_eq(temp_converted, b) + result = _lib.tlt_int_tint(i, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tfloat_always_eq(temp: 'const Temporal *', d: float) -> 'bool': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_always_eq(temp_converted, d) +def tlt_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.tlt_temporal_temporal(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tfloat_always_le(temp: 'const Temporal *', d: float) -> 'bool': +def tlt_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': + txt_converted = cstring2text(txt) temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_always_le(temp_converted, d) + result = _lib.tlt_text_ttext(txt_converted, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tfloat_always_lt(temp: 'const Temporal *', d: float) -> 'bool': +def tlt_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_always_lt(temp_converted, d) + result = _lib.tlt_tfloat_float(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def tfloat_ever_eq(temp: 'const Temporal *', d: float) -> 'bool': +def tlt_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_ever_eq(temp_converted, d) + result = _lib.tlt_tint_int(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def tfloat_ever_le(temp: 'const Temporal *', d: float) -> 'bool': +def tlt_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_ever_le(temp_converted, d) + txt_converted = cstring2text(txt) + result = _lib.tlt_ttext_text(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def tfloat_ever_lt(temp: 'const Temporal *', d: float) -> 'bool': +def tne_bool_tbool(b: bool, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_ever_lt(temp_converted, d) + result = _lib.tne_bool_tbool(b, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tint_always_eq(temp: 'const Temporal *', i: int) -> 'bool': +def tne_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tint_always_eq(temp_converted, i) + result = _lib.tne_float_tfloat(d, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tint_always_le(temp: 'const Temporal *', i: int) -> 'bool': +def tne_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tint_always_le(temp_converted, i) + result = _lib.tne_int_tint(i, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tint_always_lt(temp: 'const Temporal *', i: int) -> 'bool': +def tne_point_tpoint(gs: 'const GSERIALIZED *', temp: 'const Temporal *') -> 'Temporal *': + gs_converted = _ffi.cast('const GSERIALIZED *', gs) temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tint_always_lt(temp_converted, i) + result = _lib.tne_point_tpoint(gs_converted, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tint_ever_eq(temp: 'const Temporal *', i: int) -> 'bool': +def tne_tbool_bool(temp: 'const Temporal *', b: bool) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tint_ever_eq(temp_converted, i) + result = _lib.tne_tbool_bool(temp_converted, b) _check_error() return result if result != _ffi.NULL else None -def tint_ever_le(temp: 'const Temporal *', i: int) -> 'bool': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tint_ever_le(temp_converted, i) +def tne_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.tne_temporal_temporal(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tint_ever_lt(temp: 'const Temporal *', i: int) -> 'bool': +def tne_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': + txt_converted = cstring2text(txt) temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tint_ever_lt(temp_converted, i) + result = _lib.tne_text_ttext(txt_converted, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tpoint_always_eq(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'bool': +def tne_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - result = _lib.tpoint_always_eq(temp_converted, gs_converted) + result = _lib.tne_tfloat_float(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def tpoint_ever_eq(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'bool': +def tne_tpoint_point(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) gs_converted = _ffi.cast('const GSERIALIZED *', gs) - result = _lib.tpoint_ever_eq(temp_converted, gs_converted) + result = _lib.tne_tpoint_point(temp_converted, gs_converted) _check_error() return result if result != _ffi.NULL else None -def ttext_always_eq(temp: 'const Temporal *', txt: str) -> 'bool': +def tne_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.ttext_always_eq(temp_converted, txt_converted) + result = _lib.tne_tint_int(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def ttext_always_le(temp: 'const Temporal *', txt: str) -> 'bool': +def tne_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) txt_converted = cstring2text(txt) - result = _lib.ttext_always_le(temp_converted, txt_converted) + result = _lib.tne_ttext_text(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def ttext_always_lt(temp: 'const Temporal *', txt: str) -> 'bool': +def tand_bool_tbool(b: bool, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.ttext_always_lt(temp_converted, txt_converted) + result = _lib.tand_bool_tbool(b, temp_converted) _check_error() return result if result != _ffi.NULL else None -def ttext_ever_eq(temp: 'const Temporal *', txt: str) -> 'bool': +def tand_tbool_bool(temp: 'const Temporal *', b: bool) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.ttext_ever_eq(temp_converted, txt_converted) + result = _lib.tand_tbool_bool(temp_converted, b) _check_error() return result if result != _ffi.NULL else None -def ttext_ever_le(temp: 'const Temporal *', txt: str) -> 'bool': +def tand_tbool_tbool(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.tand_tbool_tbool(temp1_converted, temp2_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def tbool_when_true(temp: 'const Temporal *') -> 'SpanSet *': temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.ttext_ever_le(temp_converted, txt_converted) + result = _lib.tbool_when_true(temp_converted) _check_error() return result if result != _ffi.NULL else None -def ttext_ever_lt(temp: 'const Temporal *', txt: str) -> 'bool': +def tnot_tbool(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.ttext_ever_lt(temp_converted, txt_converted) + result = _lib.tnot_tbool(temp_converted) _check_error() return result if result != _ffi.NULL else None -def temporal_cmp(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'int': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_cmp(temp1_converted, temp2_converted) +def tor_bool_tbool(b: bool, temp: 'const Temporal *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tor_bool_tbool(b, temp_converted) _check_error() return result if result != _ffi.NULL else None -def temporal_eq(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_eq(temp1_converted, temp2_converted) +def tor_tbool_bool(temp: 'const Temporal *', b: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tor_tbool_bool(temp_converted, b) _check_error() return result if result != _ffi.NULL else None -def temporal_ge(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': +def tor_tbool_tbool(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_ge(temp1_converted, temp2_converted) + result = _lib.tor_tbool_tbool(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def temporal_gt(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_gt(temp1_converted, temp2_converted) +def add_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.add_float_tfloat(d, tnumber_converted) _check_error() return result if result != _ffi.NULL else None -def temporal_le(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_le(temp1_converted, temp2_converted) +def add_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.add_int_tint(i, tnumber_converted) _check_error() return result if result != _ffi.NULL else None -def temporal_lt(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_lt(temp1_converted, temp2_converted) +def add_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.add_tfloat_float(tnumber_converted, d) _check_error() return result if result != _ffi.NULL else None -def temporal_ne(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'bool': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_ne(temp1_converted, temp2_converted) +def add_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.add_tint_int(tnumber_converted, i) _check_error() return result if result != _ffi.NULL else None -def teq_bool_tbool(b: bool, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.teq_bool_tbool(b, temp_converted) +def add_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal *') -> 'Temporal *': + tnumber1_converted = _ffi.cast('const Temporal *', tnumber1) + tnumber2_converted = _ffi.cast('const Temporal *', tnumber2) + result = _lib.add_tnumber_tnumber(tnumber1_converted, tnumber2_converted) _check_error() return result if result != _ffi.NULL else None -def teq_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.teq_float_tfloat(d, temp_converted) +def div_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.div_float_tfloat(d, tnumber_converted) _check_error() return result if result != _ffi.NULL else None -def teq_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.teq_int_tint(i, temp_converted) +def div_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.div_int_tint(i, tnumber_converted) _check_error() return result if result != _ffi.NULL else None -def teq_point_tpoint(gs: 'const GSERIALIZED *', temp: 'const Temporal *') -> 'Temporal *': - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.teq_point_tpoint(gs_converted, temp_converted) +def div_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.div_tfloat_float(tnumber_converted, d) _check_error() return result if result != _ffi.NULL else None -def teq_tbool_bool(temp: 'const Temporal *', b: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.teq_tbool_bool(temp_converted, b) +def div_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.div_tint_int(tnumber_converted, i) _check_error() return result if result != _ffi.NULL else None -def teq_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.teq_temporal_temporal(temp1_converted, temp2_converted) +def div_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal *') -> 'Temporal *': + tnumber1_converted = _ffi.cast('const Temporal *', tnumber1) + tnumber2_converted = _ffi.cast('const Temporal *', tnumber2) + result = _lib.div_tnumber_tnumber(tnumber1_converted, tnumber2_converted) _check_error() return result if result != _ffi.NULL else None -def teq_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': - txt_converted = cstring2text(txt) - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.teq_text_ttext(txt_converted, temp_converted) +def float_degrees(value: float, normalize: bool) -> 'double': + result = _lib.float_degrees(value, normalize) _check_error() return result if result != _ffi.NULL else None -def teq_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.teq_tfloat_float(temp_converted, d) +def mult_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.mult_float_tfloat(d, tnumber_converted) _check_error() return result if result != _ffi.NULL else None -def teq_tpoint_point(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - result = _lib.teq_tpoint_point(temp_converted, gs_converted) +def mult_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.mult_int_tint(i, tnumber_converted) _check_error() return result if result != _ffi.NULL else None -def teq_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.teq_tint_int(temp_converted, i) +def mult_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.mult_tfloat_float(tnumber_converted, d) _check_error() return result if result != _ffi.NULL else None -def teq_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.teq_ttext_text(temp_converted, txt_converted) +def mult_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.mult_tint_int(tnumber_converted, i) _check_error() return result if result != _ffi.NULL else None -def tge_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tge_float_tfloat(d, temp_converted) +def mult_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal *') -> 'Temporal *': + tnumber1_converted = _ffi.cast('const Temporal *', tnumber1) + tnumber2_converted = _ffi.cast('const Temporal *', tnumber2) + result = _lib.mult_tnumber_tnumber(tnumber1_converted, tnumber2_converted) _check_error() return result if result != _ffi.NULL else None -def tge_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tge_int_tint(i, temp_converted) +def sub_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.sub_float_tfloat(d, tnumber_converted) _check_error() return result if result != _ffi.NULL else None -def tge_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.tge_temporal_temporal(temp1_converted, temp2_converted) +def sub_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.sub_int_tint(i, tnumber_converted) _check_error() return result if result != _ffi.NULL else None -def tge_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': - txt_converted = cstring2text(txt) - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tge_text_ttext(txt_converted, temp_converted) +def sub_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.sub_tfloat_float(tnumber_converted, d) _check_error() return result if result != _ffi.NULL else None -def tge_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tge_tfloat_float(temp_converted, d) +def sub_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': + tnumber_converted = _ffi.cast('const Temporal *', tnumber) + result = _lib.sub_tint_int(tnumber_converted, i) _check_error() return result if result != _ffi.NULL else None -def tge_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tge_tint_int(temp_converted, i) +def sub_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal *') -> 'Temporal *': + tnumber1_converted = _ffi.cast('const Temporal *', tnumber1) + tnumber2_converted = _ffi.cast('const Temporal *', tnumber2) + result = _lib.sub_tnumber_tnumber(tnumber1_converted, tnumber2_converted) _check_error() return result if result != _ffi.NULL else None -def tge_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': +def tfloat_round(temp: 'const Temporal *', maxdd: int) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.tge_ttext_text(temp_converted, txt_converted) + result = _lib.tfloat_round(temp_converted, maxdd) _check_error() return result if result != _ffi.NULL else None -def tgt_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tgt_float_tfloat(d, temp_converted) +def tfloatarr_round(temp: 'const Temporal **', count: int, maxdd: int) -> 'Temporal **': + temp_converted = [_ffi.cast('const Temporal *', x) for x in temp] + result = _lib.tfloatarr_round(temp_converted, count, maxdd) _check_error() return result if result != _ffi.NULL else None -def tgt_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': +def tfloat_degrees(temp: 'const Temporal *', normalize: bool) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tgt_int_tint(i, temp_converted) + result = _lib.tfloat_degrees(temp_converted, normalize) _check_error() return result if result != _ffi.NULL else None -def tgt_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.tgt_temporal_temporal(temp1_converted, temp2_converted) +def tfloat_derivative(temp: 'const Temporal *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tfloat_derivative(temp_converted) _check_error() return result if result != _ffi.NULL else None -def tgt_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': - txt_converted = cstring2text(txt) +def tfloat_radians(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tgt_text_ttext(txt_converted, temp_converted) + result = _lib.tfloat_radians(temp_converted) _check_error() return result if result != _ffi.NULL else None -def tgt_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': +def tnumber_abs(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tgt_tfloat_float(temp_converted, d) + result = _lib.tnumber_abs(temp_converted) _check_error() return result if result != _ffi.NULL else None -def tgt_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': +def tnumber_angular_difference(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tgt_tint_int(temp_converted, i) + result = _lib.tnumber_angular_difference(temp_converted) _check_error() return result if result != _ffi.NULL else None -def tgt_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': +def tnumber_delta_value(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.tgt_ttext_text(temp_converted, txt_converted) + result = _lib.tnumber_delta_value(temp_converted) _check_error() return result if result != _ffi.NULL else None -def tle_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': +def textcat_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': + txt_converted = cstring2text(txt) temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tle_float_tfloat(d, temp_converted) + result = _lib.textcat_text_ttext(txt_converted, temp_converted) _check_error() return result if result != _ffi.NULL else None -def tle_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': +def textcat_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tle_int_tint(i, temp_converted) + txt_converted = cstring2text(txt) + result = _lib.textcat_ttext_text(temp_converted, txt_converted) _check_error() return result if result != _ffi.NULL else None -def tle_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': +def textcat_ttext_ttext(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.tle_temporal_temporal(temp1_converted, temp2_converted) + result = _lib.textcat_ttext_ttext(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tle_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': - txt_converted = cstring2text(txt) +def ttext_upper(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tle_text_ttext(txt_converted, temp_converted) + result = _lib.ttext_upper(temp_converted) _check_error() return result if result != _ffi.NULL else None -def tle_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': +def ttext_lower(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tle_tfloat_float(temp_converted, d) + result = _lib.ttext_lower(temp_converted) _check_error() return result if result != _ffi.NULL else None -def tle_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': +def distance_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tle_tint_int(temp_converted, i) + result = _lib.distance_tfloat_float(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def tle_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': +def distance_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.tle_ttext_text(temp_converted, txt_converted) + result = _lib.distance_tint_int(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def tlt_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tlt_float_tfloat(d, temp_converted) +def distance_tnumber_tnumber(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.distance_tnumber_tnumber(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tlt_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': +def distance_tpoint_point(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tlt_int_tint(i, temp_converted) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.distance_tpoint_point(temp_converted, gs_converted) _check_error() return result if result != _ffi.NULL else None -def tlt_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': +def distance_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.tlt_temporal_temporal(temp1_converted, temp2_converted) + result = _lib.distance_tpoint_tpoint(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tlt_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': - txt_converted = cstring2text(txt) - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tlt_text_ttext(txt_converted, temp_converted) +def nad_stbox_geo(box: 'const STBox *', gs: 'const GSERIALIZED *') -> 'double': + box_converted = _ffi.cast('const STBox *', box) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.nad_stbox_geo(box_converted, gs_converted) _check_error() return result if result != _ffi.NULL else None -def tlt_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tlt_tfloat_float(temp_converted, d) +def nad_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'double': + box1_converted = _ffi.cast('const STBox *', box1) + box2_converted = _ffi.cast('const STBox *', box2) + result = _lib.nad_stbox_stbox(box1_converted, box2_converted) _check_error() return result if result != _ffi.NULL else None -def tlt_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tlt_tint_int(temp_converted, i) +def nad_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *') -> 'double': + box1_converted = _ffi.cast('const TBox *', box1) + box2_converted = _ffi.cast('const TBox *', box2) + result = _lib.nad_tbox_tbox(box1_converted, box2_converted) _check_error() return result if result != _ffi.NULL else None -def tlt_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': +def nad_tfloat_float(temp: 'const Temporal *', d: float) -> 'double': temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.tlt_ttext_text(temp_converted, txt_converted) + result = _lib.nad_tfloat_float(temp_converted, d) _check_error() return result if result != _ffi.NULL else None -def tne_bool_tbool(b: bool, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tne_bool_tbool(b, temp_converted) +def nad_tfloat_tfloat(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'double': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.nad_tfloat_tfloat(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tne_float_tfloat(d: float, temp: 'const Temporal *') -> 'Temporal *': +def nad_tint_int(temp: 'const Temporal *', i: int) -> 'int': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tne_float_tfloat(d, temp_converted) + result = _lib.nad_tint_int(temp_converted, i) _check_error() return result if result != _ffi.NULL else None -def tne_int_tint(i: int, temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tne_int_tint(i, temp_converted) +def nad_tint_tint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'int': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.nad_tint_tint(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tne_point_tpoint(gs: 'const GSERIALIZED *', temp: 'const Temporal *') -> 'Temporal *': - gs_converted = _ffi.cast('const GSERIALIZED *', gs) +def nad_tnumber_tbox(temp: 'const Temporal *', box: 'const TBox *') -> 'double': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tne_point_tpoint(gs_converted, temp_converted) + box_converted = _ffi.cast('const TBox *', box) + result = _lib.nad_tnumber_tbox(temp_converted, box_converted) _check_error() return result if result != _ffi.NULL else None -def tne_tbool_bool(temp: 'const Temporal *', b: bool) -> 'Temporal *': +def nad_tpoint_geo(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'double': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tne_tbool_bool(temp_converted, b) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.nad_tpoint_geo(temp_converted, gs_converted) _check_error() return result if result != _ffi.NULL else None -def tne_temporal_temporal(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.tne_temporal_temporal(temp1_converted, temp2_converted) +def nad_tpoint_stbox(temp: 'const Temporal *', box: 'const STBox *') -> 'double': + temp_converted = _ffi.cast('const Temporal *', temp) + box_converted = _ffi.cast('const STBox *', box) + result = _lib.nad_tpoint_stbox(temp_converted, box_converted) _check_error() return result if result != _ffi.NULL else None -def tne_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': - txt_converted = cstring2text(txt) - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tne_text_ttext(txt_converted, temp_converted) +def nad_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'double': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.nad_tpoint_tpoint(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tne_tfloat_float(temp: 'const Temporal *', d: float) -> 'Temporal *': +def nai_tpoint_geo(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'TInstant *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tne_tfloat_float(temp_converted, d) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.nai_tpoint_geo(temp_converted, gs_converted) _check_error() return result if result != _ffi.NULL else None -def tne_tpoint_point(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - result = _lib.tne_tpoint_point(temp_converted, gs_converted) +def nai_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'TInstant *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.nai_tpoint_tpoint(temp1_converted, temp2_converted) _check_error() return result if result != _ffi.NULL else None -def tne_tint_int(temp: 'const Temporal *', i: int) -> 'Temporal *': +def shortestline_tpoint_geo(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'GSERIALIZED **': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tne_tint_int(temp_converted, i) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + out_result = _ffi.new('GSERIALIZED **') + result = _lib.shortestline_tpoint_geo(temp_converted, gs_converted, out_result) _check_error() - return result if result != _ffi.NULL else None + if result: + return out_result if out_result != _ffi.NULL else None + return None -def tne_ttext_text(temp: 'const Temporal *', txt: str) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - txt_converted = cstring2text(txt) - result = _lib.tne_ttext_text(temp_converted, txt_converted) +def shortestline_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'GSERIALIZED **': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + out_result = _ffi.new('GSERIALIZED **') + result = _lib.shortestline_tpoint_tpoint(temp1_converted, temp2_converted, out_result) _check_error() - return result if result != _ffi.NULL else None + if result: + return out_result if out_result != _ffi.NULL else None + return None -def bearing_point_point(geo1: 'const GSERIALIZED *', geo2: 'const GSERIALIZED *') -> 'double': - geo1_converted = _ffi.cast('const GSERIALIZED *', geo1) - geo2_converted = _ffi.cast('const GSERIALIZED *', geo2) +def bearing_point_point(gs1: 'const GSERIALIZED *', gs2: 'const GSERIALIZED *') -> 'double': + gs1_converted = _ffi.cast('const GSERIALIZED *', gs1) + gs2_converted = _ffi.cast('const GSERIALIZED *', gs2) out_result = _ffi.new('double *') - result = _lib.bearing_point_point(geo1_converted, geo2_converted, out_result) + result = _lib.bearing_point_point(gs1_converted, gs2_converted, out_result) _check_error() if result: return out_result[0] if out_result[0] != _ffi.NULL else None @@ -6828,9 +7638,23 @@ def tpoint_direction(temp: 'const Temporal *') -> 'double': return None -def tpoint_get_coord(temp: 'const Temporal *', coord: int) -> 'Temporal *': +def tpoint_get_x(temp: 'const Temporal *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tpoint_get_x(temp_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def tpoint_get_y(temp: 'const Temporal *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tpoint_get_y(temp_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def tpoint_get_z(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tpoint_get_coord(temp_converted, coord) + result = _lib.tpoint_get_z(temp_converted) _check_error() return result if result != _ffi.NULL else None @@ -6885,23 +7709,43 @@ def geo_expand_space(gs: 'const GSERIALIZED *', d: float) -> 'STBox *': return result if result != _ffi.NULL else None -def tgeompoint_tgeogpoint(temp: 'const Temporal *', oper: bool) -> 'Temporal *': +def geo_to_tpoint(gs: 'const GSERIALIZED *') -> 'Temporal *': + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.geo_to_tpoint(gs_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def tgeogpoint_to_tgeompoint(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tgeompoint_tgeogpoint(temp_converted, oper) + result = _lib.tgeogpoint_to_tgeompoint(temp_converted) _check_error() return result if result != _ffi.NULL else None -def tpoint_expand_space(temp: 'const Temporal *', d: float) -> 'STBox *': +def tgeompoint_to_tgeogpoint(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tpoint_expand_space(temp_converted, d) + result = _lib.tgeompoint_to_tgeogpoint(temp_converted) _check_error() return result if result != _ffi.NULL else None -def tpoint_round(temp: 'const Temporal *', maxdd: int) -> 'Temporal *': +def tpoint_AsMVTGeom(temp: 'const Temporal *', bounds: 'const STBox *', extent: 'int32_t', buffer: 'int32_t', clip_geom: bool, gsarr: 'GSERIALIZED **', timesarr: 'int64 **') -> "Tuple['bool', 'int']": temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tpoint_round(temp_converted, maxdd) + bounds_converted = _ffi.cast('const STBox *', bounds) + extent_converted = _ffi.cast('int32_t', extent) + buffer_converted = _ffi.cast('int32_t', buffer) + gsarr_converted = [_ffi.cast('GSERIALIZED *', x) for x in gsarr] + timesarr_converted = [_ffi.cast('int64 *', x) for x in timesarr] + count = _ffi.new('int *') + result = _lib.tpoint_AsMVTGeom(temp_converted, bounds_converted, extent_converted, buffer_converted, clip_geom, gsarr_converted, timesarr_converted, count) + _check_error() + return result if result != _ffi.NULL else None, count[0] + + +def tpoint_expand_space(temp: 'const Temporal *', d: float) -> 'STBox *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tpoint_expand_space(temp_converted, d) _check_error() return result if result != _ffi.NULL else None @@ -6914,6 +7758,20 @@ def tpoint_make_simple(temp: 'const Temporal *') -> "Tuple['Temporal **', 'int'] return result if result != _ffi.NULL else None, count[0] +def tpoint_round(temp: 'const Temporal *', maxdd: int) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tpoint_round(temp_converted, maxdd) + _check_error() + return result if result != _ffi.NULL else None + + +def tpointarr_round(temp: 'const Temporal **', count: int, maxdd: int) -> 'Temporal **': + temp_converted = [_ffi.cast('const Temporal *', x) for x in temp] + result = _lib.tpointarr_round(temp_converted, count, maxdd) + _check_error() + return result if result != _ffi.NULL else None + + def tpoint_set_srid(temp: 'const Temporal *', srid: int) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) srid_converted = _ffi.cast('int32', srid) @@ -6922,10 +7780,21 @@ def tpoint_set_srid(temp: 'const Temporal *', srid: int) -> 'Temporal *': return result if result != _ffi.NULL else None -def econtains_geo_tpoint(geo: 'const GSERIALIZED *', temp: 'const Temporal *') -> 'int': - geo_converted = _ffi.cast('const GSERIALIZED *', geo) +def tpoint_to_geo_meas(tpoint: 'const Temporal *', measure: 'const Temporal *', segmentize: bool) -> 'GSERIALIZED **': + tpoint_converted = _ffi.cast('const Temporal *', tpoint) + measure_converted = _ffi.cast('const Temporal *', measure) + out_result = _ffi.new('GSERIALIZED **') + result = _lib.tpoint_to_geo_meas(tpoint_converted, measure_converted, segmentize, out_result) + _check_error() + if result: + return out_result if out_result != _ffi.NULL else None + return None + + +def econtains_geo_tpoint(gs: 'const GSERIALIZED *', temp: 'const Temporal *') -> 'int': + gs_converted = _ffi.cast('const GSERIALIZED *', gs) temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.econtains_geo_tpoint(geo_converted, temp_converted) + result = _lib.econtains_geo_tpoint(gs_converted, temp_converted) _check_error() return result if result != _ffi.NULL else None @@ -6994,10 +7863,10 @@ def tcontains_geo_tpoint(gs: 'const GSERIALIZED *', temp: 'const Temporal *', re return result if result != _ffi.NULL else None -def tdisjoint_tpoint_geo(temp: 'const Temporal *', geo: 'const GSERIALIZED *', restr: bool, atvalue: bool) -> 'Temporal *': +def tdisjoint_tpoint_geo(temp: 'const Temporal *', gs: 'const GSERIALIZED *', restr: bool, atvalue: bool) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - geo_converted = _ffi.cast('const GSERIALIZED *', geo) - result = _lib.tdisjoint_tpoint_geo(temp_converted, geo_converted, restr, atvalue) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.tdisjoint_tpoint_geo(temp_converted, gs_converted, restr, atvalue) _check_error() return result if result != _ffi.NULL else None @@ -7018,10 +7887,10 @@ def tdwithin_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal *', return result if result != _ffi.NULL else None -def tintersects_tpoint_geo(temp: 'const Temporal *', geo: 'const GSERIALIZED *', restr: bool, atvalue: bool) -> 'Temporal *': +def tintersects_tpoint_geo(temp: 'const Temporal *', gs: 'const GSERIALIZED *', restr: bool, atvalue: bool) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - geo_converted = _ffi.cast('const GSERIALIZED *', geo) - result = _lib.tintersects_tpoint_geo(temp_converted, geo_converted, restr, atvalue) + gs_converted = _ffi.cast('const GSERIALIZED *', gs) + result = _lib.tintersects_tpoint_geo(temp_converted, gs_converted, restr, atvalue) _check_error() return result if result != _ffi.NULL else None @@ -7204,122 +8073,53 @@ def ttext_tmin_transfn(state: "Optional['SkipList *']", temp: 'const Temporal *' return result if result != _ffi.NULL else None -def float_bucket(value: float, size: float, origin: float) -> 'double': - result = _lib.float_bucket(value, size, origin) +def temporal_simplify_min_dist(temp: 'const Temporal *', dist: float) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.temporal_simplify_min_dist(temp_converted, dist) _check_error() return result if result != _ffi.NULL else None -def floatspan_bucket_list(bounds: 'const Span *', size: float, origin: float, newcount: 'int *') -> 'Span *': - bounds_converted = _ffi.cast('const Span *', bounds) - newcount_converted = _ffi.cast('int *', newcount) - result = _lib.floatspan_bucket_list(bounds_converted, size, origin, newcount_converted) +def temporal_simplify_min_tdelta(temp: 'const Temporal *', mint: 'const Interval *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + mint_converted = _ffi.cast('const Interval *', mint) + result = _lib.temporal_simplify_min_tdelta(temp_converted, mint_converted) _check_error() return result if result != _ffi.NULL else None -def int_bucket(value: int, size: int, origin: int) -> 'int': - result = _lib.int_bucket(value, size, origin) +def temporal_simplify_dp(temp: 'const Temporal *', eps_dist: float, synchronized: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.temporal_simplify_dp(temp_converted, eps_dist, synchronized) _check_error() return result if result != _ffi.NULL else None -def intspan_bucket_list(bounds: 'const Span *', size: int, origin: int, newcount: 'int *') -> 'Span *': - bounds_converted = _ffi.cast('const Span *', bounds) - newcount_converted = _ffi.cast('int *', newcount) - result = _lib.intspan_bucket_list(bounds_converted, size, origin, newcount_converted) +def temporal_simplify_max_dist(temp: 'const Temporal *', eps_dist: float, synchronized: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.temporal_simplify_max_dist(temp_converted, eps_dist, synchronized) _check_error() return result if result != _ffi.NULL else None -def period_bucket_list(bounds: 'const Span *', duration: 'const Interval *', origin: int, newcount: 'int *') -> 'Span *': - bounds_converted = _ffi.cast('const Span *', bounds) +def temporal_tprecision(temp: 'const Temporal *', duration: 'const Interval *', origin: int) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) duration_converted = _ffi.cast('const Interval *', duration) origin_converted = _ffi.cast('TimestampTz', origin) - newcount_converted = _ffi.cast('int *', newcount) - result = _lib.period_bucket_list(bounds_converted, duration_converted, origin_converted, newcount_converted) + result = _lib.temporal_tprecision(temp_converted, duration_converted, origin_converted) _check_error() return result if result != _ffi.NULL else None -def stbox_tile_list(bounds: 'const STBox *', xsize: float, ysize: float, zsize: float, duration: "Optional['const Interval *']", sorigin: 'GSERIALIZED *', torigin: int) -> "Tuple['STBox *', 'int *']": - bounds_converted = _ffi.cast('const STBox *', bounds) - duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL - sorigin_converted = _ffi.cast('GSERIALIZED *', sorigin) - torigin_converted = _ffi.cast('TimestampTz', torigin) - cellcount = _ffi.new('int **') - result = _lib.stbox_tile_list(bounds_converted, xsize, ysize, zsize, duration_converted, sorigin_converted, torigin_converted, cellcount) - _check_error() - return result if result != _ffi.NULL else None, cellcount[0] - - -def tbox_tile_list(bounds: 'const TBox *', xsize: float, duration: 'const Interval *', xorigin: 'Optional[float]', torigin: "Optional[int]") -> "Tuple['TBox *', 'int', 'int']": - bounds_converted = _ffi.cast('const TBox *', bounds) - duration_converted = _ffi.cast('const Interval *', duration) - xorigin_converted = xorigin if xorigin is not None else _ffi.NULL - torigin_converted = _ffi.cast('TimestampTz', torigin) if torigin is not None else _ffi.NULL - rows = _ffi.new('int *') - columns = _ffi.new('int *') - result = _lib.tbox_tile_list(bounds_converted, xsize, duration_converted, xorigin_converted, torigin_converted, rows, columns) - _check_error() - return result if result != _ffi.NULL else None, rows[0], columns[0] - - -def temporal_time_split(temp: 'Temporal *', duration: 'Interval *', torigin: int) -> "Tuple['Temporal **', 'int']": - temp_converted = _ffi.cast('Temporal *', temp) - duration_converted = _ffi.cast('Interval *', duration) - torigin_converted = _ffi.cast('TimestampTz', torigin) - newcount = _ffi.new('int *') - result = _lib.temporal_time_split(temp_converted, duration_converted, torigin_converted, newcount) - _check_error() - return result if result != _ffi.NULL else None, newcount[0] - - -def tfloat_value_split(temp: 'Temporal *', size: float, origin: float) -> "Tuple['Temporal **', 'int']": - temp_converted = _ffi.cast('Temporal *', temp) - newcount = _ffi.new('int *') - result = _lib.tfloat_value_split(temp_converted, size, origin, newcount) - _check_error() - return result if result != _ffi.NULL else None, newcount[0] - - -def tfloat_value_time_split(temp: 'Temporal *', size: float, vorigin: float, duration: 'Interval *', torigin: int) -> "Tuple['Temporal **', 'int']": - temp_converted = _ffi.cast('Temporal *', temp) - duration_converted = _ffi.cast('Interval *', duration) - torigin_converted = _ffi.cast('TimestampTz', torigin) - newcount = _ffi.new('int *') - result = _lib.tfloat_value_time_split(temp_converted, size, vorigin, duration_converted, torigin_converted, newcount) - _check_error() - return result if result != _ffi.NULL else None, newcount[0] - - -def timestamptz_bucket(timestamp: int, duration: 'const Interval *', origin: int) -> 'TimestampTz': - timestamp_converted = _ffi.cast('TimestampTz', timestamp) +def temporal_tsample(temp: 'const Temporal *', duration: 'const Interval *', origin: int) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) duration_converted = _ffi.cast('const Interval *', duration) origin_converted = _ffi.cast('TimestampTz', origin) - result = _lib.timestamptz_bucket(timestamp_converted, duration_converted, origin_converted) + result = _lib.temporal_tsample(temp_converted, duration_converted, origin_converted) _check_error() return result if result != _ffi.NULL else None -def tint_value_split(temp: 'Temporal *', size: int, origin: int) -> "Tuple['Temporal **', 'int']": - temp_converted = _ffi.cast('Temporal *', temp) - newcount = _ffi.new('int *') - result = _lib.tint_value_split(temp_converted, size, origin, newcount) - _check_error() - return result if result != _ffi.NULL else None, newcount[0] - - -def tint_value_time_split(temp: 'Temporal *', size: int, vorigin: int, duration: 'Interval *', torigin: int) -> "Tuple['Temporal **', 'int']": - temp_converted = _ffi.cast('Temporal *', temp) - duration_converted = _ffi.cast('Interval *', duration) - torigin_converted = _ffi.cast('TimestampTz', torigin) - newcount = _ffi.new('int *') - result = _lib.tint_value_time_split(temp_converted, size, vorigin, duration_converted, torigin_converted, newcount) - _check_error() - return result if result != _ffi.NULL else None, newcount[0] - - def temporal_dyntimewarp_distance(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'double': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) @@ -7362,63 +8162,165 @@ def temporal_hausdorff_distance(temp1: 'const Temporal *', temp2: 'const Tempora return result if result != _ffi.NULL else None -def geo_to_tpoint(geo: 'const GSERIALIZED *') -> 'Temporal *': - geo_converted = _ffi.cast('const GSERIALIZED *', geo) - result = _lib.geo_to_tpoint(geo_converted) +def float_bucket(value: float, size: float, origin: float) -> 'double': + result = _lib.float_bucket(value, size, origin) _check_error() return result if result != _ffi.NULL else None -def temporal_simplify_min_dist(temp: 'const Temporal *', dist: float) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.temporal_simplify_min_dist(temp_converted, dist) +def floatspan_bucket_list(bounds: 'const Span *', size: float, origin: float) -> "Tuple['Span *', 'int']": + bounds_converted = _ffi.cast('const Span *', bounds) + count = _ffi.new('int *') + result = _lib.floatspan_bucket_list(bounds_converted, size, origin, count) _check_error() - return result if result != _ffi.NULL else None + return result if result != _ffi.NULL else None, count[0] -def temporal_simplify_min_tdelta(temp: 'const Temporal *', mint: 'const Interval *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - mint_converted = _ffi.cast('const Interval *', mint) - result = _lib.temporal_simplify_min_tdelta(temp_converted, mint_converted) +def int_bucket(value: int, size: int, origin: int) -> 'int': + result = _lib.int_bucket(value, size, origin) _check_error() return result if result != _ffi.NULL else None -def temporal_simplify_dp(temp: 'const Temporal *', eps_dist: float, synchronized: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.temporal_simplify_dp(temp_converted, eps_dist, synchronized) +def intspan_bucket_list(bounds: 'const Span *', size: int, origin: int) -> "Tuple['Span *', 'int']": + bounds_converted = _ffi.cast('const Span *', bounds) + count = _ffi.new('int *') + result = _lib.intspan_bucket_list(bounds_converted, size, origin, count) _check_error() - return result if result != _ffi.NULL else None + return result if result != _ffi.NULL else None, count[0] -def temporal_simplify_max_dist(temp: 'const Temporal *', eps_dist: float, synchronized: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.temporal_simplify_max_dist(temp_converted, eps_dist, synchronized) +def period_bucket_list(bounds: 'const Span *', duration: 'const Interval *', origin: int) -> "Tuple['Span *', 'int']": + bounds_converted = _ffi.cast('const Span *', bounds) + duration_converted = _ffi.cast('const Interval *', duration) + origin_converted = _ffi.cast('TimestampTz', origin) + count = _ffi.new('int *') + result = _lib.period_bucket_list(bounds_converted, duration_converted, origin_converted, count) _check_error() - return result if result != _ffi.NULL else None + return result if result != _ffi.NULL else None, count[0] -def tpoint_AsMVTGeom(temp: 'const Temporal *', bounds: 'const STBox *', extent: 'int32_t', buffer: 'int32_t', clip_geom: bool, geom: 'GSERIALIZED **', timesarr: 'int64 **') -> "Tuple['bool', 'int']": - temp_converted = _ffi.cast('const Temporal *', temp) +def stbox_tile_list(bounds: 'const STBox *', xsize: float, ysize: float, zsize: float, duration: "Optional['const Interval *']", sorigin: 'GSERIALIZED *', torigin: int) -> "Tuple['STBox *', 'int']": bounds_converted = _ffi.cast('const STBox *', bounds) - extent_converted = _ffi.cast('int32_t', extent) - buffer_converted = _ffi.cast('int32_t', buffer) - geom_converted = [_ffi.cast('GSERIALIZED *', x) for x in geom] - timesarr_converted = [_ffi.cast('int64 *', x) for x in timesarr] + duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL + sorigin_converted = _ffi.cast('GSERIALIZED *', sorigin) + torigin_converted = _ffi.cast('TimestampTz', torigin) count = _ffi.new('int *') - result = _lib.tpoint_AsMVTGeom(temp_converted, bounds_converted, extent_converted, buffer_converted, clip_geom, geom_converted, timesarr_converted, count) + result = _lib.stbox_tile_list(bounds_converted, xsize, ysize, zsize, duration_converted, sorigin_converted, torigin_converted, count) _check_error() return result if result != _ffi.NULL else None, count[0] -def tpoint_to_geo_meas(tpoint: 'const Temporal *', measure: 'const Temporal *', segmentize: bool) -> 'GSERIALIZED **': - tpoint_converted = _ffi.cast('const Temporal *', tpoint) - measure_converted = _ffi.cast('const Temporal *', measure) - out_result = _ffi.new('GSERIALIZED **') - result = _lib.tpoint_to_geo_meas(tpoint_converted, measure_converted, segmentize, out_result) +def tintbox_tile_list(box: 'const TBox *', xsize: int, duration: 'const Interval *', xorigin: 'Optional[int]', torigin: "Optional[int]") -> "Tuple['TBox *', 'int']": + box_converted = _ffi.cast('const TBox *', box) + duration_converted = _ffi.cast('const Interval *', duration) + xorigin_converted = xorigin if xorigin is not None else _ffi.NULL + torigin_converted = _ffi.cast('TimestampTz', torigin) if torigin is not None else _ffi.NULL + count = _ffi.new('int *') + result = _lib.tintbox_tile_list(box_converted, xsize, duration_converted, xorigin_converted, torigin_converted, count) _check_error() - if result: - return out_result if out_result != _ffi.NULL else None - return None + return result if result != _ffi.NULL else None, count[0] + + +def tfloatbox_tile_list(box: 'const TBox *', xsize: float, duration: 'const Interval *', xorigin: 'Optional[float]', torigin: "Optional[int]") -> "Tuple['TBox *', 'int']": + box_converted = _ffi.cast('const TBox *', box) + duration_converted = _ffi.cast('const Interval *', duration) + xorigin_converted = xorigin if xorigin is not None else _ffi.NULL + torigin_converted = _ffi.cast('TimestampTz', torigin) if torigin is not None else _ffi.NULL + count = _ffi.new('int *') + result = _lib.tfloatbox_tile_list(box_converted, xsize, duration_converted, xorigin_converted, torigin_converted, count) + _check_error() + return result if result != _ffi.NULL else None, count[0] + + +def temporal_time_split(temp: 'Temporal *', duration: 'Interval *', torigin: int) -> "Tuple['Temporal **', 'TimestampTz *', 'int']": + temp_converted = _ffi.cast('Temporal *', temp) + duration_converted = _ffi.cast('Interval *', duration) + torigin_converted = _ffi.cast('TimestampTz', torigin) + time_buckets = _ffi.new('TimestampTz **') + count = _ffi.new('int *') + result = _lib.temporal_time_split(temp_converted, duration_converted, torigin_converted, time_buckets, count) + _check_error() + return result if result != _ffi.NULL else None, time_buckets[0], count[0] + + +def tfloat_value_split(temp: 'Temporal *', size: float, origin: float) -> "Tuple['Temporal **', 'double *', 'int']": + temp_converted = _ffi.cast('Temporal *', temp) + value_buckets = _ffi.new('double **') + count = _ffi.new('int *') + result = _lib.tfloat_value_split(temp_converted, size, origin, value_buckets, count) + _check_error() + return result if result != _ffi.NULL else None, value_buckets[0], count[0] + + +def tfloat_value_time_split(temp: 'Temporal *', size: float, duration: 'Interval *', vorigin: float, torigin: int) -> "Tuple['Temporal **', 'double *', 'TimestampTz *', 'int']": + temp_converted = _ffi.cast('Temporal *', temp) + duration_converted = _ffi.cast('Interval *', duration) + torigin_converted = _ffi.cast('TimestampTz', torigin) + value_buckets = _ffi.new('double **') + time_buckets = _ffi.new('TimestampTz **') + count = _ffi.new('int *') + result = _lib.tfloat_value_time_split(temp_converted, size, duration_converted, vorigin, torigin_converted, value_buckets, time_buckets, count) + _check_error() + return result if result != _ffi.NULL else None, value_buckets[0], time_buckets[0], count[0] + + +def timestamptz_bucket(timestamp: int, duration: 'const Interval *', origin: int) -> 'TimestampTz': + timestamp_converted = _ffi.cast('TimestampTz', timestamp) + duration_converted = _ffi.cast('const Interval *', duration) + origin_converted = _ffi.cast('TimestampTz', origin) + result = _lib.timestamptz_bucket(timestamp_converted, duration_converted, origin_converted) + _check_error() + return result if result != _ffi.NULL else None + + +def tint_value_split(temp: 'Temporal *', size: int, origin: int) -> "Tuple['Temporal **', 'int *', 'int']": + temp_converted = _ffi.cast('Temporal *', temp) + value_buckets = _ffi.new('int **') + count = _ffi.new('int *') + result = _lib.tint_value_split(temp_converted, size, origin, value_buckets, count) + _check_error() + return result if result != _ffi.NULL else None, value_buckets[0], count[0] + + +def tint_value_time_split(temp: 'Temporal *', size: int, duration: 'Interval *', vorigin: int, torigin: int) -> "Tuple['Temporal **', 'int *', 'TimestampTz *', 'int']": + temp_converted = _ffi.cast('Temporal *', temp) + duration_converted = _ffi.cast('Interval *', duration) + torigin_converted = _ffi.cast('TimestampTz', torigin) + value_buckets = _ffi.new('int **') + time_buckets = _ffi.new('TimestampTz **') + count = _ffi.new('int *') + result = _lib.tint_value_time_split(temp_converted, size, duration_converted, vorigin, torigin_converted, value_buckets, time_buckets, count) + _check_error() + return result if result != _ffi.NULL else None, value_buckets[0], time_buckets[0], count[0] + + +def tpoint_space_split(temp: 'Temporal *', xsize: 'float', ysize: 'float', zsize: 'float', sorigin: 'GSERIALIZED *', bitmatrix: bool) -> "Tuple['Temporal **', 'GSERIALIZED ***', 'int']": + temp_converted = _ffi.cast('Temporal *', temp) + xsize_converted = _ffi.cast('float', xsize) + ysize_converted = _ffi.cast('float', ysize) + zsize_converted = _ffi.cast('float', zsize) + sorigin_converted = _ffi.cast('GSERIALIZED *', sorigin) + space_buckets = _ffi.new('GSERIALIZED ***') + count = _ffi.new('int *') + result = _lib.tpoint_space_split(temp_converted, xsize_converted, ysize_converted, zsize_converted, sorigin_converted, bitmatrix, space_buckets, count) + _check_error() + return result if result != _ffi.NULL else None, space_buckets[0], count[0] + + +def tpoint_space_time_split(temp: 'Temporal *', xsize: 'float', ysize: 'float', zsize: 'float', duration: 'Interval *', sorigin: 'GSERIALIZED *', torigin: int, bitmatrix: bool) -> "Tuple['Temporal **', 'GSERIALIZED ***', 'TimestampTz *', 'int']": + temp_converted = _ffi.cast('Temporal *', temp) + xsize_converted = _ffi.cast('float', xsize) + ysize_converted = _ffi.cast('float', ysize) + zsize_converted = _ffi.cast('float', zsize) + duration_converted = _ffi.cast('Interval *', duration) + sorigin_converted = _ffi.cast('GSERIALIZED *', sorigin) + torigin_converted = _ffi.cast('TimestampTz', torigin) + space_buckets = _ffi.new('GSERIALIZED ***') + time_buckets = _ffi.new('TimestampTz **') + count = _ffi.new('int *') + result = _lib.tpoint_space_time_split(temp_converted, xsize_converted, ysize_converted, zsize_converted, duration_converted, sorigin_converted, torigin_converted, bitmatrix, space_buckets, time_buckets, count) + _check_error() + return result if result != _ffi.NULL else None, space_buckets[0], time_buckets[0], count[0] diff --git a/pymeos_cffi/pyproject.toml b/pymeos_cffi/pyproject.toml index 8a74b572..67a2b511 100644 --- a/pymeos_cffi/pyproject.toml +++ b/pymeos_cffi/pyproject.toml @@ -7,7 +7,7 @@ py-modules = [] [project] name = 'pymeos_cffi' -version = '1.1.0-alpha.4' +version = '1.1.0-alpha.7' authors = [ { name = 'Victor Divi', email = 'vdiviloper@gmail.com' } ] @@ -37,8 +37,6 @@ requires-python = '>=3.7' dependencies = [ 'cffi', 'python-dateutil', - 'spans', - 'postgis', 'shapely' ] diff --git a/pymeos_cffi/test.py b/pymeos_cffi/test.py deleted file mode 100644 index 0f2c6d35..00000000 --- a/pymeos_cffi/test.py +++ /dev/null @@ -1,12 +0,0 @@ -from pymeos_cffi import * - -meos_initialize('UTC') - -t = tfloat_in('2.0@2000-01-01') - -try: - print(tfloat_out(t, -2)) -except MeosInvalidArgValueError as e: - print(e) - -print(tfloat_out(t, 2)) \ No newline at end of file diff --git a/pymeos_examples/PyMEOS Examples/AIS.ipynb b/pymeos_examples/PyMEOS Examples/AIS.ipynb index b875669e..813a3f97 100644 --- a/pymeos_examples/PyMEOS Examples/AIS.ipynb +++ b/pymeos_examples/PyMEOS Examples/AIS.ipynb @@ -32,7 +32,11 @@ "pymeos_initialize()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:39.780045600Z", + "start_time": "2023-09-26T09:14:39.136882600Z" + } } }, { @@ -74,7 +78,11 @@ "ais.head()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:39.883043Z", + "start_time": "2023-09-26T09:14:39.773045900Z" + } } }, { @@ -92,8 +100,8 @@ "outputs": [ { "data": { - "text/plain": " t mmsi sog \n0 2021-01-08 00:00:00 265513270 0@2021-01-08 00:00:00+01 \\\n1 2021-01-08 00:00:01 219027804 0@2021-01-08 00:00:01+01 \n2 2021-01-08 00:00:01 265513270 0@2021-01-08 00:00:01+01 \n3 2021-01-08 00:00:03 219027804 0@2021-01-08 00:00:03+01 \n4 2021-01-08 00:00:04 265513270 0@2021-01-08 00:00:04+01 \n\n point \n0 0101000020E6100000643BDF4F8D874C404CA59F70768B... \n1 0101000020E61000009B38B9DFA1F84B40137D3ECA88BB... \n2 0101000020E6100000643BDF4F8D874C404CA59F70768B... \n3 0101000020E61000009B38B9DFA1F84B40137D3ECA88BB... \n4 0101000020E6100000643BDF4F8D874C404CA59F70768B... ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
tmmsisogpoint
02021-01-08 00:00:002655132700@2021-01-08 00:00:00+010101000020E6100000643BDF4F8D874C404CA59F70768B...
12021-01-08 00:00:012190278040@2021-01-08 00:00:01+010101000020E61000009B38B9DFA1F84B40137D3ECA88BB...
22021-01-08 00:00:012655132700@2021-01-08 00:00:01+010101000020E6100000643BDF4F8D874C404CA59F70768B...
32021-01-08 00:00:032190278040@2021-01-08 00:00:03+010101000020E61000009B38B9DFA1F84B40137D3ECA88BB...
42021-01-08 00:00:042655132700@2021-01-08 00:00:04+010101000020E6100000643BDF4F8D874C404CA59F70768B...
\n
" + "text/plain": " t mmsi sog \\\n0 2021-01-08 00:00:00 265513270 0@2021-01-08 00:00:00+01 \n1 2021-01-08 00:00:01 219027804 0@2021-01-08 00:00:01+01 \n2 2021-01-08 00:00:01 265513270 0@2021-01-08 00:00:01+01 \n3 2021-01-08 00:00:03 219027804 0@2021-01-08 00:00:03+01 \n4 2021-01-08 00:00:04 265513270 0@2021-01-08 00:00:04+01 \n\n point \n0 POINT(57.059 12.272388)@2021-01-08 00:00:00+01 \n1 POINT(55.94244 11.866278)@2021-01-08 00:00:01+01 \n2 POINT(57.059 12.272388)@2021-01-08 00:00:01+01 \n3 POINT(55.94244 11.866278)@2021-01-08 00:00:03+01 \n4 POINT(57.059 12.272388)@2021-01-08 00:00:04+01 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
tmmsisogpoint
02021-01-08 00:00:002655132700@2021-01-08 00:00:00+01POINT(57.059 12.272388)@2021-01-08 00:00:00+01
12021-01-08 00:00:012190278040@2021-01-08 00:00:01+01POINT(55.94244 11.866278)@2021-01-08 00:00:01+01
22021-01-08 00:00:012655132700@2021-01-08 00:00:01+01POINT(57.059 12.272388)@2021-01-08 00:00:01+01
32021-01-08 00:00:032190278040@2021-01-08 00:00:03+01POINT(55.94244 11.866278)@2021-01-08 00:00:03+01
42021-01-08 00:00:042655132700@2021-01-08 00:00:04+01POINT(57.059 12.272388)@2021-01-08 00:00:04+01
\n
" }, "execution_count": 3, "metadata": {}, @@ -108,7 +116,11 @@ "ais.head()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:43.943454700Z", + "start_time": "2023-09-26T09:14:39.900042800Z" + } } }, { @@ -128,8 +140,8 @@ "outputs": [ { "data": { - "text/plain": " trajectory \nmmsi \n219001559 [0101000020E6100000191C25AFCECB4C400BB6114F76F... \\\n219027804 [0101000020E61000009B38B9DFA1F84B40137D3ECA88B... \n257136000 [0101000020E610000023A0C211A4744C405320B3B3E87... \n265513270 [0101000020E6100000643BDF4F8D874C404CA59F70768... \n566948000 [0101000020E61000003332C85D84C94B405E83BEF4F67... \n\n sog distance \nmmsi \n219001559 [0@2021-01-08 00:00:05+01, 0@2021-01-08 23:59:... 17760.655571 \n219027804 [0@2021-01-08 00:00:01+01, 0@2021-01-08 10:04:... 94950.742960 \n257136000 [14@2021-01-08 00:02:57+01, 13@2021-01-08 00:0... 940404.131058 \n265513270 [0@2021-01-08 00:00:00+01, 0@2021-01-08 23:59:... 1626.095588 \n566948000 [0@2021-01-08 00:00:04+01, 0@2021-01-08 16:40:... 28213.629024 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trajectorysogdistance
mmsi
219001559[0101000020E6100000191C25AFCECB4C400BB6114F76F...[0@2021-01-08 00:00:05+01, 0@2021-01-08 23:59:...17760.655571
219027804[0101000020E61000009B38B9DFA1F84B40137D3ECA88B...[0@2021-01-08 00:00:01+01, 0@2021-01-08 10:04:...94950.742960
257136000[0101000020E610000023A0C211A4744C405320B3B3E87...[14@2021-01-08 00:02:57+01, 13@2021-01-08 00:0...940404.131058
265513270[0101000020E6100000643BDF4F8D874C404CA59F70768...[0@2021-01-08 00:00:00+01, 0@2021-01-08 23:59:...1626.095588
566948000[0101000020E61000003332C85D84C94B405E83BEF4F67...[0@2021-01-08 00:00:04+01, 0@2021-01-08 16:40:...28213.629024
\n
" + "text/plain": " trajectory \\\nmmsi \n219001559 [POINT(57.592245 9.975512)@2021-01-08 00:00:05... \n219027804 [POINT(55.94244 11.866278)@2021-01-08 00:00:01... \n257136000 [POINT(56.911257 7.122958)@2021-01-08 00:02:57... \n265513270 [POINT(57.059 12.272388)@2021-01-08 00:00:00+0... \n566948000 [POINT(55.574352 4.617153)@2021-01-08 00:00:04... \n\n sog distance \nmmsi \n219001559 [0.1@2021-01-08 00:00:05+01, 0.1@2021-01-08 00... 17765.588739 \n219027804 [0@2021-01-08 00:00:01+01, 0@2021-01-08 00:34:... 94963.575024 \n257136000 [14@2021-01-08 00:02:57+01, 13.9@2021-01-08 00... 940404.131214 \n265513270 [0@2021-01-08 00:00:00+01, 0@2021-01-08 00:00:... 1629.539165 \n566948000 [0.5@2021-01-08 00:00:04+01, 0.5@2021-01-08 00... 28215.190429 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trajectorysogdistance
mmsi
219001559[POINT(57.592245 9.975512)@2021-01-08 00:00:05...[0.1@2021-01-08 00:00:05+01, 0.1@2021-01-08 00...17765.588739
219027804[POINT(55.94244 11.866278)@2021-01-08 00:00:01...[0@2021-01-08 00:00:01+01, 0@2021-01-08 00:34:...94963.575024
257136000[POINT(56.911257 7.122958)@2021-01-08 00:02:57...[14@2021-01-08 00:02:57+01, 13.9@2021-01-08 00...940404.131214
265513270[POINT(57.059 12.272388)@2021-01-08 00:00:00+0...[0@2021-01-08 00:00:00+01, 0@2021-01-08 00:00:...1629.539165
566948000[POINT(55.574352 4.617153)@2021-01-08 00:00:04...[0.5@2021-01-08 00:00:04+01, 0.5@2021-01-08 00...28215.190429
\n
" }, "execution_count": 4, "metadata": {}, @@ -147,7 +159,11 @@ "trajectories.head()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:44.673517300Z", + "start_time": "2023-09-26T09:14:43.938455900Z" + } } }, { @@ -165,8 +181,8 @@ "outputs": [ { "data": { - "text/plain": " original #points PyMEOS #points\nmmsi \n219001559 48323 36431\n219027804 38326 17087\n257136000 21770 21119\n265513270 21799 7990\n566948000 26619 24148", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
original #pointsPyMEOS #points
mmsi
2190015594832336431
2190278043832617087
2571360002177021119
265513270217997990
5669480002661924148
\n
" + "text/plain": " original #points PyMEOS #points\nmmsi \n219001559 48323 36494\n219027804 38326 17161\n257136000 21770 21120\n265513270 21799 7954\n566948000 26619 24176", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
original #pointsPyMEOS #points
mmsi
2190015594832336494
2190278043832617161
2571360002177021120
265513270217997954
5669480002661924176
\n
" }, "execution_count": 5, "metadata": {}, @@ -179,7 +195,11 @@ " axis=1)" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:44.674518200Z", + "start_time": "2023-09-26T09:14:44.619518100Z" + } } }, { @@ -198,7 +218,7 @@ { "data": { "text/plain": "
", - "image/png": "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B34et8f/97/+/JckafEli2u834erPvT/7ZY7KLGkJaSVu9yofqOgPC4iC0UPAAAQE4p8RXro14f0wcoPJEkXdLtAEwdOVJybj0MAAABh06B5yb8BdYbHQIGgrGgZFeGpZ3cECLMre1+pbzd8W+v7eY1XkjS87fByhRMr7hp8l4ZsGCKf8aldw3Y6JPWQoDwuIgvf8gEAQNTLLMzUhO8naM7WOXK73Lpt4G26sPuFdocFAAAQeyrt2NxfAGFUQOyhzaNCev30Ot2vdA2Qnuk9gxZLi+QWuqD7BUF7PEQmih4AACCqbcrapPHfjteafWuUGJeofxzzDx3d5mi7wwIAAEBVomXEA2hLBITFxhFsFD0AAEDUWrhzoW6YeYN25+9Ws6RmeuG4F9StcTe7wwIAAIgx/JofQNVYbBzBFpwVYsrwer2655571LFjRyUmJurQQw/Vgw8+6B+uBAAAEA5fr/tal399uXbn71b3xt311ui3KHgAAAA4RblfdsdCh2c094tF874hEHUtWhiOHYRI0Ed6PPbYY5o6dapee+019ezZU/PmzdOll16q1NRU3XDDDcF+OgAAgHKMMfq/P/9Pz85/VpJ0bJtj9djRjykpPsnmyAAAAOAXqz+OZRofwK/0R/JMb4VgC3rR4+eff9app56qMWPGSJI6dOigt99+W7/99luwnwoAAKCcIm+RHvj1AX206iNJ0kXdL9KtA26Vp9IFMwEAABB+1XVuxmghJNrUqgObNo8GdS1aMNIDoRL0oseRRx6pl19+WStWrFCXLl20cOFCzZ49W0899VSl2xcUFKigoMB/OTMzU5JUVFSkoqKiYIeHECptL9otutHOsYF2jn7R2MaZhZm69cdbNW/7PLldbt12+G06p8s58nl98nl9dodni2hs52hFGwEAUFas/uo7Vvcbka7O01uVjvTg2EeQBb3occcddygzM1PdunWTx+OR1+vVww8/rAsvvLDS7R999FHdf//9Fa6fPn26kpKYhiISzZgxw+4QEAa0c2ygnaNftLTxDu8OvZnzpnb5dqme6um8pPPUYFUDfbHqC7tDc4Roaedolpuba3cIAACETmVTWTGdTYygnVG10pEeTG+FYAt60eO///2v3nzzTb311lvq2bOn/vjjD910001q1aqVLrnkkgrbT5o0SRMmTPBfzszMVNu2bXXiiSeqYcOG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efPsDiNq+Hw+u0NADNm6dav27NljdxhRgffeuiFvwRPKXMaF7JEBxBxjjFwul91hIMRo5+jn8/nkdvO7CABAaKxfv16zZ89WTk6OevfurcGDB8vlcvH+E6B169bps88+U2Zmpnr27KlTTz3V7pAiyqJFi3T66afrqquuUsuWLdW6dWu7Q4oo69at0y+//KK9e/eqW7duGj58uNxuN98RArRx40b9+uuv2rlzp/r376/BgwfbHVJEWbBggQ4//HB99dVXOvHEE+0OJyIVFRUpLi5OLpeL995a2rt3r5KSklSvXj3OeXUQ7s9/FD0QMkuXLtV3332n8ePH2x0KQig/P18+n09JSUn+Ez4n/+izaNEi/e9//9MDDzxA20apoqIixcfHS5L/Awev5ejDezMAuy1evFjDhw9Xjx49tHjxYrVt21adO3fW+++/L7fbTedLDRYtWqRRo0apb9++Wr58uVq0aCGPx6OxY8faHVpEWL16tY4//nhdeOGFmjBhgv+zTymOv+otXrxYxx13nAYPHqwlS5aoYcOGatGihT788EPVr1+fz441WLx4scaMGaNOnTpp/vz56tmzpy6++GJdc801docWERYuXKhjjjlGN998MwWPOlq2bJkmT56svXv3qn79+vroo4845wXor7/+0qWXXqrTTjtNN998sxISEjjn1YIdn/84shESf/zxhw4//HDl5OSUu54hYNHlzz//1OjRo3X00Udr0KBBevHFF7VlyxZ/pRbRYeHChRo8eHCFNuX1HD2WLl2qs88+WyNGjNBJJ52kL774Qnv27JHL5aKdowjvzQDslpOTo6uuukrnnnuuZs6cqeXLl+v222/XokWLNGjQIBUXF/u/+KKiFStW6KSTTtJll12mzz77TLNnz9bevXu1detWu0NzvNL3ujfffFPHHHOMnn76aXk8Hr300kt66KGH9Nhjj0kSnX/V2LVrly666CJddtll+uSTT/T777/rpptu0tdff60xY8YoIyOD74HVWLNmjU455RRddNFF+vzzz7V06VIdeuih+vrrr+0OLSL8+eefGjZsmMaPH68nn3xSPp9PCxYs0Oeff65FixbZHV5EWLJkiYYNG6akpCT169dPS5Ys0UUXXeS/ne8EVduwYYPOO+88rV69Wp9//rmmTp2qgoICvi8HyK7Pf7yjI+gWLlzofzO67bbbyt1GBTR6rFmzRkcffbQ6deqkG2+8UZ06ddL//d//6eqrr9aqVav4wholFi5cqKFDh2rcuHF66KGHyt1WdmQPItfKlSs1ZMgQpaamauTIkSooKNDEiRN13333afPmzXyQixK8NwNwgoKCAuXk5Gj06NGKi4tTs2bNdM455+iNN97Qnj17NGLECEnyT5WDAwoKCvTiiy9q5MiRuu++++RyudSyZUv17dtXixcv1sSJE/X000/bHaZjlb7Xbdy4UV26dJEkHXnkkXrzzTf16aef6oUXXlCPHj20adMmSaxRUZmNGzfKGKOrr75aktSoUSONGDFCXbt21eLFi3XyySdLonBUmaKiIv3nP//RgAEDNGnSJCUkJKhVq1a68sor9d1332ndunV2h+hoPp9P999/v3JycnTfffdJkk466SRdddVVOvnkk3XBBRfo/PPPtzlKZ8vOzta4ceN04YUX6p///KceeeQRXXHFFWrWrJl/G74TVM4Yo08//VStWrXS559/ri5duuidd94pV/jgPaN6dn3+490IQbV27VoNGzZMF198sf7xj3+oqKhIU6ZM0cSJE3XTTTfpr7/+UmFhod1hIgi+/PJLDRw4UC+//LIuvvhivfnmm5owYYJyc3N11VVXae3atXxhjXAbN27U0KFDdf755+sf//iHCgsL/R+Ozj//fH399dfat28fH44i3BtvvKHhw4frtdde0+23366ZM2fqoosu0ty5c3Xvvfdq27ZttHGE470ZgFM0bNhQxcXFmjlzpv+6+Ph4HXHEEXrllVe0bds23X333ZLofDmYx+PRueeeqxtuuEHx8fFyuVx6+OGH9fbbbys3N1erV6/WtGnTdN5559kdqqP5fD4tWrRI7777rtLS0vTZZ59p5syZmjNnjho2bKgzzzxTEh33Vdm7d68WL17sv5yTk6PExEQ9++yz2rJli5566ikbo3O2Ro0aadSoUUpJSfEfXy1atJDb7eZzWA3cbremTJmiAQMGaODAgTr66KNVr149vfDCC1q2bJluueUWzZ8/X+PGjbM7VMfKzs7W3r17/WtAuVwubdq0SV9//bWGDBmiYcOG6eeff5bEjxoP5nK5dOqpp+qKK67QEUccoWnTpqlnz556++239eKLLyovL4++rxrY9fmPd3IE1TfffKMmTZqoQYMG2rZtm8aOHau3335b8+bN0xdffKExY8bogw8+kNfrtTtUWJSVlaXly5crKyvLf92FF17o/6Dx97//XZmZmXxhjWALFy5Up06dlJGRoQ0bNujUU0/V559/rr1792rNmjW66aab9OKLL1aYKgeRJS8vT1u3blVBQYH/ukmTJuncc8/VkiVL9Nprr/FFLMLx3gzAKVwul8466yz9+uuv+uqrr8pdP3ToUJ100kmaN2+eiouLbYzSmeLi4tS/f3/17dtXUslUV88//7w++eQTvfrqq/rggw908803a968eVq5cqW9wTrYxRdfrF27dunZZ59V+/bt1bBhQyUmJqply5Z65plntHXrVv3+++92h+lIzZs31yGHHKLXX39dTz31lL766isNGTJEw4cP1/nnn68BAwZo+fLldofpOMYYxcfH629/+5suv/xySQdGErVo0UJNmzZVXNyB5XbLdgrigBYtWuizzz5TcnKydu/erRdeeEFHHHGEunTpoosuukhnnXWW5s6dq927d9sdqiOlpaUpPz9fTz75pFasWKE777xTr7zyii677DLdcsstatSokc477zzt2rWLPpxKtGrVyl8Uj4+P1wsvvKBevXrpnXfe0bRp05Sfny+Xy6U33njD5kidya7PfxQ9EFRXXnmlbrrpJv3000867LDD5Ha79f777+ubb77RihUr1K9fP911113Kzc21O1RY1LNnTzVo0EC//fZbuYr2mWeeqTFjxmjGjBnauXOnjRHCqrFjx2ry5Mnas2ePOnfuLJfLpQ8//FD/+9//NGfOHI0aNUovvfSSduzYYXeosKBNmzbat2+ffzqH0g8aN910kwYNGqSXXnpJeXl5doYIi3hvBmCXbdu2afbs2fr111+1c+dOeTweXXzxxfJ6vXr++ec1a9Ys/7ZxcXHq27ev1q5dW+5HNbGsNH+//PKLMjIylJCQ4L+tS5cuWrRokcaOHevvQE1PT1d8fLxSU1PtCtlRyh5/GRkZkqQePXqoU6dO+u2337R+/XpJB0Z1JCYmKjk5WUlJSbbF7CRlj78dO3aoZcuWevbZZ1VcXKwXX3xR119/vcaNG6cnn3xSktSsWTNt3LjR5qido/RHQ8YYGWOUlpbmv1x6zOXl5Wnfvn3+Hx/dc889uvjii1mnR+XzV6pZs2b69NNP9fjjj6tFixaSSgpI8fHxatmypXJzcxUfH29LvE5mjFFCQoKeeeYZLV26VBMmTNDUqVP10ksv6ZZbbtFZZ52ljz76SJmZmXrvvffsDtcRdu/eraVLl2rp0qXKzMwsN3271+tV/fr1NWXKFH/h48UXX9S1116rSy+9VBs2bLA5evs55vOfAYLE6/X6/37yySfNWWedZebNm1futj179hiPx2Pef/99W2JEcB155JGmb9++Zs2aNRVuS09PN88884wNUSEYfD6f/+///ve/5sorrzQ//fSTMebA69nn85l69eqZV155xZYYERxer9d069bNnHDCCaa4uNgYY0xRUZExxpji4mLToEED8+abb9oZIizgvRmAXRYuXGg6dOhgDj30UNO6dWvTpk0b8/HHHxtjjFm8eLHp2bOnGT16tHn99deNMSXvPTfeeKMZMWKEycnJsTN0Rzg4f23btjWfffaZKSws9G9T9hxvjDG33HKLGTt2rMnKygp3uI5T2fH3ySefGGOM2bBhgzn11FNNQkKCGT9+vDHGmN27d5sHHnjA9OvXz+zYscPO0B2hsvx99NFHxhhjsrOzzZ49e8p9B/R6veb00083t99+u10hO8rSpUvNsccea37++WdjTPnvVmWtW7fONGjQwKxevdo8/PDDJiEhwf85LZYFmr+yrr32WnPOOeeY/Pz8UIcXEQoKCowxJbkrm7+CggKzadMm06dPH/PXX38ZY4wpLCw0mzdvNn379jWffvqpLfE6yaJFi8yAAQNMly5dTPv27c0ZZ5xhtmzZUm6b0u/NeXl55vLLLzcJCQmmYcOGZv78+XaE7ChO+vxH0QNBVfaD95w5c8q94fh8PjN//nzTrVs388cff9gRHoKk9AS/d+9e07VrVzNo0CDz559/+m/PyckxgwcPNu+8845dISIIyn44+vPPP/0fnIwpea2vXLnS9O7d2/z44492hIcgKH0t//HHH6Zly5ZmzJgxJjMz03/7jh07TO/evc306dPtChFBwHszgHDbsWOH6dSpk7n99tvNhg0bzJw5c8y1115rPB6P+cc//mGMMWbJkiXm1FNPNZ07dzYdOnQwI0aMMI0aNTILFiywN3gHqCp/cXFx5umnnzbZ2dnltt+9e7eZNGmSSU9PN4sXL7YpaueoLn+lx9/GjRvNLbfcYlq0aGHS0tLM4Ycfbpo3b06Hlan+9fvkk09WKKqtWrXK3HnnnSYtLc3fiRrL1q5daw499FCTlpZmBg4caH755RdjTOUd97t37zb9+/c3Z5xxhqlfvz4FD1O7/BljzKZNm8wdd9zB+a+MyopGBxc++vfvbx555BFjTEnR48EHHzSdOnUyGzZssCVmp1i2bJlp2rSpmThxolmwYIF59dVXzbHHHuv/QW/ZPJZ+x7rmmmtMWlpauT6xWOW0z38UPRB01VXh77rrLjNw4ECzffv2MEaEUCg9wW/cuNH07NnTdO/e3TzyyCPmo48+MhMnTjSNGzc2q1evtjlKWFXd6/nee+81vXv3Nps3bw5jRKirmn719OOPP5p27dqZgQMHmrffftv88MMP5s477zTNmzc369atC1OUsKqqdua9GUA4rVy50nTt2rXCF9hHHnnEuFwuM3XqVGOMMZs3bzZz5swx9913n3nllVfMihUrbIjWearLn9vtNi+//LIxpuTz+Ndff22uuuoq06FDBwpG+wV6/O3bt89s2rTJvPzyy+bzzz/n885+tTn+tm3bZu69917Ttm1bCkam5HPYuHHjzJlnnmnefPNNc8YZZ5h+/fpV2XG/ZcsWExcXZxo0aMDr19Q+fz/88IO54oorTLt27cjffjUVjXw+n8nPzze33XabOeyww0z37t3N2LFjTbNmzWI+h1lZWeacc84xV155ZbnrL7jgAjNixIhK7/PSSy8Zl8vF+W8/p33+o+iBOlm/fn2tfsUxffp0M3HiRJOSksIvSSPI6tWrzaxZs2rcrri42Fx55ZVmyJAh5pBDDjGDBw/mpB9BAm3nUp999pm5+eabTWpqasx/MIoUy5YtM7fccku5KTEqs3XrVjNq1CjTrVs30759e9O7d2/z+++/hylKWBVoO5fivRlAqMybN8/Uq1fPLFy40Bhjyp2X7r333nK3oaKa8peQkOD/RfOWLVvM66+/btauXWtHqI4UyPG3aNEiu8JzvNocf0VFRWbdunX8CKqMjz76yD/9748//mhOP/30Kjvu9+3bZ2688UazfPlyW2J1otrkb+fOnebDDz+kYLlfIEWj0h+vbtu2zbz33nvmyiuvNI899hg/OjAlx9MNN9xg3n77bWPMgVkR3n//fXPUUUeZ4uJi/3VlVTbde6xy2uc/lzFlVgUCArBgwQKNHDlSL774os4666xKtzHGyOVySZLy8/N12223adasWfrPf/6j3r17hzNc1NGiRYs0atQojR49Wo888oiaNWtWYRuzf1G20oXY9u3bp7y8PCUlJalhw4bhDhl1EGg7l76eJemOO+7Q7NmzNXXqVPXq1Suc4aIOFi1apEGDBqmgoECffvqpxowZU2Gbg9t4/fr18nq9Sk1NVXp6ejjDRR3Vtp15bwYQaqNGjVJOTo4+/vhjNW7cWEVFRYqPj5fX69Xo0aPVpk0bvfTSS3K73f7PkjggkPxNmzZN8fHxFd7HEVj+Xn75ZblcLo6/SgT6+o2Li7M7VMebNWuWnnvuOa1Zs0ZTp07V4MGDVVBQoHXr1qlr167+3KJyleUvPz9f69evV9euXTn/HeTjjz/Wzp07dcUVV2j27Nl66qmntG7dOr344osaPHhwhT4cHGCM0dy5c3XEEUf4L7tcLn388cd64IEHNGfOHHk8HrlcLmVmZtLnVQUnff7jKEetLFy4UEcddZQuuuiiKgsePp/P/6aTm5ur+vXr6/HHH9eMGTPoVIkQa9eu1ciRI3XRRRfplVdeqbQjvLi42P8lYceOHZKk1NRUtWjRgpN/hKhNO0vyt/Pf//53ffLJJxQ8IsDChQs1ePBgXX755Tr33HP19ttvKzc3V2V/71D2i0JWVpYkqX379jrkkEMoeESI2rYz780AwmHcuHHyer2aOHGi9u7dq/j4ePl8Pnk8HrVs2VIZGRmKi4uj46UKgeSvtKOUDr+KAsmfx+Ph+KtCoK9fVM3n80mSjjnmGN1www065JBDNG7cOM2ePVsTJ07Ucccdp+zsbPJYheryd9ttt+n4449XdnY257+DnHrqqbriiiskScOGDdONN96ojh076tprr9Wvv/4ql8ulwsJCrVixwuZIncflclUoeEgl352ys7P9BY+7775bY8aMUVFRkZ3hOpaTPv/xDo+ALVu2TEceeaRuvPFGPfXUUyouLtasWbP00Ucf6ccff/RvV3rgTpgwQY8//rh27dql+vXrV9qhCmeaPXu2jjzySD3++OMqLi7WY489pssvv1z33HOPvvvuO0nyfzibPHmyJk2apDVr1tgZMuqgLu28cuVKSVLjxo1tixuBmT9/vo466ihNmDBBzz//vAYPHqxPP/1UW7Zskcvl8neIl36YmzBhgp588knt2bPHzrBRS3VpZ96bAYTDmDFjdOaZZ2rJkiUaN26c9uzZ4/+eEB8fr0aNGqmoqEhMPFA58mcN+bOG/Fnndrv9+SnbcT98+HC9/vrr+uCDD9SgQQM67atQU/7ef/99NWjQwOYonaumolFp0Q2VK/u6TE1NVWJior/g8dRTT+npp59mhFYVnPT+QUkZNTLGqKioSHfeeaeSk5N1yimnSJLOOOMMbdiwQdu2bdPu3bt11VVX6b777lPTpk0llZwkpkyZouuvv97O8FEHCxYsUF5eniTpxBNPVGFhodq3b6/33ntP3333nS666CJdc801kqSkpCT99NNPSk5OtjNk1EFd2plRPJFhz549Ouqoo3TttdfqoYceklTyi4s33nhDDz74oP79739X+ILFOTvy0M4AnMTr9crj8UiS/xd9N910k5KSkvTGG2+oe/fuGjt2rHbt2qVvvvlGv/zyCx0GZZA/a8ifNeTPmrL5K6v0Bygul0vHHHOMnnjiCTVo0ECzZ89Wz549bYjUmchf8JUWjUpzJ0lTpkzR8OHDlZycrOnTp1M02q+q469UaUf9rbfeqilTpujnn3/W4YcfHsYIna3sqBinvX+wpgdqVHoC+P3333XXXXdJKpnvvUOHDnrkkUeUnp6uP//8U6effrpuueUWPfLII/777ty5018EgbOVPdH/+9//1qeffqpzzjlHr776qt544w01b95c27Zt0x133KHNmzfrrbfe8rftnj17lJaWZmf4CBDtHP1K23jBggXq16+fpJIPIj6fT/fee68+/vhjfffdd2ratGmFUQCcsyMH7QzAbjt37tTOnTuVnZ3tnw7C5/P5f81X+rcxRqtWrdJrr72mtWvXqlGjRho/frx69OhhZ/i2I3/WkD9ryJ81NeXvYF6vV4899pgefvhh/fTTT+rbt28Yo3Ue8hdc1XXal+2QHjt2rH766aeYLxrV9vh79913df755ys5OVmzZs1S//79wxmu4xQUFMjr9SohIcF/3B1cOHfM+0dIlkdH1FiwYIEZM2aMycrKMsYY88cff5ihQ4eaE044waxdu7bcts8//7xp0qSJ2bhxoykqKjLGGOPz+cIdMupgwYIFZuzYsSYnJ8cYY8zcuXNN/fr1Tb9+/cwZZ5xRbttly5YZl8tlvv76a/91tHNkoJ2j38FtXKq07bZv325SUlLMAw88UO52r9dbbjs4G+0MwG6LFi0y/fr1M127djUtW7Y011xzTaXbcb6pHPmzhvxZQ/6sCTR/B/v444/NkiVLQhyd85E/63bs2GGW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\n" + "image/png": "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ERERERERERER6QU2PYiIiIiIiIiIiIiISC+w6UFERERERERERERERHqBTQ8iIiIiIiIiIiIiItILbHoQEREREREREREREZFeYNODiIiIiIiIiIiIiIj0ApseRERERERERERERESkF9j0ICIiIiIiIiIiIiIivcCmBxERERERERERERER6QU2PYiIiIiIiIiIiIiISC+w6UFERERERERERERERHqBTQ8iIiIiIiIiIiIiItILbHoQEREREREREREREZFeYNODiIiIiIiIiIiIiIj0ApseRERERERERERERESkF9j0ICIiIiIiIiIiIiIivcCmBxERERERERERERER6QU2PYiIiIiIiIiIiIiISC+w6UFERERERERERERERHqBTQ8iIiIiIiIiIiIiItILbHoQEREREREREREREZFeYNODiIiIiIiIiIiIiIj0ApseRERERERERERERESkF9j0ICIiIiIiIiIiIiIivWAgdwFPE0IAAFJTU2WuhMorNzcXmZmZSE1NhaGhodzlUBXhONcOHGf9xzGuHTjONUfBvm/BvjDR88h93MTPF2mYnzTMTxrmJw3zk4b5ScP8pGF+0sidX3mOmXSu6ZGWlgYAaNCggcyVEBERERFVr7S0NNSpU0fuMqgG4HETEREREdVGZTlmUggdO51Mo9EgJiYGlpaWUCgUcpdD5ZCamooGDRogOjoaVlZWcpdDVYTjXDtwnPUfx7h24DjXHEIIpKWlwdnZGUol70BLzyf3cRM/X6RhftIwP2mYnzTMTxrmJw3zk4b5SSN3fuU5ZtK5Kz2USiVcXFzkLoMksLKy4gdHLcBxrh04zvqPY1w7cJxrBl7hQeWhK8dN/HyRhvlJw/ykYX7SMD9pmJ80zE8a5ieNnPmV9ZiJp5EREREREREREREREZFeYNODiIiIiIiIiIiIiIj0ApseVGmMjY2xYMECGBsby10KVSGOc+3AcdZ/HOPageNMRFWFny/SMD9pmJ80zE8a5icN85OG+UnD/KSpSfnp3IPMiYiIiIiIiIiIiIiIKoJXehARERERERERERERkV5g04OIiIiIiIiIiIiIiPQCmx5ERERERERERERERKQX2PQgIiIiIiIiIiIiIiK9wKYHERERERERERERERHpBTY9iIiIiIiISFZCCLlLoFokNjYWFy9elLsMvaHRaOQugWqR2NhY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f39s3rwZ33zzDU6cOIFXXnkFx48fx4QJEwAABgYG0Gg00Gg0+PrrrxEYGKh/Z9lXANc/aZhfUWx6EBERERER6Qnxz92LO3ToALVajdDQUO17lpaWePfdd+Hp6Yk9e/YgLS0NAGBvb4+FCxeiffv2stSsS5ifNMxPGuYnDfOThvlJx0nniuP6Jw3zK45NDyIiIiIiIj2hUCgAAAMHDkRoaCgWL16M9PR0APkHxNbW1vj0009x5swZnDp1Svtz+vLQSqmYnzTMTxrmJw3zk4b5ScNJZ2m4/knD/Ipj04OIiIiIiEjPNG3aFP/973+xadMmzJ49GwkJCdoDYkNDQ7Rr1w516tSRuUrdxfykYX7SMD9pmJ80zK9iOOlcObj+ScP8njCQuwAiIiIiIiKqfH369MG2bdvw+uuvIzY2FsOHD0e7du2wYcMGPHz4EA0aNJC7RJ3G/KRhftIwP2mYnzTMr+IKJp0HDBgAU1NTfPbZZ7C1tQVQ+yadK4rrnzTML59CFFx/RURERERERHrn8uXLmD59OiIiImBgYACVSoUtW7bA09NT7tJqBOYnDfOThvlJw/ykYX4Vt3fvXrz++uvw9fUtMum8fv16nD9/Hi4uLnKXqPO4/klT2/Nj04OIiIiIiEjPpaamIjExEWlpaXByctKedUplw/ykYX7SMD9pmJ80zK/iavukc2Xg+idNbc6PTQ8iIiIiIiIiIiKiSlabJ52J5MSmBxERERERERERERER6QWl3AUQERERERERERERERFVBjY9iIiIiIiIiIiIiIhIL7DpQUREREREREREREREeoFNDyIiIiIiIiIiIiIi0gtsehARERERERERERERkV5g04OIiIiIiIiIiIiIiPQCmx5ERERERERERERERKQX2PQgIiIiIiIiIiIiIiK9wKYHERERERERERERERHpBTY9iIiIiIiIiIiIiIhIL7DpQUREREREREREREREeuH/AWY9YYYEpFd5AAAAAElFTkSuQmCC" }, "metadata": {}, "output_type": "display_data" @@ -212,7 +232,11 @@ "plt.show()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:49.684675500Z", + "start_time": "2023-09-26T09:14:44.633516300Z" + } } }, { @@ -261,7 +285,11 @@ "cursor = connection.cursor()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:49.742716800Z", + "start_time": "2023-09-26T09:14:49.676674700Z" + } } }, { @@ -284,7 +312,11 @@ "connection.commit()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:49.743717700Z", + "start_time": "2023-09-26T09:14:49.713716200Z" + } } }, { @@ -307,7 +339,11 @@ "connection.commit()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:50.483807800Z", + "start_time": "2023-09-26T09:14:49.716717300Z" + } } }, { @@ -328,7 +364,11 @@ "mmsi, trajectory, sog = cursor.fetchone()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:50.571835900Z", + "start_time": "2023-09-26T09:14:50.477807800Z" + } } }, { @@ -338,7 +378,7 @@ { "data": { "text/plain": "
", - "image/png": "iVBORw0KGgoAAAANSUhEUgAABj0AAAO5CAYAAABCHuf5AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAADtyElEQVR4nOzdeXxU5b3H8e+ZJZN9T8hCwr4JGFBAxA1UULCi4t6WutRaq1Vvaa2lO/f2lmp7ba2i1qUurfuGG1WwQkX2LSyyQyAhCQSy75lk5v4RMiQkhIRMciYzn/frxYvMmXPO/OY5J5PkfM/zPIbb7XYLAAAAAAAAAACgl7OYXQAAAAAAAAAAAIA3EHoAAAAAAAAAAAC/QOgBAAAAAAAAAAD8AqEHAAAAAAAAAADwC4QeAAAAAAAAAADALxB6AAAAAAAAAAAAv0DoAQAAAAAAAAAA/AKhBwAAAAAAAAAA8AuEHgAAAAAAAAAAwC8QegAAAADdxDAM/fCHPzztei+99JIMw9CBAwe6vygAAAAA8GOEHgAAAEAnbd26VTfccIP69eun4OBgpaamaurUqXriiSfMLk2SVFhYqD/+8Y+6+OKLlZCQoOjoaE2cOFFvvvlmq3WXLVsmwzDa/Ld69WrPegcOHDjleoZh6Hvf+55n3YqKCv3mN7/RlVdeqdjYWBmGoZdeeqnNWp977jldcskl6tOnjxwOhwYMGKA77rjjlAHQkSNH9P3vf1+pqakKDg5W//799d3vfrfVerm5ubrpppsUHR2tyMhIXXPNNdq/f3+b+3zhhRc0YsQIBQcHa8iQIac8jp3ZJwAAAABz2MwuAAAAAOhNVq5cqSlTpig9PV3f+973lJSUpJycHK1evVqPP/647r///k7vc/bs2brlllvkcDi8UuOqVav0i1/8QjNmzNAvf/lL2Ww2vfvuu7rlllu0fft2zZs3r9U2DzzwgMaPH99i2eDBgz1fJyQk6B//+Eer7T799FO9+uqrmjZtmmfZsWPH9N///d9KT09XRkaGli1bdspaN23apAEDBmjmzJmKiYlRVlaWnnvuOX388cfavHmzUlJSPOvm5OToggsukCTdc889Sk1NVV5entauXdtinxUVFZoyZYpKS0v185//XHa7XX/+8591ySWXKDMzU3FxcZ51//a3v+mee+7R9ddfrzlz5mj58uV64IEHVFVVpYcffviM9gkAAADAPIbb7XabXQQAAADQW1x11VVat26ddu/erejo6BbPFRQUKDEx0fPYMAzdd999evLJJ3u0xqysLFksFvXr18+zzO126/LLL9eKFStUWFiosLAwSY09PaZMmaK3335bN9xwQ6df6/LLL9e6det05MgRBQcHS5Jqa2tVXFyspKQkrV+/XuPHj9eLL76o22+/vUP73LBhg8aNG6f58+frZz/7mWf5jBkztHPnTq1bt67dkOHRRx/Vww8/rLVr13qCnJ07d2rUqFH66U9/qt///veSpOrqaqWlpWnixIn6+OOPPdt/+9vf1sKFC5WTk6OYmJhO7RMAAACAuRjeCgAAAOiEffv2aeTIka0CD0ktAo/mFi5cqFGjRsnhcGjkyJH69NNPWzzf1pwe/fv31ze+8Q0tXrxYY8aMUXBwsM466yy99957p61xwIABLQIPqTGAufbaa1VbW3vKIZnKy8tVX19/2v03yc/P19KlSzVr1ixP4CFJDodDSUlJHd7Pyfr37y9JKikp8SzbuXOn/vWvf+mhhx5SXFycampq5HQ629z+nXfe0fjx41v0XBk+fLguu+wyvfXWW55lS5cuVWFhoe69994W2993332qrKzUJ5980ul9AgAAADAXoQcAAADQCf369dOGDRu0bdu2Dq3/1Vdf6d5779Utt9yiRx99VDU1Nbr++utVWFh42m337Nmjm2++WdOnT9f8+fNls9l04403asmSJWdU++HDhyVJ8fHxrZ674447FBkZqeDgYE2ZMkXr168/7f7eeOMNuVwufetb3zqjeporLCxUQUGB1q9frzvuuEOSdNlll3me//zzzyVJffr00WWXXaaQkBCFhIRo+vTpLcIil8ulLVu2aNy4ca1eY8KECdq3b5/Ky8slNQ6tJanVuueee64sFovn+c7sEwAAAIC5mNMDAAAA6ISf/OQnmj59usaMGaMJEybooosu0mWXXaYpU6bIbre3Wn/Hjh3avn27Bg0aJEmaMmWKMjIy9Prrr+uHP/xhu6+1e/duvfvuu5o1a5Yk6bvf/a6GDx+uhx9+WFOnTu1U3UVFRXr++ed10UUXKTk52bM8KChI119/vWbMmKH4+Hht375df/rTn3TRRRdp5cqVGjt27Cn3+eqrryo5OVmXXnppp2ppS2pqqmprayVJcXFx+utf/9riPe7Zs0eSdPfdd2v8+PF68803lZ2drXnz5unyyy/Xli1bFBoaqqKiItXW1rZ4j02aluXl5WnYsGHKz8+X1Wpt1UMnKChIcXFxysvLk6RO7RMAAACAuQg9AAAAgE6YOnWqVq1apfnz5+uzzz7TqlWr9OijjyohIUHPP/+8Zs6c2WL9yy+/3BN4SNLZZ5+tyMjIUw4x1VxKSoquu+46z+PIyEh95zvf0SOPPKLDhw93eAippt4YJSUleuKJJ1o8N2nSJE2aNMnzeObMmbrhhht09tlna+7cua2G4mqye/dubdiwQT/60Y9ksXS9A/m//vUv1dTUaMeOHfrnP/+pysrKFs9XVFRIkpKSkvTJJ594XrNv37669dZb9dprr+muu+5SdXW1JLU5KXzTEFxN61RXVysoKKjNeoKDg1us19F9AgAAADAXw1sBAAAAnTR+/Hi99957Ki4u1tq1azV37lyVl5frhhtu0Pbt21usm56e3mr7mJgYFRcXn/Z1Bg8eLMMwWiwbOnSoJLUY0ul07r//fn366ad6/vnnlZGR0aHXveaaa7R06VI1NDS0uc6rr74qSV4Z2kpq7AEzffp0zZkzR2+//bbmzZvXYgL4kJAQSdJNN93UImS58cYbZbPZtHLlyhbrNfUaaa6mpqbFOiEhIaqrq2uznpqamhbrdXSfAAAAAMxF6AEAAACcoaCgII0fP16///3v9fTTT8vpdOrtt99usY7Vam1zW7fb3RMlat68eXrqqaf0hz/8QbNnz+7wdmlpaaqrq2vV46LJa6+9pmHDhuncc8/1VqkegwYN0tixYz3BitTY60VqnNOjOavVqri4OE+IFBsbK4fDofz8/Fb7bVrWtK/k5GQ1NDSooKCgxXp1dXUqLCz0rNeZfQIAAAAwF6EHAAAA4AVNk1y3dWH8TO3du7dVOLJ7925JUv/+/U+7/YIFC/Tb3/5W//Vf/6WHH364U6+9f/9+BQcHKzw8vNVza9as0d69e73Wy6Mt1dXVKi0t9TxuCldyc3NbrFdXV6djx44pISFBkmSxWDR69Og2J2Jfs2aNBg4cqIiICEnSmDFjJKnVuuvXr5fL5fI835l9AgAAADAXoQcAAADQCUuXLm2zl8aiRYskyauTWefl5en999/3PC4rK9Mrr7yiMWPGnHY+jzfffFMPPPCAvvWtb+mxxx475XpHjx5ttWzz5s368MMPNW3atDbn63jttdckSd/85jc7+lbaVF9f3+YwX2vXrtXWrVs9QZIkTZ48WYmJiXr11Vc9Q0pJ0ksvvaSGhoYWk57fcMMNWrduXYuQYteuXfriiy904403epZdeumlio2N1dNPP93i9Z9++mmFhobqqquu6vQ+AQAAAJjLcPdUv3oAAADAD4waNUpVVVW67rrrNHz4cNXV1WnlypV68803lZaWpk2bNik6OlqSZBiG7rvvvhZzU0iNvTQmT56sl156SVLjhfs77rhDWVlZnh4c/fv3l8PhUEFBge655x716dNHf//73/X1119r0aJFuuKKK05Z49q1a3XRRRcpKipKjzzyiOx2e4vnJ02apIEDB0pqvPAfEhKiSZMmKTExUdu3b9ezzz4ru92uVatWacSIES22bWhoUGpqqgYMGKBVq1adsoYnn3xSJSUlysvL09NPP61Zs2Zp7NixkhrnGImKilJJSYn69u2rm2++WSNHjlRYWJi2bt2qF198UcHBwVq9erWGDBni2ecrr7yi2267TePHj9fs2bOVnZ2txx9/XBMnTtTSpUs9Q4mVl5dr7NixKi8v109+8hPZ7XY99thjamhoUGZmpqdXiCQ99dRTuu+++3TDDTfoiiuu0PLly/XKK6/of//3f/Xzn//cs15n9gkAAADAPDazCwAAAAB6kz/96U96++23tWjRIj377LOqq6tTenq67r33Xv3yl7/0BB7eMGTIED3xxBN66KGHtGvXLg0YMEBvvvlmu4GHJG3fvl11dXU6evSo7rzzzlbPv/jii57Q49prr9Wrr76qxx57TGVlZUpISNCsWbP0m9/8RoMHD2617eeff64jR47oF7/4Rbs1/OlPf9LBgwc9j9977z299957kqRvf/vbioqKUmhoqO666y4tXbpU77zzjqqrq5WSkqJbb71Vv/zlL1sN4fWd73xHQUFB+sMf/qCHHnpI0dHR+v73v6/f//73LeZOiYiI0LJly/SjH/1Iv/vd7+RyuTR58mT9+c9/bhVO3HvvvbLb7fq///s/ffjhh0pLS9Of//xnPfjggy3W68w+AQAAAJiHnh4AAACAD+rfv79GjRqljz/+2OxSAAAAAKDXYE4PAAAAAAAAAADgFwg9AAAAAAAAAACAXyD0AAAAAAAAAAAAfoE5PQAAAAAAAAAAgF+gpwcAAAAAAAAAAPALhB4AAAAAAAAAAMAvEHoAAAAAAAAAAAC/QOgBAAAAAAAAAAD8AqEHAAAAAAAAAADwC4QeAAAAAAAAAADALxB6AAAAAAAAAAAAv0DoAQAAAAAAAAAA/AKhBwAAAAAAAAAA8AuEHgAAAAAAAAAAwC8QegAAAAAAAAAAAL9A6AEAAAAAAAAAAPwCoQcAAAAAAAAAAPALhB4AAAAAAAAAAMAvEHoAAAAAAAAAAAC/QOgBAAAAAAAAAAD8AqEHAAAAAAAAAADwC4QeAAAAAAAAAADALxB6AAAAAAAAAAAAv0DoAQAAAAAAAAAA/AKhBwAAAAAAAAAA8AuEHgAAAAAAAAAAwC8QegAAAAAAAAAAAL9A6AEAAAAAAAAAAPwCoQcAAAAAAAAAAPALhB4AAAAAAAAAAMAvEHoAAAAAAAAAAAC/QOgBAAAAAAAAAAD8AqEHAAAAAAAAAADwC4QeAAAAAAAAAADALxB6AAAAAAAAAAAAv0DoAQAAAAAAAAAA/AKhBwAAAAAAAAAA8AuEHgAAAAAAAAAAwC8QegAAAAAAAAAAAL9A6AEAAAAAAAAAAPwCoQcAAAAAAAAAAPALhB4AAAAAAAAAAMAvEHoAAAAAAAAAAAC/QOgBAAAAAAAAAAD8AqEHAAAAAAAAAADwC4QeAAAAAAAAAADALxB6AAAAAAAAAAAAv0DoAQAAAAAAAAAA/AKhBwAAAAAAAAAA8AuEHgAAAAAAAAAAwC8QegAAAAAAAAAAAL9A6AEAAAAAAAAAAPwCoQcAAAAAAAAAAPALhB4AAAAAAAAAAMAvEHoAAAAAAAAAAAC/QOgBAAAAAAAAAAD8AqEHAAAAAAAAAADwC4QeAAAAAAAAAADALxB6AAAAAAAAAAAAv0DoAQAAAAAAAAAA/AKhBwAAAAAAAAAA8AuEHgAAAAAAAAAAwC8QegAAAAAAAAAAAL9A6AEAAAAAAAAAAPwCoQcAAAAAAAAAAPALhB4AAAAAAAAAAMAvEHoAAAAAAAAAAAC/QOgBAAAAAAAAAAD8AqEHAAAAAAAAAADwC4QeAAAAAAAAAADALxB6AAAAAAAAAAAAv0DoAQAAAAAAAAAA/AKhBwAAAAAAAAAA8AuEHgAAAAAAAAAAwC8QegAAAAAAAAAAAL9A6AEAAAAAAAAAAPwCoQcAAAAAAAAAAPALhB4AAAAAAAAAAMAvEHoAAAAAAAAA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54AUPVCm1qxZo7lz5+rmm2+2OxSUkezsbPl8PsXHx/t3+Oz8w8+KFSv03//+Vw8//DBtG8Y8Ho/cbrck+U86+D6HF47LAOy0cuVK9ezZU23atNHKlStVv359NW/eXB999JGcTicdL6ewYsUK9e/fXx07dtS6detUu3ZtuVwuDR482O7QQsKmTZvUu3dvXXnllRo3bpz/nCcf29/JrVy5Uueff766du2q1atXKzExUbVr19Ynn3yi2NhYzhlPYeXKlRo0aJCaNWumJUuWqG3btho5cqT+9re/2R1ayFi+fLnOO+883XHHHRQ8Smnt2rV68MEHdeTIEcXGxmrmzJns94rp999/1zXXXKOLLrpId9xxh2JiYtjvlYAd54Bs2Sgzy5Yt0+mnn66MjIwCzzMMLHysWrVKAwcO1LnnnqszzzxTr7zyinbt2uWv1CI8LF++XF27di3UpnyXw8uaNWt02WWXqVevXhowYIC++uorHT58WA6Hg7YOExyXAdgpIyNDY8aM0eWXX67vvvtO69at0913360VK1bozDPPVF5env+iF4WtX79eAwYM0LXXXqsvvvhC8+fP15EjR7R79267Qwt6+ce56dOn67zzztOzzz4rl8ulV199VY8++qiefPJJSaLj7yQOHjyoq666Stdee60+++wzLV68WLfffrtmzZqlQYMG6cCBA1wDnsTmzZt1wQUX6KqrrtKXX36pNWvWqGnTppo1a5bdoYWMVatWqXv37rr55pv19NNPy+fzaenSpfryyy+1YsUKu8MLCatXr1b37t0VHx+v5ORkrV69WldddZX/da4JTmz79u0aPny4Nm3apC+//FJTpkxRTk4O18rFZNc5IEd1lInly5f7D0h33XVXgdeogoaHzZs369xzz1WzZs102223qVmzZnrjjTd04403auPGjVy0honly5fr7LPP1tixY/Xoo48WeO3YkT0IbRs2bFC3bt1UqVIl9evXTzk5ORo/frweeOAB7dy5k5O5MMBxGYDdcnJylJGRoYEDByoqKko1a9bUsGHD9M477+jw4cPq1auXJPmnysFfcnJy9Morr6hfv3564IEH5HA4lJSUpI4dO2rlypUaP368nn32WbvDDFr5x7kdO3aoRYsWkqSzzjpL06dP1+eff66XX35Zbdq00R9//CGJNSqKsmPHDhljdOONN0qSKleurF69eqlly5ZauXKlhgwZIonCUVE8Ho/efvttde7cWRMnTlRMTIzq1KmjG264QXPnztXWrVvtDjHo+Xw+PfTQQ8rIyNADDzwgSRowYIDGjBmjIUOG6IorrtCIESNsjjK4paena+zYsbryyiv1f//3f3r88cd1/fXXq2bNmv73cE1QNGOMPv/8c9WpU0dffvmlWrRooffee69A4YPjxsnZdQ7IEQkBt2XLFnXv3l0jR47Uv/71L3k8Hr344osaP368br/9dv3+++/Kzc21O0xY9L///U9dunTRa6+9ppEjR2r69OkaN26cMjMzNWbMGG3ZsoWL1hC3Y8cOnX322RoxYoT+9a9/KTc3139yNGLECM2aNUspKSmcHIWBd955Rz179tRbb72lu+++W999952uuuoqLVy4UJMmTdKePXto5xDGcRlAMEhMTFReXp6+++47/3Nut1tnnHGGXn/9de3Zs0f33XefJDpejudyuXT55Zfr1ltvldvtlsPh0GOPPaYZM2YoMzNTmzZt0tSpUzV8+HC7Qw1qPp9PK1as0Pvvv68qVaroiy++0HfffacFCxYoMTFRl156qSQ67k/kyJEjWrlypf9xRkaG4uLi9Pzzz2vXrl165plnbIwuuFWuXFn9+/dXQkKCf/uqXbu2nE4n52DF4HQ69eKLL6pz587q0qWLzj33XEVHR+vll1/W2rVrdeedd2rJkiUaO3as3aEGrfT0dB05csS/DpTD4dAff/yhWbNmqVu3burevbt+/vlnSdzUeDyHw6ELL7xQ119/vc444wxNnTpVbdu21YwZM/TKK68oKyuLvq9TsOsckKM5Au6bb75R9erVVbFiRe3Zs0eDBw/WjBkztGjRIn311VcaNGiQPv74Y3m9XrtDhQVpaWlat26d0tLS/M9deeWV/hONf/7zn0pNTeWiNYQtX75czZo104EDB7R9+3ZdeOGF+vLLL3XkyBFt3rxZt99+u1555ZVCU+Ug9GRlZWn37t3KycnxPzdx4kRdfvnlWr16td566y0uyEIYx2UAwcDhcGjo0KH69ddf9fXXXxd4/uyzz9aAAQO0aNEi5eXl2RhlcIqKilKnTp3UsWNHSUenunrppZf02Wef6d///rc+/vhj3XHHHVq0aJE2bNhgb7BBbOTIkTp48KCef/55NWzYUImJiYqLi1NSUpKee+457d69W4sXL7Y7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FSdqY47LAMrLhg0bTMuWLQtdvD7++OPG4XCYKVOmGGOM2blzp1mwYIF54IEHzOuvv27Wr19vQ7TB52T5czqd5rXXXjPGHD0XnzVrlhkzZoxp1KgRBaM/FXf7S0lJMX/88Yd57bXXzJdffsl5zp9Ksv3t2bPHTJo0ydSvX5+CkTl6DjZ27Fhz6aWXmunTp5tLLrnEJCcnn7DTfteuXSYqKspUrFiR7++fSprDH374wVx//fWmQYMG5PBPpyoa+Xw+k52dbe666y5z2mmnmdatW5vBgwebmjVrRnwO09LSzLBhw8wNN9xQ4PkrrrjC9OrVq8ifefXVV43D4WAf+KdgOwek6IFS27ZtW4nu5pg9e7YZP368SUhI4G7SELFp0yYzb968U74vLy/P3HDDDaZbt26mSZMmpmvXruz0Q0hx2znfF198Ye644w5TqVKliD8xCiVr1641d955Z4GpMYqye/du079/f9OqVSvTsGFD0759e7N48eJyihJWFLeN83FcBlAWFi1aZKKjo83y5cuNMabAPmnSpEkFXkNhp8pfTEyM/27mXbt2mWnTppktW7bYEWpQKs72t2LFCrvCC3ol2f48Ho/ZunUrN0AdY+bMmf6pf3/88Udz8cUXn7DTPiUlxdx2221m3bp1tsQarEqSw/3795tPPvmEouWfilM0yr95dc+ePebDDz80N9xwg3nyySe58cAc3Z5uvfVWM2PGDGPMXzMifPTRR+acc84xeXl5/ueOVdR075Eq2M4BHcYcsyoQUExLly5Vv3799Morr2jo0KFFvscYI4fDIUnKzs7WXXfdpXnz5untt99W+/btyzNclMKKFSvUv39/DRw4UI8//rhq1qxZ6D3mz4XZ8hdjS0lJUVZWluLj45WYmFjeIaMUitvO+d9lSZowYYLmz5+vKVOmqF27duUZLkppxYoVOvPMM5WTk6PPP/9cgwYNKvSe49t527Zt8nq9qlSpkqpVq1ae4aIUStrGHJcBlKX+/fsrIyNDn376qapWrSqPxyO32y2v16uBAweqXr16evXVV+V0Ov3nkfhLcfI3depUud3uQsdvFC9/r732mhwOB9tfEYr7/Y2KirI71KA3b948vfDCC9q8ebOmTJmirl27KicnR1u3blXLli39ucWJFZXD7Oxsbdu2TS1btmQfeJxPP/1U+/fv1/XXX6/58+frmWee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6DLDxAJ+5M8a8mcN+bOmJPk73gUXXBDxI2QkchgIK1euVL9+/XTJJZfooosu0k033SRJJ10U2uVy6bTTTtPChQsjepRMabe/jh076v3334/47W/dunU677zz1LhxY910001q2rSpnn32WQ0fPlwZGRmS/lpLJmiOIeVdZUHoWL58ualQoYIZP358geezsrJO+DO33XabufTSSwvdfYrgdKI2zsnJMT6fzzz77LPG6XSaUaNGmR9++MHs2bPH3HfffaZRo0Zm586dNkWNkqKdI8PJ2vl4s2fPNgkJCeaTTz4pp+gQCLQxADv5fD5z3333meHDh/uf27Ztm+nRo4dZsGCB+fbbb22MLviRP2vInzXkzxryZx05tG79+vWmevXq5r777jM//fSTmTJlimndurXZvn27/z3Bcod9sCnt9kc+j8rLyzO33nqrufHGG/3PpaenmyFDhhiHw2EGDhzof/5ko47KGwuZo0h79uxRv3791L17d02ePFler1f/+Mc/tGHDBm3atEk33nij+vXrp9atW0uSNm/erDfffFNvvfWW5s+fX+DuUwSnE7Xx+vXrtWXLFt14443q27evPvnkE40dO1azZ89WlSpVlJmZqU8++cQ/ygfBjXaODMXZZ/fv31+tWrWSJPXp00fdu3fXM888owsuuEAOh4O7/4McbQzAbg6HQ5s2bdKePXv8z7377rv67bffdPXVVyslJUUNGjTQd999p7i4uALziIP8WUX+rCF/1pA/68ihNcYYTZs2Tb1799YjjzwiSapXr57ef/997d692z+KgZwVrbTbH/k8yuVyaePGjapdu7akoyM6KlSooHPOOUdJSUn69NNPddNNN2nKlCknHXVU3ih64IS6deumHTt26NNPP9XUqVPl8XjUsWNHNWrUSC+88IJWrVqlSZMmKT09Xffcc4+WL1+uuXPnRvSCSKHmZG38/PPPa8WKFXr11Vf1888/a9euXcrNzVXz5s2VlJRkd+goAdo5MhR3n92gQQNJ0pgxY9SuXbugOinBydHGAOySv0DlkCFDNHHiRA0aNEhJSUmaPn26PvzwQ7Vt21Yul0vnnnuu7rjjDk2dOpWOgmOQP2vInzXkzxryZx05tI6iUemx/Vnj9Xrl8/nUrFkz7dixQytXrlS7du20detW/fOf/9TkyZPVsmVLvfvuuzpw4ICqV69ud8h+DmPsnmALwWr37t2aMGGCPvzwQ3Xv3l0zZsxQtWrVJB3dud58882aMWOG+vfvr++//16NGjVSo0aN7A0aJXKyNp4+fbrGjh2rd999V4MGDbI5UlhBO0eG4uyz3333XQ0YMMDmSFFatDGA8ubz+eR0Ov2dJzt37tT8+fO1ZMkS/fHHH2rWrJkeeugh/+vXXnutDh8+rE8++cTu0IMC+bOG/FlD/qwhf9aRw8DI77SfMWOGJk6cqLZt256w075///6aOnWq3SEHBbY/a47P3/fff6+bb75ZMTExql27tr7//nuNGjVKU6dO1dq1a9WhQwf9/PPPOv300+0O3Y+RHjihpKQkPfHEE6pbt6569+6tatWq+Tf2K664Qg888IC+++479e/fXz169LA7XJTCydr4yiuv1IMPPqh58+bRGR7iaOfIUJx99ty5c+kQD2G0MYDytG7dOr344otKS0tTjRo19I9//EN169bV5Zdfrssvv1wXXXSRDhw4IOmvBSuzsrJUu3Zt/4VyJCN/1pA/a8ifNeTPOnJoXX4e8nNx7rnn6sknn/R32t91110aPHiw/3qgV69e2rt3r81RBwe2P2uOzV/16tX1j3/8Qz169NA777yjWbNm6eDBg7r88ss1evRoSdKRI0fUunVr//RXwSKyWxGnVKdOHU2YMEHdu3eXdHRnYIzRwYMHVaNGDXXo0MHmCGHVqdq4Y8eO9gaIgKCdIwPtHP5oYwDl4ffff1eXLl106NAhHT58WPPmzVObNm30ySefKCsrS5LUvXt3rV+/Xu+//742bNigiRMn6ttvv9Vtt90W8Z0F5M8a8mcN+bOG/FlHDq1bt26dbr31Vo0ePVrjx4/Xnj17/J32Tz75pDIyMk7aaR/J2P6sOT5/P/zwg9q0aaOPPvpIycnJmjBhgp566il/wUOSPvroI0VFRSkuLs7GyItQpsukI2xNmjTJNG/e3GzdutXuUFBGaOPIQDtHBto5/NHGAALF5/OZa665xgwdOtT/OD093YwZM8bExsaaadOmGWOMWbhwobngggtMtWrVTMuWLU3btm3N0qVLbYw8OJA/a8ifNeTPGvJnHTm0bs2aNSYhIcGMGDHCDBkyxHTu3NlUqVLFfPzxxyYzM9MYY8xTTz1levfubd577z2zfv16M2HCBFOjRg3z+++/2xy9vdj+rDlZ/mJiYsy0adOMx+Pxv3/x4sXm6quvNpUrVw7K/DG9FUrkvffe09y5c/Xhhx/q22+/VcOGDe0OCQFGG0cG2jky0M7hjzYGEGgOh0MpKSmqV6+eJMkYowoVKujVV19VTEyMbrrpJjVr1kzdunXTiy++qF27dikvL0/NmzdXrVq1bI7efuTPGvJnDfmzhvxZRw6tMcboqaeeUr9+/fTuu+/KGKPMzEyNGzdOV1xxhV577TWNHDlSPXr00I8//qibb75Z1atXV1RUlGbPnq1WrVrZ/SfYiu3PmlPlb+zYsWrevLm6du2q7OxsOZ1OORwO/fDDD2rXrp3N0RdG0QMl0qZNG73zzjv68ccf1bZtW7vDQRmgjSMD7RwZaOfwRxsDKAs1atTQ119/LWOMnE6ncnNzFR0drRdeeEG7du3Sddddp0WLFqlBgwZq0KCB3eEGHfJnDfmzhvxZQ/6sI4elR6e9dWx/1hQ3f3FxcerYsaOmTp2q6Ohou8MuUmRPVIYSa9++vT7++GM6VsIYbRwZaOfIQDuHP9oYQCAZYyRJN910k+Li4jR27Fjl5eUpOjpaubm5kqRbb71V6enpWrdunZ2hBiXyZw35s4b8WUP+rCOHgVFUp7MkvfDCC+rfv7+uu+46ZWZmqkGDBuratau6d+9OwUNsf1YVN39paWkF8hesBQ+JogdKIZg3aAQGbRwZaOfIQDuHP9oYQKDkL4baunVrjRgxQosWLdJdd90lj8fj39fUqlVLLpcr4hdKLQr5s4b8WUP+rCF/1pFDa+i0t4btz5qS5M/r9doZarFR9AAAAAAASJJ/GoO///3vuuiiizRv3jwNHTpUu3fv1qZNmzR9+nS5XC7/1BsoiPxZQ/6sIX/WkD/ryGHp0WlvHdufNeGWP9b0AAAAAADI6/UqOjpamzdv1rfffquJEyeqcePGeu6559SkSRM1atRImZmZ+uSTT5hKowjkzxryZw35s4b8WUcOrTu20zkvL08ff/yxhg4dqqlTpyozMzPkOp3LE9ufNeGYP4fJHz8FAAAAAIhIPp9PTqdT27Zt09lnn63Bgwdr6tSp/te/++47ValSRbVq1VKdOnVsjDQ4kT9ryJ815M8a8mcdObTO6/XK5XL5O52vu+46vffee3ruuee0cuXKAp3OnTp1sjvcoML2Z0245o+iBwAAAABEiLVr12rZsmUaPnx4odcOHDigbt266fzzz9eUKVPkcDhkjPFPuQHyZxX5s4b8WUP+rCOHZSNcO50Dje3PmkjLH9NbAQAAAEAE2LBhg7p06aKMjAwdOnRIY8eOLfC6MUZ33XWXrr/+ev9Fbihf7AYa+bOG/FlD/qwhf9aRQ+tO1OnsdDp14MAB9e7dW4MHD9aUKVMkyd/p3KtXLzvCDSpsf9ZEYv4Y6QEAAAAAYS4lJUVjx45Vbm6u2rRpo0ceeUTPP/+8brnlFkl/TauBopE/a8ifNeTPGvJnHTm0bsOGDerUqZMyMjL00ksvFep03r9/v2bOnFmg0xlHsf1ZE6n5Y6QHAAAAAIS5tLQ01a1bV927d1e/fv2UkJCg2267TZJ0yy23yOl02hxhcCN/1pA/a8ifNeTPOnJoTUpKih588EH1799fbdq00d///nd5vd4Cnc41atTQDTfcYHOkwYntz5pIzR9FDwAAAAAIc/Xq1dPNN9+shg0bSpLGjh0rY0yBi15JysvLU0pKiqpVq2ZbrMGI/FlD/qwhf9aQP+vIoTWR2ukcKGx/1kRq/ih6AAAAAEAY8vl8Msb4pyxo2LChf37w+Ph43XLLLYUueu+8804lJibq/vvvV3R0tJ3h2478WUP+rCF/1pA/68hh4ERqp7MVbH/WkD+KHgAAAAAQdtasWaPHH39ce/bsUfPmzTV48GANGjRIDodDeXl5ioqKUmxsrG699VY5HA794x//0PTp0/Xbb79p8eLFYXGxawX5s4b8WUP+rCF/1pFD6+h0Lj22P2vI31EsZA4AAAAAYWTdunU688wzNWDAADVq1Ej/+9//5Ha71b17dz377LOS5L/olY7ONd6rVy9t3bpV33//vdq1a2dn+LYjf9aQP2vInzXkzzpyaN2JOp2lgrnLzs7Wiy++qPvuu0/Jycn+Tufk5GQ7w7cV25815O8YBgAAAAAQFnw+n7nnnnvMsGHD/M+lpqaaRx991HTs2NHccMMN/ue9Xq/xer1m/PjxxuFwmBUrVtgRclAhf9aQP2vInzXkzzpyaN3atWtNpUqVzPDhw82ECRNMhw4dTOfOnc3tt9/uf4/H4/H//8iRI6ZTp06matWqEZ9Dtj9ryF9BrJQDAAAAAGHC4XBo165d2rNnj/+5hIQE3Xrrrbrqqqu0dOlSPfnkk5Ikp9OpAwcOyOfzaenSpeF1d18pkT9ryJ815M8a8mcdObTGGKNp06apX79+mjFjhp544gn9+OOPuuiii/T9999rzJgxkqSoqCj5fD75fD499thjWrp0afjdZV8KbH/WkL+CKHoAAAAAQBgwf85c3KlTJ3m9Xq1bt87/WkJCgq699lolJyfrs88+U1pamiSpZs2aevzxx9WhQwdbYg4m5M8a8mcN+bOG/FlHDq2j07n02P6sIX+FUfQAAAAAgDDgcDgkSQMHDtS6des0efJkpaenSzp6MVylShXdf//9+uWXX/TTTz/5fy5cFqy0ivxZQ/6sIX/WkD/ryKE1dDpbw/ZnDfkrjKIHAAAAAISRpk2b6oMPPtD06dM1YcIEHThwwH8x7Ha71b59e1WqVMnmKIMX+bOG/FlD/qwhf9aRw9Kh0zkw2P6sIX9/ibI7AAAAAABAYPXs2VMffvihLrvsMu3evVvDhg1T+/btNW3aNO3bt0/169e3O8SgRv6sIX/WkD9ryJ915LD08judBwwYoLi4OD344IOqXr26pMjrdC4ttj9ryN9RDpM//goAAAAAEFaWLFmicePGaevWrYqKipLL5dJ7772n5ORku0MLCeTPGvJnDfmzhvxZRw5L7/PPP9dll12mQYMGFeh0fuutt/Tbb7+pXr16docY9Nj+rIn0/FH0AAAAAIAwlpqaqkOHDiktLU1JSUn+O05RPOTPGvJnDfmzhvxZRw5LL9I7nQOB7c+aSM4fRQ8AAAAAAAAACLBI7nQG7ETRAwAAAAAAAAAAhAWn3QEAAAAAAAAAAAAEAkUPAAAAAAAAAAAQFih6AAAAAAAAAACAsEDRAwAAAAAAAAAAhAWKHgAAAAAAAAAAICxQ9AAAAAAAAAAAAGGBogcAAAAAAAAAAAgLFD0AAAAAAAAAAEBYoOgBAAAAAAAAAADCAkUPAAAAAAAAAAAQFih6AAAAAAAAAACAsPD/dtXC3SJBs20AAAAASUVORK5CYII=\n" + "image/png": "iVBORw0KGgoAAAANSUhEUgAABj0AAAO5CAYAAABCHuf5AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3hUZf7+8XtmMum9AQkldKQIqKBiA0UUXBt2Xde2xVUXd92frm7Vbbb9WlZddW3YexcsuKIgHST0DiGQBNJ7m8zM749khoTUySQ5U96v6+Ji5syZcz55zswkc+7zPI/J6XQ6BQAAAAAAAAAA4OfMRhcAAAAAAAAAAADQEwg9AAAAAAAAAABAQCD0AAAAAAAAAAAAAYHQAwAAAAAAAAAABARCDwAAAAAAAAAAEBAIPQAAAAAAAAAAQEAg9AAAAAAAAAAAAAGB0AMAAAAAAAAAAAQEQg8AAAAAAAAAABAQCD0AAACAXmIymXTbbbd1ut78+fNlMpmUlZXV+0UBAAAAQAAj9AAAAAA8tGnTJl166aUaMmSIwsPDlZ6errPPPltPPPGE0aVJkoqKivTwww/r9NNPV0pKiuLj43XSSSfp7bffbrXut99+K5PJ1Oa/lStXutfLyspqdz2TyaSf/exn7nUrKyv1l7/8Reeee64SExNlMpk0f/78Nmt97rnndMYZZ6hfv34KCwvT0KFDdcMNN7QbAB0+fFi/+MUvlJ6ervDwcGVkZOimm25qtV5OTo4uv/xyxcfHKzY2VhdeeKH27t3b5jZfeOEFHXPMMQoPD9fIkSPbPY6ebBMAAACAMUKMLgAAAADwJ8uXL9eMGTM0ePBg/exnP1P//v114MABrVy5Uo8//rh+9atfebzNa6+9VldeeaXCwsJ6pMYVK1boD3/4g+bMmaM//vGPCgkJ0fvvv68rr7xSW7du1X333dfqOfPmzdOUKVNaLBsxYoT7dkpKil599dVWz/viiy/0+uuva9asWe5lhYWF+utf/6rBgwdr4sSJ+vbbb9utdf369Ro6dKguuOACJSQkaN++fXruuef02WefacOGDUpLS3Ove+DAAZ1yyimSpJtvvlnp6enKzc3V6tWrW2yzsrJSM2bMUFlZmX7/+9/LarXq0Ucf1RlnnKHMzEwlJSW513322Wd1880365JLLtEdd9yhpUuXat68eaqurtbvfve7bm0TAAAAgHFMTqfTaXQRAAAAgL8477zztGbNGu3cuVPx8fEtHsvPz1dqaqr7vslk0q233qonn3yyT2vct2+fzGazhgwZ4l7mdDo1c+ZMLVu2TEVFRYqKipLU2NNjxowZevfdd3XppZd6vK+ZM2dqzZo1Onz4sMLDwyVJdXV1KikpUf/+/bV27VpNmTJFL730kq6//voubXPdunU64YQTdP/99+vuu+92L58zZ462b9+uNWvWdBgyPPTQQ/rd736n1atXu4Oc7du3a/z48brrrrv0z3/+U5JUU1OjQYMG6aSTTtJnn33mfv6Pf/xjffTRRzpw4IASEhI82iYAAAAAYzG8FQAAAOCBPXv2aNy4ca0CD0ktAo/mPvroI40fP15hYWEaN26cvvjiixaPtzWnR0ZGhn70ox/pq6++0qRJkxQeHq6xY8fqgw8+6LTGoUOHtgg8pMYA5qKLLlJdXV27QzJVVFSooaGh0+275OXlafHixZo7d6478JCksLAw9e/fv8vbOVpGRoYkqbS01L1s+/bt+vzzz3XnnXcqKSlJtbW1stlsbT7/vffe05QpU1r0XBkzZozOOussvfPOO+5lixcvVlFRkW655ZYWz7/11ltVVVWlBQsWeLxNAAAAAMYi9AAAAAA8MGTIEK1bt06bN2/u0vrff/+9brnlFl155ZV66KGHVFtbq0suuURFRUWdPnfXrl264oorNHv2bN1///0KCQnRZZddpkWLFnWr9kOHDkmSkpOTWz12ww03KDY2VuHh4ZoxY4bWrl3b6fbeeustORwOXXPNNd2qp7mioiLl5+dr7dq1uuGGGyRJZ511lvvxr7/+WpLUr18/nXXWWYqIiFBERIRmz57dIixyOBzauHGjTjjhhFb7mDp1qvbs2aOKigpJjUNrSWq17vHHHy+z2ex+3JNtAgAAADAWc3oAAAAAHvh//+//afbs2Zo0aZKmTp2q0047TWeddZZmzJghq9Xaav1t27Zp69atGj58uCRpxowZmjhxot58803ddtttHe5r586dev/99zV37lxJ0k033aQxY8bod7/7nc4++2yP6i4uLtbzzz+v0047TQMGDHAvDw0N1SWXXKI5c+YoOTlZW7du1b/+9S+ddtppWr58uSZPntzuNl9//XUNGDBAZ555pke1tCU9PV11dXWSpKSkJP373/9u8TPu2rVLkvTzn/9cU6ZM0dtvv63s7Gzdd999mjlzpjZu3KjIyEgVFxerrq6uxc/o4lqWm5ur0aNHKy8vTxaLpVUPndDQUCUlJSk3N1eSPNomAAAAAGMRegAAAAAeOPvss7VixQrdf//9+vLLL7VixQo99NBDSklJ0fPPP68LLrigxfozZ850Bx6SdOyxxyo2NrbdIaaaS0tL08UXX+y+Hxsbq5/85Cd68MEHdejQoS4PIeXqjVFaWqonnniixWPTpk3TtGnT3PcvuOACXXrppTr22GN1zz33tBqKy2Xnzp1at26dfvOb38hs9r4D+eeff67a2lpt27ZNr732mqqqqlo8XllZKUnq37+/FixY4N7nwIEDddVVV+mNN97QT3/6U9XU1EhSm5PCu4bgcq1TU1Oj0NDQNusJDw9vsV5XtwkAAADAWAxvBQAAAHhoypQp+uCDD1RSUqLVq1frnnvuUUVFhS699FJt3bq1xbqDBw9u9fyEhASVlJR0up8RI0bIZDK1WDZq1ChJajGkU2d+9atf6YsvvtDzzz+viRMndmm/F154oRYvXiy73d7mOq+//rok9cjQVlJjD5jZs2frjjvu0Lvvvqv77ruvxQTwERERkqTLL7+8Rchy2WWXKSQkRMuXL2+xnqvXSHO1tbUt1omIiFB9fX2b9dTW1rZYr6vbBAAAAGAsQg8AAACgm0JDQzVlyhT985//1NNPPy2bzaZ33323xToWi6XN5zqdzr4oUffdd5/+85//6IEHHtC1117b5ecNGjRI9fX1rXpcuLzxxhsaPXq0jj/++J4q1W348OGaPHmyO1iRGnu9SI1zejRnsViUlJTkDpESExMVFhamvLy8Vtt1LXNta8CAAbLb7crPz2+xXn19vYqKitzrebJNAAAAAMYi9AAAAAB6gGuS67ZOjHfX7t27W4UjO3fulCRlZGR0+vynnnpK9957r37961/rd7/7nUf73rt3r8LDwxUdHd3qsVWrVmn37t091sujLTU1NSorK3Pfd4UrOTk5Ldarr69XYWGhUlJSJElms1kTJkxocyL2VatWadiwYYqJiZEkTZo0SZJarbt27Vo5HA73455sEwAAAICxCD0AAAAADyxevLjNXhoLFy6UpB6dzDo3N1cffvih+355ebleeeUVTZo0qdP5PN5++23NmzdP11xzjR555JF21ysoKGi1bMOGDfrkk080a9asNufreOONNyRJV199dVd/lDY1NDS0OczX6tWrtWnTJneQJEnTp09XamqqXn/9dfeQUpI0f/582e32FpOeX3rppVqzZk2LkGLHjh365ptvdNlll7mXnXnmmUpMTNTTTz/dYv9PP/20IiMjdd5553m8TQAAAADGMjn7ql89AAAAEADGjx+v6upqXXzxxRozZozq6+u1fPlyvf322xo0aJDWr1+v+Ph4SZLJZNKtt97aYm4KqbGXxvTp0zV//nxJjSfub7jhBu3bt8/dgyMjI0NhYWHKz8/XzTffrH79+unFF1/Uli1btHDhQp1zzjnt1rh69WqddtppiouL04MPPiir1dri8WnTpmnYsGGSGk/8R0REaNq0aUpNTdXWrVv13//+V1arVStWrNAxxxzT4rl2u13p6ekaOnSoVqxY0W4NTz75pEpLS5Wbm6unn35ac+fO1eTJkyU1zjESFxen0tJSDRw4UFdccYXGjRunqKgobdq0SS+99JLCw8O1cuVKjRw50r3NV155Rdddd52mTJmia6+9VtnZ2Xr88cd10kknafHixe6hxCoqKjR58mRVVFTo//2//yer1apHHnlEdrtdmZmZ7l4hkvSf//xHt956qy699FKdc845Wrp0qV555RX94x//0O9//3v3ep5sEwAAAIBxQowuAAAAAPAn//rXv/Tuu+9q4cKF+u9//6v6+noNHjxYt9xyi/74xz+6A4+eMHLkSD3xxBO68847tWPHDg0dOlRvv/12h4GHJG3dulX19fUqKCjQjTfe2Orxl156yR16XHTRRXr99df1yCOPqLy8XCkpKZo7d67+8pe/aMSIEa2e+/XXX+vw4cP6wx/+0GEN//rXv7R//373/Q8++EAffPCBJOnHP/6x4uLiFBkZqZ/+9KdavHix3nvvPdXU1CgtLU1XXXWV/vjHP7YawusnP/mJQkND9cADD+jOO+9UfHy8fvGLX+if//xni7lTYmJi9O233+o3v/mN/v73v8vhcGj69Ol69NFHW4UTt9xyi6xWq/7v//5Pn3zyiQYNGqRHH31Ut99+e4v1PNkmAAAAAOPQ0wMAAADwQRkZGRo/frw+++wzo0sBAAAAAL/BnB4AAAAAAAAAACAgEHoAAAAAAAAAAICAQOgBAAAAAAAAAAACAnN6AAAAAAAAAACAgEBPDwAAAAAAAAAAEBAIPQAAAAAAAAAAQEAg9AAAAAAAAAAAAAGB0AMAAAAAAAAAAAQEQg8AAAAAAAAAABAQCD0AAAAAAAAAAEBAIPQAAAAAAAAAAAABgdADAAAAAAAAAAAEBEIPAAAAAAAAAAAQEAg9AAAAAAAAAABAQCD0AAAAAAAAAAAAAYHQAwAAAAAAAAAABARCDwAAAAAAAAAAEBAIPQAAAAAAAAAAQEAg9AAAAAAAAAAAAAGB0AMAAAAAAAAAAAQEQg8AAAAAAAAAABAQCD0AAAAAAAAAAEBAIPQAAAAAAAAAAAABgdADAAAAAAAAAAAEBEIPAAAAAAAAAAAQEAg9AAAAAAAAAABAQCD0AAAAAAAAAAAAAYHQAwAAAAAAAAAABARCDwAAAAAAAAAAEBAIPQAAAAAAAAAAQEAg9AAAAAAAAAAAAAGB0AMAAAAAAAAAAAQEQg8AAAAAAAAAABAQCD0AAAAAAAAAAEBAIPQAAAAAAAAAAAABgdADAAAAAAAAAAAEBEIPAAAAAAAAAAAQEAg9AAAAAAAAAABAQCD0AAAAAAAAAAAAAYHQAwAAAAAAAAAABARCDwAAAAAAAAAAEBAIPQAAAAAAAAAAQEAg9AAAAAAAAAAAAAGB0AMAAAAAAAAAAAQEQg8AAAAAAAAAABAQCD0AAAAAAAAAAEBAIPQAAAAAAAAAAAABgdADAAAAAAAAAAAEBEIPAAAAAAAAAAAQEAg9AAAAAAAAAABAQCD0AAAAAAAAAAAAAYHQAwAAAAAAAAAABARCDwAAAAAAAAAAEBAIPQAAAAAAAAAAQEAg9AAAAAAAAAAAAAGB0AMAAAAAAAAAAAQEQg8AAAAAAAAAABAQCD0AAAAAAAAAAEBAIPQAAAAAAAAAAAABgdADAAAAAAAAAAAEBEIPAAAAAAAAAAAQEAg9AAAAAAAAAABAQCD0AAAAAAAAAAAAAYHQAwAAAAAAAAAABARCDwAAAAAAAAAAEBAIPQAAAAAAAAAAQEAg9AAAAAAAAAAAAAGB0AMAAAAAAAAAAAQEQg8AAAAAAAAAABAQCD0AAAAAAAAAAEBAIPQAAAAAAAAAAAABgdADAAAAAAAAAAAEBEIPAAAAAAAAAAAQEAg9AAAAAAAAAABAQCD0AAAAAAAAAAAAAYHQAwAAAAAAAAAABARCDwAAAAAAAAAAEBAIPQAAAAAAAAAAQEAg9AAAAAAAAAAA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pS9vP0XJ0WG0n5doP+8Y3X6e7LdXQg9X4LF//35988039NgAAMAPmUwmjUiN1ojUaF01tXGM+byyGq3eV9wUhJRox+EK7S2o0t6CKr215oAkqX9suKYMTdTUjARNGZqoUakxLa54BIBglp+fr8rKSj3wwAP6+9//rgcffFBffPGF5s6dq8WLF+uMM85o83m+OCwwQ+d5h/bzjq+0X26VdLjiyKmVwsp6LVy40H1/waaeP+2SEe1ssY/uONJ+R+qrr632ervBxFdeg/6K9uvc7nKprKbxPfr6p99oZNyR/li0n3doP+8Y1X6eDAnc4799XYHHrl27tHjxYiUlJfX0LgAAgEEGxEXowknpunBSuiSptLpea7NKtCarsSfIpoNlOlReq0835OrTDbmSpLgIq04elqQzRqfo9FEpSo+PMPJHAABDOZqGuLnwwgv1m9/8RpI0adIkLV++XM8880y7oYcvDQvM0Hneof2842vtt+NQhR7cuKLFsjlz5rhv377Cs94cu/42S798fb2+3l7gvj/yTy23cfVpYzRn2pBu1Xt0+zWvLzIySnPmnNqt7QYTX3sN+hvar+tW7SvWE1vWSpKmnjhVJw9Lov28RPt5x+j282RIYI9Dj8rKSu3evdt9f9++fcrMzFRiYqIGDBigSy+9VD/88IM+++wz2e12HTrU2I0zMTFRoaGhnu4OAAD4sPjIUM0c208zx/aTJNXU27X+QIk7CFm3v0RlNTZ9seWQvtjS+DfB8JQonT4qRWeMStGJQ5MUEcr4tACCR3JyskJCQjR27NgWy4855hh9//337T7PF4cFZug879B+3vGV9guxtj6t4k1dVqtVFou5w22ZzWavf/a22s/uZLhFT/jKa9Bf0X6ds1hCWtxu3l60n3doP+8Y1X6e7NPj0GPt2rWaMWOG+77raqPrrrtO9957rz755BNJjVcrNbd48WJNnz7d090BAAA/EhFq0bThyZo2PFlS48Sem3LKtHRXob7bWaD12SXaU1ClPQVVemlZlkJDzDpxaKJOH5miM0anaGRqNPOBAAhooaGhmjJlinbs2NFi+c6dOzVkSPeu3AYQWMwG/S1kdzCVOeBLnDrynnTy9gQ84nHoMX36dDk7eKd19BgAAAguIRazJg9O0OTBCZp31kiV1di0fHehluwq0JKdhcoprdHSXYVauqtQ/1i4Tf1j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" }, "metadata": {}, "output_type": "display_data" @@ -352,7 +392,11 @@ "plt.show()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:51.926130500Z", + "start_time": "2023-09-26T09:14:50.579836200Z" + } } }, { @@ -374,7 +418,11 @@ "pymeos_finalize()" ], "metadata": { - "collapsed": false + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-09-26T09:14:51.978130400Z", + "start_time": "2023-09-26T09:14:51.920528300Z" + } } } ],