# Temporal Geometry Types (Part 1)

## Spatiotemporal Types

MobilityDB provides a set of spatiotemporal types, namely, `tgeometry` (temporal geometry), `tgeography` (temporal geography), `tgeompoint` (temporal geometry point), `tgeogpoint` (temporal geography point), `tcbuffer` (temporal circular buffer), `tnpoint` (temporal network point), `tpose` (temporal pose), and `trgeometry` (temporal rigid geometry). At a conceptual level, these spatiotemporal types are organized in the hierarchy depicted in tspatial_fig.

### Hierarchy of spatiotemporal types in MobilityDB.

In this chapter and the following one we cover the type `TGeo` and its subtypes, that is, the spatiotemporal types derived from the PostGIS types `geometry` and `geography`. In the subsequent chapters we continue describing the remaining spatiotemporal types.

tpoint_tgeo illustrates the use of the `tgeompoint` (top) and the `tgeometry` (bottom) types for modelling the trajectory and the wind swath of tropical storms in 2024. The data is obtained from the National Oceanic and Atmospherique Administration (NOAA). As illustrated in the figure, the type `tgeompoint` allows linear interpolation, while the type `tgeometry` only allows step interpolation.

### Illustration of the use of the `tgeompoint` (top) and the `tgeometry` (bottom) types for modelling the trajectory and the wind swath of tropical storms in 2024.

## Notation

We presented in ttype_notation and in talpha_notation the notation used for defining the signature of the functions and operators for temporal types. We extend next this notations for spatiotemporal types.

- `tspatial` represents a spatiotemporal type, such as, `tgeometry`, `tgeompoint`, `tpose`, or `tnpoint`,

- `tgeo` represents a temporal geometry/geography type, that is, `tgeometry`, `tgeography`, `tgeompoint`, or `tgeogpoint`,

- `tgeom` represents a temporal geometry type that is, `tgeometry` or `tgeompoint`,

- `tpoint` represents a temporal point type, that is, `tgeompoint` or `tgeogpoint`,

- `spatial` represents a spatial base type, such as, `geometry`, `geography`, `pose`, or `npoint`,

- `geo` represents the types `geometry` or `geography`,

- `geompoint` represents the type `geometry` restricted to a point.

- `point` represents the types `geometry` or `geography` restricted to a point.

- `lines` represents the types `geometry` or `geography` restricted to a (multi)line.

In the following, we specify with the symbol &Z_support; that the function supports 3D geometries and with the symbol &geography_support; that the function supports geographies.

## Input and Output

- Return the Well-Known Text (WKT) or the Extended Well-Known Text (EWKT) representation &Z_support; &geography_support;

  `asText({tspatial,tspatial[],spatial[]}) → {text,text[]}`

  `asEWKT({tspatial,tspatial[],spatial[]}) → {text,text[]}`

      SELECT asText(tgeompoint 'SRID=4326;[Point(0 0 0)@2001-01-01, Point(1 1 1)@2001-01-02)');
      -- [POINT Z (0 0 0)@2001-01-01, POINT Z (1 1 1)@2001-01-02)
      SELECT asText(ARRAY[tgeometry 'SRID=4326;[Point(0 0)@2001-01-01,
        Linestring(1 1,2 1)@2001-01-02]', 'Polygon((1 1,2 2,3 1,1 1))@2001-01-01']);
      /* {"[POINT(0 0)@2001-01-01, LINESTRING(1 1,2 1)@2001-01-02]",
          "POLYGON((1 1,2 2,3 1,1 1))@2001-01-01"} */
      SELECT asText(ARRAY[geometry 'Point(0 0)', 'Point(1 1)']);
      -- {"POINT(0 0)","POINT(1 1)"}

      SELECT asEWKT(tgeompoint 'SRID=4326;[Point(0 0 0)@2001-01-01, Point(1 1 1)@2001-01-02)');
      -- SRID=4326;[POINT Z (0 0 0)@2001-01-01, POINT Z (1 1 1)@2001-01-02)
      SELECT asEWKT(ARRAY[tgeometry 'SRID=4326;[Point(0 0)@2001-01-01,
        Linestring(1 1,2 1)@2001-01-02]', 'Polygon((1 1,2 2,3 1,1 1))@2001-01-01']);
      -- {"SRID=4326;[POINT(0 0)@2001-01-01, LINESTRING(1 1,2 1)@2001-01-02]",
          "POLYGON((1 1,2 2,3 1,1 1))@2001-01-01"}
      SELECT asEWKT(ARRAY[geometry 'SRID=5676;Point(0 0)', 'SRID=5676;Point(1 1)']);
      -- {"SRID=5676;POINT(0 0)","SRID=5676;POINT(1 1)"}

- Return the Moving Features JSON representation &Z_support; &geography_support;

  `asMFJSON(tspatial,options=0,flags=0,maxdecdigits=15) → bytea`

  The `options` argument can be used to add BBOX and/or CRS in MFJSON output:

  - 0: means no option (default value)

  - 1: MFJSON BBOX

  - 2: MFJSON Short CRS (e.g., EPSG:4326)

  - 4: MFJSON Long CRS (e.g., urn:ogc:def:crs:EPSG::4326)

  The `flags` argument can be used to customize the JSON output, for example, to produce an easy-to-read (for human readers) JSON output. Refer to the documentation of the `json-c` library for the possible values. Typical values are as follows:

  - 0: means no option (default value)

  - 1: JSON_C_TO_STRING_SPACED

  - 2: JSON_C_TO_STRING_PRETTY

  The `maxdecdigits` argument can be used to set the maximum number of decimal places in the output of floating point values (default 15).

      SELECT asMFJSON(tgeompoint 'Point(1 2)@2019-01-01 18:00:00.15+02');
      /* {"type":"MovingPoint","coordinates":[[1,2]],"datetimes":["2019-01-01T17:00:00.15+01"],
          "interpolation":"None"} */
      SELECT asMFJSON(tgeometry 'SRID=3812;Linestring(1 1,1 2)@2019-01-01 18:00:00.15+02');
      /* {"type":"MovingGeometry",
          "crs":{"type":"Name","properties":{"name":"EPSG:3812"}},
          "values":[{"type":"LineString","coordinates":[[1,1],[1,2]]}],
          "datetimes":["2019-01-01T17:00:00.15+01"],"interpolation":"None"} */
      SELECT asMFJSON(tgeompoint 'SRID=4326;
        Point(50.813810 4.384260)@2019-01-01 18:00:00.15+02', 3, 0, 2);
      /* {"type":"MovingPoint","crs":{"type":"name","properties":{"name":"EPSG:4326"}},
          "stBoundedBy":{"bbox":[50.81,4.38,50.81,4.38],
          "period":{"begin":"2019-01-01 17:00:00.15+01","end":"2019-01-01 17:00:00.15+01"}},
          "coordinates":[[50.81,4.38]],"datetimes":["2019-01-01T17:00:00.15+01"],
          "interpolations":"None"} */

- Return the Well-Known Binary (WKB), the Extended Well-Known Binary (EWKB) representation, or the Hexadecimal Extended Well-Known Binary (EWKB) representation &Z_support; &geography_support;

  `asBinary(tspatial,endian text='') → bytea`

  `asEWKB(tspatial,endian text='') → bytea`

  `asHexEWKB(tspatial,endian text='') → text`

  The result is encoded using either the little-endian (NDR) or the big-endian (XDR) encoding. If no encoding is specified, then the encoding of the machine is used.

      SELECT asBinary(tgeompoint 'Point(1 2 3)@2001-01-01');
      -- \x012e0011000000000000f03f00000000000000400000000000000840009c57d3c11c0000
      SELECT asEWKB(tgeogpoint 'SRID=7844;Point(1 2 3)@2001-01-01');
      -- \x012f0071a41e0000000000000000f03f00000000000000400000000000000840009c57d3c11c0000
      SELECT asHexEWKB(tgeompoint 'SRID=3812;Point(1 2 3)@2001-01-01');
      -- 012E0051E40E0000000000000000F03F00000000000000400000000000000840009C57D3C11C0000

- Input from the Well-Known Text (WKT) or from the Extended Well-Known Text (EWKT) representation &Z_support; &geography_support;

  `tspatialFromText(text) → tspatial`

  `tspatialFromEWKT(text) → tspatial`

  In the above functions, `tspatial` replaces any spatial type, such as `tgeompoint`, `tgeometry`, or `tpose`.

      SELECT asEWKT(tgeompointFromText(text '[POINT(1 2)@2001-01-01, POINT(3 4)@2001-01-02]'));
      -- [POINT(1 2)@2001-01-01, POINT(3 4)@2001-01-02]
      SELECT asEWKT(tgeographyFromText(text
        '[Point(1 2)@2001-01-01, Linestring(1 2,3 4)@2001-01-02]'));
      -- SRID=4326;[POINT(1 2)@2001-01-01, LINESTRING(1 2,3 4)@2001-01-02]

      SELECT asEWKT(tgeompointFromEWKT(text 'SRID=3812;[Point(1 2)@2001-01-01,
        Point(3 4)@2001-01-02]'));
      -- SRID=3812;[POINT(1 2)@2001-01-01, POINT(3 4)@2001-01-02]
      SELECT asEWKT(tgeographyFromEWKT(text 'SRID=7844;[Point(1 2)@2001-01-01,
        Linestring(1 2,3 4)@2001-01-02]'));
      -- SRID=7844;[POINT(1 2)@2001-01-01, LINESTRING(1 2,3 4)@2001-01-02]

- Input from the Moving Features JSON representation &Z_support; &geography_support;

  `tspatialFromMFJSON(text) → tspatial`

  In the above function, `tspatial` replaces any spatiotemporal type, for example, `tgeompoint`, `tgeometry`, or `tpose`

      SELECT asEWKT(tgeompointFromMFJSON(text '{"type":"MovingPoint","crs":{"type":"name",
        "properties":{"name":"EPSG:4326"}},"coordinates":[[50.81,4.38]],
        "datetimes":["2019-01-01T17:00:00.15+01"],"interpolation":"None"}'));
      -- SRID=4326;POINT(50.81 4.38)@2019-01-01 17:00:00.15+01
      SELECT asEWKT(tgeogpointFromMFJSON(text '{"type":"MovingPoint","crs":{"type":"name",
        "properties":{"name":"EPSG:4326"}},"coordinates":[[50.81,4.38]],
        "datetimes":["2019-01-01T17:00:00.15+01"],"interpolation":"None"}'));
      -- SRID=4326;POINT(50.81 4.38)@2019-01-01 17:00:00.15+01
      SELECT asEWKT(tgeographyFromMFJSON(text '{"type":"MovingGeometry",
        "crs":{"type":"Name","properties":{"name":"EPSG:7844"}},
        "values":[{"type":"LineString","coordinates":[[1,1],[1,2]]}],
        "datetimes":["2019-01-01T17:00:00.15+01"],"interpolation":"None"}'));
      -- SRID=7844;LINESTRING(1 1,1 2)@2019-01-01 17:00:00.15+01

- Input from the Well-Known Binary (WKB), from the Extended Well-Known Binary (EWKB), or from the Hexadecimal Extended Well-Known Binary (HexEWKB) representation &Z_support; &geography_support;

  `tspatialFromBinary(bytea) → tspatial`

  `tspatialFromEWKB(bytea) → tspatial`

  `tspatialFromHexEWKB(text) → tspatial`

  In the previous functions, `tspatial` replaces any spatiotemporal type, for example, `tgeompoint`, `tgeometry`, or `tpose`

      SELECT asEWKT(tgeompointFromBinary(
        '\x012e0011000000000000f03f00000000000000400000000000000840009c57d3c11c0000'));
      -- POINT Z (1 2 3)@2001-01-01
      SELECT asEWKT(tgeogpointFromEWKB(
        '\x012f0071a41e0000000000000000f03f00000000000000400000000000000840009c57d3c11c0000'));
      -- SRID=7844;POINT Z (1 1 1)@2001-01-01
      SELECT asEWKT(tgeompointFromHexEWKB(
        '012E0051E40E0000000000000000F03F00000000000000400000000000000840009C57D3C11C0000'));
      -- SRID=3812;POINT(1 2 3)@2001-01-01

## Conversions

- Convert a spatiotemporal value to a spatiotemporal box

  `tspatial::stbox`

  `stbox(tspatial)`

      SELECT tgeompoint '[Point(1 1)@2001-01-01, Point(3 3)@2001-01-03]'::stbox;
      -- STBOX XT(((1,1),(3,3)),[2001-01-01, 2001-01-03])
      SELECT tgeography '[Point(1 1 1)@2001-01-01, Point(3 3 3)@2001-01-03]'::stbox;
      -- SRID=4326;GEODSTBOX ZT(((1,1,1),(3,3,3)),[2001-01-01, 2001-01-03])

- Convert between a temporal geometry and a temporal geography

  `tgeometry::tgeography`

  `tgeography::tgeometry`

  `tgeompoint::tgeogpoint`

  `tgeogpoint::tgeompoint`

      SELECT asText((tgeompoint '[Point(0 0)@2001-01-01, Point(0 1)@2001-01-02)')::tgeogpoint);
      -- [POINT(0 0)@2001-01-01, POINT(0 1)@2001-01-02)
      SELECT asText((tgeography 'Linestring(0 0,1 1)@2001-01-01')::tgeometry);
      -- LINESTRING(0 0,1 1)@2001-01-01

A common way to store temporal points in PostGIS is to represent them as geometries of type `LINESTRING M` and use the M dimension to encode timestamps as seconds since 1970-01-01 00:00:00. These time-enhanced geometries, called trajectories, can be validated with the function `ST_IsValidTrajectory` to verify that the M value is growing from each vertex to the next. Trajectories can be manipulated with the functions `ST_ClosestPointOfApproach`, `ST_DistanceCPA`, and `ST_CPAWithin`. Temporal point values can be converted to/from PostGIS trajectories.

- Convert between a temporal point and a PostGIS trajectory

  `tpoint::geo`

  `geo::tpoint`

      SELECT ST_AsText((tgeompoint 'Point(0 0)@2001-01-01')::geometry);
      -- POINT M (0 0 978307200)
      SELECT ST_AsText((tgeompoint '{Point(0 0)@2001-01-01, Point(1 1)@2001-01-02,
        Point(1 1)@2001-01-03}')::geometry);
      -- MULTIPOINT M (0 0 978307200,1 1 978393600,1 1 978480000)"
      SELECT ST_AsText((tgeompoint '[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02)')::geometry);
      -- LINESTRING M (0 0 978307200,1 1 978393600)
      SELECT ST_AsText((tgeompoint '{[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02),
        [Point(1 1)@2001-01-03, Point(1 1)@2001-01-04),
        [Point(1 1)@2001-01-05, Point(0 0)@2001-01-06)}')::geometry);
      /* MULTILINESTRING M ((0 0 978307200,1 1 978393600),(1 1 978480000,1 1 978566400),
         (1 1 978652800,0 0 978739200)) */
      SELECT ST_AsText((tgeompoint '{[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02),
        [Point(1 1)@2001-01-03],
        [Point(1 1)@2001-01-05, Point(0 0)@2001-01-06)}')::geometry);
      /* GEOMETRYCOLLECTION M (LINESTRING M (0 0 978307200,1 1 978393600),
         POINT M (1 1 978480000),LINESTRING M (1 1 978652800,0 0 978739200)) */

      SELECT asText(geometry 'LINESTRING M (0 0 978307200,0 1 978393600,
        1 1 978480000)'::tgeompoint);
      -- [POINT(0 0)@2001-01-01, POINT(0 1)@2001-01-02, POINT(1 1)@2001-01-03];
      SELECT asText(geometry 'GEOMETRYCOLLECTION M (LINESTRING M (0 0 978307200,1 1 978393600),
        POINT M (1 1 978480000),LINESTRING M (1 1 978652800,0 0 978739200))'::tgeompoint);
      /* {[POINT(0 0)@2001-01-01, POINT(1 1)@2001-01-02], [POINT(1 1)@2001-01-03],
         [POINT(1 1)@2001-01-05, POINT(0 0)@2001-01-06]} */

## Accessors

- Return the trajectory or the traversed area &Z_support; &geography_support;

  `trajectory(tpoint,unary_union=false) → geo`

  `traversedArea(tgeo,unary_union=false) → geo`

  This function is equivalent to `getValues` for temporal alphanumeric values. The last argument states whether the PostGIS function ST_UnaryUnion is applied to remove redundant geometries in the result. Notice that setting this argument to true is computationally expensive.

      SELECT ST_AsText(trajectory(tgeompoint '[Point(0 0)@2001-01-01, Point(0 1)@2001-01-02,
        Point(0 0)@2001-01-03]'));
      -- LINESTRING(0 0,0 1,0 0)
      SELECT ST_AsText(trajectory(tgeompoint '[Point(0 0)@2001-01-01, Point(0 1)@2001-01-02,
        Point(0 0)@2001-01-03]', true));
      -- LINESTRING(0 0,0 1)
      SELECT ST_AsText(trajectory(tgeompoint '{[Point(0 0)@2001-01-01, Point(0 1)@2001-01-02),
        [Point(0 1)@2001-01-03, Point(0 0)@2001-01-04)}'));
      -- LINESTRING(0 0,0 1)
      SELECT ST_AsText(trajectory(tgeompoint 'Interp=Step;{[Point(0 0)@2001-01-01,
        Point(0 1)@2001-01-02], [Point(0 1)@2001-01-03, Point(1 1)@2001-01-04]}'));
      -- MULTIPOINT((0 0),(1 1),(0 1))

      SELECT ST_AsText(traversedArea(tgeometry '[Point(1 1)@2001-01-01,
        Linestring(1 1,2 2)@2001-01-02, Point(1 1)@2001-01-03]'));
      -- GEOMETRYCOLLECTION(POINT(1 1),LINESTRING(1 1,2 2))
      SELECT ST_AsText(traversedArea(tgeometry '[Point(1 1)@2001-01-01,
        Linestring(1 1,2 2)@2001-01-02, Point(1 1)@2001-01-03]', true));
      -- LINESTRING(1 1,2 2)

- Return the centroid as a temporal point &geography_support;

  `centroid(tgeo) → tpoint`

      SELECT asText(centroid(tgeometry '[Point(1 1)@2000-01-01, Linestring(1 1,3 3)@2000-01-02,
        Polygon((1 1,4 4,7 1,1 1))@2000-01-03]'));
      -- Interp=Step;[POINT(1 1)@2000-01-01, POINT(2 2)@2000-01-02, POINT(4 2)@2000-01-03]
      SELECT asText(centroid(tgeography '[MultiPoint(1 1,4 4,7 1)@2000-01-01,
        Polygon((1 1,4 4,7 1,1 1))@2000-01-02]'),6);
      -- Interp=Step;[POINT(4 2.001727)@2000-01-01, POINT(4 2.001727)@2000-01-02]

- Return the X/Y/Z coordinate values as a temporal float &Z_support; &geography_support;

  `getX(tpoint) → tfloat`

  `getY(tpoint) → tfloat`

  `getZ(tpoint) → tfloat`

      SELECT getX(tgeompoint '{Point(1 2)@2001-01-01, Point(3 4)@2001-01-02,
        Point(5 6)@2001-01-03}');
      -- {1@2001-01-01, 3@2001-01-02, 5@2001-01-03}
      SELECT getX(tgeogpoint 'Interp=Step;[Point(1 2 3)@2001-01-01, Point(4 5 6)@2001-01-02,
        Point(7 8 9)@2001-01-03]');
      -- Interp=Step;[1@2001-01-01, 4@2001-01-02, 7@2001-01-03]
      SELECT getY(tgeompoint '{Point(1 2)@2001-01-01, Point(3 4)@2001-01-02,
        Point(5 6)@2001-01-03}');
      -- {2@2001-01-01, 4@2001-01-02, 6@2001-01-03}
      SELECT getY(tgeogpoint 'Interp=Step;[Point(1 2 3)@2001-01-01, Point(4 5 6)@2001-01-02,
        Point(7 8 9)@2001-01-03]');
      -- Interp=Step;[2@2001-01-01, 5@2001-01-02, 8@2001-01-03]
      SELECT getZ(tgeompoint '{Point(1 2)@2001-01-01, Point(3 4)@2001-01-02,
        Point(5 6)@2001-01-03}');
      -- The temporal point must have Z dimension
      SELECT getZ(tgeogpoint 'Interp=Step;[Point(1 2 3)@2001-01-01, Point(4 5 6)@2001-01-02,
        Point(7 8 9)@2001-01-03]');
      -- Interp=Step;[3@2001-01-01, 6@2001-01-02, 9@2001-01-03]

- Return true if the temporal point does not spatially self-intersect &Z_support;

  `isSimple(tpoint) → boolean`

  Notice that a temporal sequence set point is simple if every composing sequence is simple.

      SELECT isSimple(tgeompoint '[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02,
        Point(0 0)@2001-01-03]');
      -- false
      SELECT isSimple(tgeompoint '[Point(0 0 0)@2001-01-01, Point(1 1 1)@2001-01-02,
        Point(2 0 2)@2001-01-03, Point(0 0 0)@2001-01-04]');
      -- false
      SELECT isSimple(tgeompoint '{[Point(0 0 0)@2001-01-01, Point(1 1 1)@2001-01-02],
        [Point(1 1 1)@2001-01-03, Point(0 0 0)@2001-01-04]}');
      -- true

- Return the length traversed by the temporal point &Z_support; &geography_support;

  `length(tpoint) → float`

      SELECT length(tgeompoint '[Point(0 0 0)@2001-01-01, Point(1 1 1)@2001-01-02]');
      -- 1.73205080756888
      SELECT length(tgeompoint '[Point(0 0 0)@2001-01-01, Point(1 1 1)@2001-01-02,
        Point(0 0 0)@2001-01-03]');
      -- 3.46410161513775
      SELECT length(tgeompoint 'Interp=Step;[Point(0 0 0)@2001-01-01,
        Point(1 1 1)@2001-01-02, Point(0 0 0)@2001-01-03]');
      -- 0

- Return the cumulative length traversed by the temporal point &Z_support; &geography_support;

  `cumulativeLength(tpoint) → tfloatSeq`

      SELECT round(cumulativeLength(tgeompoint '{[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02,
        Point(1 0)@2001-01-03], [Point(1 0)@2001-01-04, Point(0 0)@2001-01-05]}'), 6);
      -- {[0@2001-01-01, 1.414214@2001-01-02, 2.414214@2001-01-03],
        [2.414214@2001-01-04, 3.414214@2001-01-05]}
      SELECT cumulativeLength(tgeompoint 'Interp=Step;[Point(0 0 0)@2001-01-01,
        Point(1 1 1)@2001-01-02, Point(0 0 0)@2001-01-03]');
      -- Interp=Step;[0@2001-01-01, 0@2001-01-03]

- Return the speed of the temporal point in units per second &Z_support; &geography_support;

  `speed(tpoint) → tfloatSeqSet`

  The temporal point must have linear interpolation

      SELECT speed(tgeompoint '{[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02,
        Point(1 0)@2001-01-03], [Point(1 0)@2001-01-04, Point(0 0)@2001-01-05]}') * 3600 * 24;
      /* Interp=Step;{[1.4142135623731@2001-01-01, 1@2001-01-02, 1@2001-01-03],
         [1@2001-01-04, 1@2001-01-05]} */
      SELECT speed(tgeompoint 'Interp=Step;[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02,
        Point(1 0)@2001-01-03]');
      -- ERROR:  The temporal value must have linear interpolation

- Return the time-weighted centroid &Z_support;

  `twCentroid(tgeompoint) → point`

      SELECT ST_AsText(twCentroid(tgeompoint '{[Point(0 0 0)@2001-01-01,
        Point(0 1 1)@2001-01-02, Point(0 1 1)@2001-01-03, Point(0 0 0)@2001-01-04)}'));
      -- POINT Z (0 0.666666666666667 0.666666666666667)

- Return the direction, that is, the azimuth between the start and end locations &Z_support; &geography_support;

  `direction(tpoint) → float`

  The result is expressed in radians. It is NULL if there is only one location or if the start and end locations are equal.

      SELECT round(degrees(direction(tgeompoint '[Point(0 0)@2001-01-01,
        Point(-1 -1)@2001-01-02, Point(1 1)@2001-01-03]'))::numeric, 6);
      -- 45.000000
      SELECT direction(tgeompoint '{[Point(0 0 0)@2001-01-01,
        Point(0 1 1)@2001-01-02, Point(0 1 1)@2001-01-03, Point(0 0 0)@2001-01-04)}');
      -- NULL

- Return the temporal azimuth &Z_support; &geography_support;

  `azimuth(tpoint) → tfloat`

  The result is expressed in radians. The azimut is undefined when two succesive locations are equal and in this case a temporal gap is added.

      SELECT round(degrees(azimuth(tgeompoint '[Point(0 0 0)@2001-01-01,
        Point(1 1 1)@2001-01-02, Point(1 1 1)@2001-01-03, Point(0 0 0)@2001-01-04)')));
      -- Interp=Step;{[45@2001-01-01, 45@2001-01-02], [225@2001-01-03, 225@2001-01-04)}

- Return the temporal angular difference &geography_support;

  `angularDifference(tpoint) → tfloat`

  The result is expressed in degrees.

      SELECT round(angularDifference(tgeompoint '[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02,
        Point(1 1)@2001-01-03]'), 3);
      -- {0@2001-01-01, 180@2001-01-02, 0@2001-01-03}
      SELECT round(degrees(angularDifference(tgeompoint '{[Point(1 1)@2001-01-01,
        Point(2 2)@2001-01-02], [Point(2 2)@2001-01-03, Point(1 1)@2001-01-04]}')), 3);
      -- {0@2001-01-01, 0@2001-01-02, 0@2001-01-03, 0@2001-01-04}

- Return the temporal bearing &Z_support; &geography_support;

  `bearing({tpoint,point},{tpoint,point}) → tfloat`

  Notice that this function does not accept two temporal geographic points.

      SELECT degrees(bearing(tgeompoint '[Point(1 1)@2001-01-01, Point(3 3)@2001-01-03]',
        geometry 'Point(2 2)'));
      -- [45@2001-01-01, 0@2001-01-02, 225@2001-01-03]
      SELECT round(degrees(bearing(tgeompoint '[Point(0 0)@2001-01-01, Point(2 0)@2001-01-03]',
        tgeompoint '[Point(2 1)@2001-01-01, Point(0 1)@2001-01-03]')), 3);
      --  [63.435@2001-01-01, 0@2001-01-02, 296.565@2001-01-03]
      SELECT round(degrees(bearing(tgeompoint '[Point(2 1)@2001-01-01, Point(0 1)@2001-01-03]',
        tgeompoint '[Point(0 0)@2001-01-01, Point(2 0)@2001-01-03]')), 3);
      -- [243.435@2001-01-01, 116.565@2001-01-03]

## Transformations

- Round the coordinate values to a number of decimal places &Z_support; &geography_support;

  `round(tspatial,integer=0) → tspatial`

      SELECT asText(round(tgeompoint '{Point(1.12345 1.12345 1.12345)@2001-01-01,
        Point(2 2 2)@2001-01-02, Point(1.12345 1.12345 1.12345)@2001-01-03}', 2));
      /* {POINT Z (1.12 1.12 1.12)@2001-01-01, POINT Z (2 2 2)@2001-01-02,
         POINT Z (1.12 1.12 1.12)@2001-01-03} */
      SELECT asText(round(tgeography 'Linestring(1.12345 1.12345,2.12345 2.12345)@2001-01-01', 2));
      -- LINESTRING(1.12 1.12,2.12 2.12)@2001-01-01

- Return an array of fragments of the temporal point which are simple &Z_support;

  `makeSimple(tpoint) → tgeompoint[]`

      SELECT asText(makeSimple(tgeompoint '[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02,
        Point(0 0)@2001-01-03]'));
      /* {"[POINT(0 0)@2001-01-01, POINT(1 1)@2001-01-02)",
         "[POINT(1 1)@2001-01-02, POINT(0 0)@2001-01-03]"} */
      SELECT asText(makeSimple(tgeompoint '[Point(0 0 0)@2001-01-01, Point(1 1 1)@2001-01-02,
        Point(2 0 2)@2001-01-03, Point(0 0 0)@2001-01-04]'));
      /* {"[POINT Z (0 0 0)@2001-01-01, POINT Z (1 1 1)@2001-01-02, POINT Z (2 0 2)@2001-01-03,
         POINT Z (0 0 0)@2001-01-04]"} */
      SELECT asText(makeSimple(tgeompoint '[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02,
        Point(0 1)@2001-01-03, Point(1 0)@2001-01-04]'));
      /* {"[POINT(0 0)@2001-01-01, POINT(1 1)@2001-01-02, POINT(0 1)@2001-01-03)",
          "[POINT(0 1)@2001-01-03, POINT(1 0)@2001-01-04]"} */
      SELECT asText(makeSimple(tgeompoint '{[Point(0 0 0)@2001-01-01, Point(1 1 1)@2001-01-02],
        [Point(1 1 1)@2001-01-03, Point(0 0 0)@2001-01-04]}'));
      /* {"{[POINT Z (0 0 0)@2001-01-01, POINT Z (1 1 1)@2001-01-02],
         [POINT Z (1 1 1)@2001-01-03, POINT Z (0 0 0)@2001-01-04]}"} */

- Construct a geometry/geography with M measure from a temporal point and a temporal float &Z_support; &geography_support;

  `geoMeasure(tpoint,tfloat,segmentize=false) → geo`

  The last argument `segmentize` states whether the resulting value is a either `Linestring M` or a `MultiLinestring M` where each component is a segment of two points.

      SELECT ST_AsText(geoMeasure(tgeompoint '{Point(1 1 1)@2001-01-01,
        Point(2 2 2)@2001-01-02}', '{5@2001-01-01, 5@2001-01-02}'));
      -- MULTIPOINT ZM (1 1 1 5,2 2 2 5)
      SELECT ST_AsText(geoMeasure(tgeogpoint '{[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02],
        [Point(1 1)@2001-01-03, Point(1 1)@2001-01-04]}',
        '{[5@2001-01-01, 5@2001-01-02],[7@2001-01-03, 7@2001-01-04]}'));
      -- GEOMETRYCOLLECTION M (POINT M (1 1 7),LINESTRING M (1 1 5,2 2 5))
      SELECT ST_AsText(geoMeasure(tgeompoint '[Point(1 1)@2001-01-01,
        Point(2 2)@2001-01-02, Point(1 1)@2001-01-03]',
        '[5@2001-01-01, 7@2001-01-02, 5@2001-01-03]', true));
      -- MULTILINESTRING M ((1 1 5,2 2 5),(2 2 7,1 1 7))

  A typical visualization for mobility data is to show on a map the trajectory of the moving object using different colors according to the speed. figspeed shows the result of the query below using a color ramp in QGIS.

      WITH Temp(t) AS (
        SELECT tgeompoint '[Point(0 0)@2001-01-01, Point(1 1)@2001-01-05,
          Point(2 0)@2001-01-08, Point(3 1)@2001-01-10, Point(4 0)@2001-01-11]' )
      SELECT ST_AsText(geoMeasure(t, round(speed(t) * 3600 * 24, 2), true))
      FROM Temp;
      /* MULTILINESTRING M ((0 0 0.35,1 1 0.35),(1 1 0.47,2 0 0.47),(2 0 0.71,3 1 0.71),
        (3 1 1.41,4 0 1.41)) */

  The following expression is used in QGIS to achieve this. The `scale_linear` function transforms the M value of each composing segment to the range [0, 1]. This value is then passed to the `ramp_color` function.

      ramp_color('RdYlBu', scale_linear(
          m(start_point(geometry_n($geometry,@geometry_part_num))),
            0, 2, 0, 1) )

  ### Visualizing the speed of a moving object using a color ramp in QGIS.

- Return the 3D affine transform of a temporal geometry to translate, rotate, and/or scale it in a single step &Z_support;

  `affine(tgeo,float a,float b,float c,float d,float e,float f,float g,`

  `float h,float i,float xoff,float yoff,float zoff) → tgeo`

  `affine(tgeo,float a,float b,float d,float e,float xoff,float yoff) → tgeo`

      -- Rotate a 3D temporal point 180 degrees about the z axis
      SELECT asEWKT(affine(temp, cos(pi()), -sin(pi()), 0, sin(pi()), cos(pi()), 0, 0, 0, 1,
        0, 0, 0))
      FROM (SELECT tgeompoint '[POINT(1 2 3)@2001-01-01, POINT(1 4 3)@2001-01-02]' AS temp) t;
      -- [POINT Z (-1 -2 3)@2001-01-01, POINT Z (-1 -4 3)@2001-01-02]
      SELECT asEWKT(rotate(temp, pi()))
      FROM (SELECT tgeompoint '[POINT(1 2 3)@2001-01-01, POINT(1 4 3)@2001-01-02]' AS temp) t;
      -- [POINT Z (-1 -2 3)@2001-01-01, POINT Z (-1 -4 3)@2001-01-02]
      -- Rotate a 3D temporal point 180 degrees in both the x and z axis
      SELECT asEWKT(affine(temp, cos(pi()), -sin(pi()), 0, sin(pi()), cos(pi()), -sin(pi()),
         0, sin(pi()), cos(pi()), 0, 0, 0))
      FROM (SELECT tgeometry '[Point(1 1)@2001-01-01,
        Linestring(1 1,2 2)@2001-01-02]' AS temp) t;
      -- [POINT(-1 -1)@2001-01-01, LINESTRING(-1 -1,-2 -2)@2001-01-02]

- Return the temporal point rotated counter-clockwise about the origin point

  `rotate(tgeo,float radians) → tgeo`

  `rotate(tgeo,float radians,float x0,float y0) → tgeo`

  `rotate(tgeo,float radians,geometry origin) → tgeo`

      -- Rotate a temporal point 180 degrees
      SELECT asEWKT(rotate(tgeompoint '[Point(5 10)@2001-01-01, Point(5 5)@2001-01-02,
        Point(10 5)@2001-01-03]', pi()), 6);
      -- [POINT(-5 -10)@2001-01-01, POINT(-5 -5)@2001-01-02, POINT(-10 -5)@2001-01-03]
      -- Rotate 30 degrees counter-clockwise at x=5, y=10
      SELECT asEWKT(rotate(tgeompoint '[Point(5 10)@2001-01-01, Point(5 5)@2001-01-02,
        Point(10 5)@2001-01-03]', pi()/6, 5, 10), 6);
      -- [POINT(5 10)@2001-01-01, POINT(7.5 5.67)@2001-01-02, POINT(11.83 8.17)@2001-01-03]
      -- Rotate 60 degrees clockwise from centroid
      SELECT asEWKT(rotate(temp, -pi()/3, ST_Centroid(traversedArea(temp))), 2)
      FROM (SELECT tgeometry '[Point(5 10)@2001-01-01, Point(5 5)@2001-01-02,
        Linestring(5 5,10 5)@2001-01-03]' AS temp) AS t;
      /* [POINT(10.58 9.67)@2001-01-01, POINT(6.25 7.17)@2001-01-02,
          LINESTRING(6.25 7.17,8.75 2.83)@2001-01-03] */

- Return a temporal point scaled by given factors &Z_support;

  `scale(tgeo,float Xfactor,float Yfactor,float Zfactor) → tgeo`

  `scale(tgeo,float Xfactor,float Yfactor) → tgeo`

  `scale(tgeo,geometry factor) → tgeo`

  `scale(tgeo,geometry factor,geometry origin) → tgeo`

      SELECT asEWKT(scale(tgeompoint '[Point(1 2 3)@2001-01-01, Point(1 1 1)@2001-01-02]',
        0.5, 0.75, 0.8));
      --  [POINT Z (0.5 1.5 2.4)@2001-01-01, POINT Z (0.5 0.75 0.8)@2001-01-02]
      SELECT asEWKT(scale(tgeompoint '[Point(1 2 3)@2001-01-01, Point(1 1 1)@2001-01-02]',
        0.5, 0.75));
      -- [POINT Z (0.5 1.5 3)@2001-01-01, POINT Z (0.5 0.75 1)@2001-01-02]
      SELECT asEWKT(scale(tgeompoint '[Point(1 2 3)@2001-01-01, Point(1 1 1)@2001-01-02]',
        geometry 'Point(0.5 0.75 0.8)'));
      -- [POINT Z (0.5 1.5 2.4)@2001-01-01, POINT Z (0.5 0.75 0.8)@2001-01-02]
      SELECT asEWKT(scale(tgeometry '[Point(1 1)@2001-01-01, Linestring(1 1,2 2)@2001-01-02]',
        geometry 'Point(2 2)', geometry 'Point(1 1)'));
      -- [POINT(1 1)@2001-01-01, LINESTRING(1 1,3 3)@2001-01-02]

- Transform a temporal geometric point into the coordinate space of a Mapbox Vector Tile &Z_support;

  `asMVTGeom(tpoint,bounds,extent=4096,buffer=256,clip=true) → (geom,times)`

  The result is a couple composed of a `geometry` value and an array of associated timestamp values encoded as Unix epoch. The parameters are as follows:

  - `tpoint` is the temporal point to transform

  - `bounds` is an `stbox` defining the geometric bounds of the tile contents without buffer

  - `extent` is the tile extent in tile coordinate space

  - `buffer` is the buffer distance in tile coordinate space

  - `clip` is a Boolean that determines if the resulting geometries and timestamps should be clipped or not

      SELECT ST_AsText((mvt).geom), (mvt).times
      FROM (SELECT asMVTGeom(tgeompoint '[Point(0 0)@2001-01-01, Point(100 100)@2001-01-02]',
        stbox 'STBOX X((40,40),(60,60))') AS mvt ) AS t;
      -- LINESTRING(-256 4352,4352 -256) | {946714680,946734120}
      SELECT ST_AsText((mvt).geom), (mvt).times
      FROM (SELECT asMVTGeom(tgeompoint '[Point(0 0)@2001-01-01, Point(100 100)@2001-01-02]',
        stbox 'STBOX X((40,40),(60,60))', clip:=false) AS mvt ) AS t;
      -- LINESTRING(-8192 12288,12288 -8192) | {946681200,946767600}

- Extract from a temporal point with linear interpolation the subsequences where the point stays within an area with a specified maximum size for at least the given duration &Z_support; &geography_support;

  `stops(tpoint,maxDist=0.0,minDuration='0 minutes') → tpoint`

  The size of the area is computed as the diagonal of the minimum rotated rectangle of the points in the subsequence. If `maxDist` is not given it is assumed 0.0 and thus, the function extracts the constant segments of the given temporal point. The distance is computed in the units of the coordinate system. Note that even though the function accepts 3D geometries, the computation is always performed in 2D.

      SELECT asText(stops(tgeompoint '[Point(1 1)@2001-01-01, Point(1 1)@2001-01-02,
        Point(2 2)@2001-01-03, Point(2 2)@2001-01-04]'));
      /* {[POINT(1 1)@2001-01-01, POINT(1 1)@2001-01-02), [POINT(2 2)@2001-01-03,
         POINT(2 2)@2001-01-04]} */
      SELECT asText(stops(tgeompoint '[Point(1 1 1)@2001-01-01, Point(1 1 1)@2001-01-02,
        Point(2 2 2)@2001-01-03, Point(2 2 2)@2001-01-04]', 1.75));
      /* {[POINT Z (1 1 1)@2001-01-01, POINT Z (1 1 1)@2001-01-02, POINT Z (2 2 2)@2001-01-03,
         POINT Z (2 2 2)@2001-01-04]} */