Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
MobilityDB Manual
MobilityDB Manual ii
COLLABORATORS
TITLE :
MobilityDB Manual
REVISION HISTORY
Contents
1 Introduction 1
3 Temporal Types 12
3.1 Examples of Temporal Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2 Validity of Temporal Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
6 MobilityDB Tutorial 80
6.1 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
6.2 Loading the Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
6.3 Loading the Data in Partitioned Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
6.4 Exploring the Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
6.5 Querying the Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
6.5.1 Range Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
6.5.2 Temporal Aggregate Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
6.5.3 Distance queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
6.5.4 Nearest-Neighbor Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
List of Figures
5.1 Visualizing the speed of a moving object using a color ramp in QGIS. . . . . . . . . . . . . . . . . . . . . . . . 50
MobilityDB is an extension to the PostgreSQL object-relational database system and its spatial extension PostGIS. It allows
temporal and spatio-temporal objects to be stored in the database, that is, objects whose attribute values and/or location evolves
in time. MobilityDB includes functions for analysis and processing of temporal and spatio-temporal objects and provides support
for GiST and SP-GiST indexes. MobilityDB is open source and its code is available on Github. An adapter for the Python
programming language is also available on Github.
MobilityDB is developed by the Computer & Decision Engineering Department of the Université Libre de Bruxelles (ULB)
under the direction of Prof. Esteban Zimányi. ULB is an OGC Associate Member.
MobilityDB Manual 1 / 104
Chapter 1
Introduction
MobilityDB is an extension of PostgreSQL and PostGIS that provides temporal types. Such types are data types that represent
the evolution on time of values of some element type, called the base type of the temporal type. For instance, temporal integers
may be used to represent the evolution on time of the number of employees of a department. In this case, the data type is
“temporal integer” and “integer” is the base type. Similarly, a temporal float may be used to represent the evolution on time of
the temperature of a room. As another example, a temporal point may be used to represent the evolution on time of the location of
a car, as reported by GPS devices. Temporal types are useful because representing values that evolve in time is essential in many
applications, for example in mobility applications. Furthermore, the operators on the base types (such as arithmetic operators
and aggregation for integers and floats, spatial relationships and distance for geometries) can be intuitively generalized when the
values evolve in time.
MobilityDB provides the following temporal types: tbool, tint, tfloat, ttext, tgeompoint and tgeogpoint. These
temporal types are based, respectively, on the bool, int, float, and text base types provided by PostgreSQL, and on the
geometry and geography base types provided by PostGIS (restricted to 2D or 3D points).1 Furthermore, MobilityDB uses
four time types to represent extents of time: the timestamptz type provided by PostgreSQL and three new types which
are period, timestampset, and periodset. In addition, two range types are defined in MobilityDB: intrange and
floatrange.
1 Although 4D temporal points can be represented, the M dimension is currently not taken into account.
MobilityDB Manual 2 / 104
Chapter 2
Temporal types are based on four time types: the timestamptz type provided by PostgreSQL and three new types which are
period, timestampset, and periodset.
The period type is a specialized version of the tstzrange (short for timestamp with time zone range) type provided by
PostgreSQL. Type period has similar functionality as type tstzrange but has a more efficient implementation, in particular
it is of fixed length while the tstzrange type is of variable length. Furthermore, empty periods and infinite bounds are not
allowed in period values, while they are allowed in tstzrange values.
A value of the period type has two bounds, the lower bound and the upper bound, which are timestamptz values. The
bounds can be inclusive or exclusive. An inclusive bound means that the boundary instant is included in the period, while an
exclusive bound means that the boundary instant is not included in the period. In the text form of a period value, inclusive
and exclusive lower bounds are represented, respectively, by “[” and “(”. Likewise, inclusive and exclusive upper bounds are
represented, respectively, by “]” and “)”. In a period value, the lower bound must be less than or equal to the upper bound. A
period value with equal and inclusive bounds is called an instant period and corresponds to a timestamptz value. Examples
of period values are as follows:
The timestampset type represents a set of distinct timestamptz values. A timestampset value must contain at least
one element, in which case it corresponds to a timestamptz value. The elements composing a timestampset value must
be ordered. Examples of timestampset values are as follows:
Finally, the periodset type represents a set of disjoint period values. A periodset value must contain at least one
element, in which case it corresponds to a period value. The elements composing a periodset value must be ordered.
Examples of periodset values are as follows:
MobilityDB Manual 3 / 104
Values of the periodset type are converted into a normal form so that equivalent values have identical representations. For
this, consecutive adjacent period values are merged when possible. An example of transformation into a normal form is as
follows:
Values of the timestamptz type, the tstzrange type, or the time types can be converted to one another using an explicit
CAST or using the :: notation. This is shown next.
Besides the built-in range types provided by PostgreSQL, two additional range types are defined, intrange (another name for
int4range) and floatrange.
2.1 Functions and Operators for Time Types and Range Types
We present next the functions and operators for time types. These functions and operators are polymorphic, that is, their argu-
ments may be of several types, and the result type may depend on the type of the arguments. To express this in the signature of
the operators, we use the following notation:
• A set of types such as {period, timestampset, periodset} represents any of the types listed,
• time represents any time type, that is, timestamptz, period, timestampset, or periodset,
• number represents any numeric type, that is, int or float,
• range represents any numeric range type, that is, intrange or floatrange.
• type[] represents an array of type.
In the following, for conciseness, the time part of the timestamps is omitted in the examples. Recall that in that case PostgreSQL
assumes the time 00:00:00.
The period type has a constructor function that accepts two or four arguments. The two-argument form constructs a period in
normal form, that is, with inclusive lower bound and exclusive upper bound. The four-argument form constructs a period with
bounds specified by the third and fourth arguments, which are Boolean values stating, respectively, whether the left and right
bounds are inclusive or not.
-- It is assumed that the lower bound is inclusive and the upper bound is exclusive.
SELECT period(’2012-01-01 08:00:00’, ’2012-01-03 08:00:00’);
-- Period defined with four arguments
SELECT period(’2012-01-01 08:00:00’, ’2012-01-03 09:30:00’, false, true);
The timestampset type has a constructor function that accepts a single argument which is an array of timestamptz values.
The periodset type has a constructor function that accepts a single argument which is an array of period values.
• Lower bound
lower(period): timestamptz
• Upper bound
upper(period): timestamptz
• Timespan
timespan({period, periodset}): interval
• Period on which the timestamp set or period set is defined ignoring the potential time gaps
period({timestampset, periodset}): period
• Start timestamp
startTimestamp({timestampset, periodset}): timestamptz
The function does not take into account whether the bounds are inclusive or not.
• End timestamp
endTimestamp({timestampset, periodset}): timestamptz
The function does not take into account whether the bounds are inclusive or not.
• N-th distinct timestamp
timestampN({timestampset, periodset}, int): timestamptz
The function does not take into account whether the bounds are inclusive or not.
• Distinct timestamps
timestamps({timestampset, periodset}): timestampset
The function does not take into account whether the bounds are inclusive or not.
• Number of periods
numPeriods(periodset): int
• Start period
startPeriod(periodset): period
• End period
endPeriod(periodset): period
MobilityDB Manual 7 / 104
• N-th period
periodN(periodset, int): period
• Periods
periods(periodset): period[]
• Shift
shift({timestampset,period,periodset}): {timestampset,period,periodset}
2.1.3 Operators
In PostgreSQL, the range operators <<, &<, >>, &>, and -|- only accept a range as left or right argument. We extended these
operators for numeric ranges so that one argument may be an integer or a float.
The comparison operators (=, <, and so on) require that the left and right arguments be of the same type. Excepted equality and
inequality, the other comparison operators are not useful in the real world but allow B-tree indexes to be constructed on time
types.
The operators available for the time types and range types are given next.
• Equal
time = time
• Not equal
time <> time
MobilityDB Manual 8 / 104
• Less than
time < time
• Greater than
time > time
• Contains
{timestampset, period, periodset} @> time
• Is contained by
time <@ {timestampset, period, periodset}
• Is adjacent to
time -|- time
{number, range} -|- {number, range}
• Scritly left of
{number, range} << {number, range}
• Strictly right of
{number, range} >> {number, range}
• Scritly before
time <<# time
• Strictly after
time #>> time
MobilityDB Manual 10 / 104
• Is not after of
time &<# time
• Is not before of
time #&> time
• Union
time + time
• Intersection
time * time
• Difference
time - time
GiST and SP-GiST indexes can be created for table columns of the timestampset, period, and periodset types. An
example of creation of a GiST index in a column during of type period in a table reservation is as follows:
A GiST or SP-GiST index can accelerate queries involving the following operators: =, &&, <@, @>, -|-, <<, >>, &<, and &>.
In addition, B-tree indexes can be created for table columns of a time type. For these index types, basically the only useful
operation is equality. There is a B-tree sort ordering defined for values of time types, with corresponding < and > operators, but
the ordering is rather arbitrary and not usually useful in the real world. The B-tree support is primarily meant to allow sorting
internally in queries, rather than creation of actual indexes.
MobilityDB Manual 12 / 104
Chapter 3
Temporal Types
There are six built-in temporal types, tbool, tint, tfloat, ttext, tgeompoint, and tgeogpoint, which are, respec-
tively, based on the base types bool, int, float, text, geometry, and geography (the last two types restricted to 2D or
3D points with Z dimension).
The interpolation of a temporal value states how the value evolves between successive instants. The interpolation is stepwise
when the value remains constant between two successive instants. For example, the number of employees of a department may
be represented with a temporal integer, which indicates that its value is constant between two time instants. On the other hand, the
interpolation is linear when the value evolves linearly between two successive instants. For example, the temperature of a room
may be represented with a temporal float, which indicates that the values are known at the two time instants but continuously
evolve between them. Similarly, the location of a vehicule may be represented by a temporal point where the location between
two consecutive GPS readings is obtained by linear interpolation. Temporal types based on discrete base types, that is the tbool,
tint, or ttext evolve necesssarily in a stepwise manner. On the other hand, temporal types based on continuous base types,
that is tfloat, tgeompoint, or tgeogpoint may evolve in a stepwise or linear manner.
The duration of a temporal value states the temporal extent at which the evolution of values is recorded. Temporal values come
in four durations, namely, instant, instant set, sequence, and sequence set.
A temporal value of instant duration (briefly, an instant value) represents the value at a time instant, for example
A temporal value of instant set duration (briefly, an instant set value) represents the evolution of the value at a set of time instants,
where the values between these instants are unknown. An example is as follows:
A temporal value of sequence duration (briefly, a sequence value) represents the evolution of the value during a sequence of time
instants, where the values between these instants are interpolated using either a stepwise or a linear function (see below). An
example is as follows:
As can be seen, a value of a type with sequence duration has a lower and an upper bound that can be inclusive (represented by
‘[’ and ‘]’) or exclusive (represented by ‘(’ and ‘)’). A temporal sequence value with a single instant such as
represents that the value evolves linearly from 10 to 20 during (2018-01-01 08:00:00, 2018-01-01 08:05:00)
and evolves from 20 to 15 during [2018-01-01 08:05:00, 2018-01-01 08:10:00].
Finally, a temporal value of sequence set duration (briefly, a sequence set value) represents the evolution of the value at a set of
sequences, where the values between these sequences are unknown. An example is as follows:
Temporal values with instant or sequence duration are called temporal unit values, while temporal values with instant set or
sequence set duration are called temporal set values. Temporal set values can be thought of as an array of the corresponding
unit values. Temporal set values must be uniform, that is, they must be constructed from unit values of the same base type and
the same duration.
Temporal sequence values are converted into a normal form so that equivalent values have identical representations. For this,
consecutive instant values are merged when possible. For stepwise interpolation, three consecutive instant values can be merged
into two if they have the same value. For linear interpolation, three consecutive instant values can be merged into two if the linear
functions defining the evolution of values are the same. Examples of transformation into a normal form are as follows.
Similary, temporal sequence set values are converted into a normal form. For this, consecutive sequence values are merged when
possible. Examples of transformation into a normal form are as follows.
Temporal types support type modifiers (or typmod in PostgreSQL terminology), which specify additional information for a
column definition. For example, in the following table definition:
the type modifier for the type varchar is the value 25, which indicates the maximum length of the values of the column, while
the type modifier for the type tint is the string Sequence, which restricts the duration of the values of the column to be
sequences. In the case of temporal alphanumeric types (that is, tbool, tint, tfloat, and ttext), the possible values for
the type modifier are Instant, InstantSet, Sequence, and SequenceSet. If no type modifier is specified for a column,
values of any duration are allowed.
On the other hand, in the case of temporal point types (that is, tgeompoint or tgeogpoint) the type modifier may be used
to specify specify the duration, the dimensionality, and/or the spatial reference identifier (SRID). For example, in the following
table definition:
the type modifier for the type tgeogpoint is composed of three values, the first one indicating the duration as above, the second
one the spatial type of the geographies composing the temporal point, and the last one the SRID of the composing geographies.
For temporal points, the possible values for the first argument of the type modifier are as above, those for the second argument
are either Point or PointZ, and those for the third argument are valid SRIDs. All the three arguments are optional and if any
of them is not specified for a column, values of any duration, dimensionality, and/or SRID are allowed.
Each temporal type is associated to another type, referred to as its bounding box, which represent its extent in the value and/or
the time dimension. The bounding box of the various temporal types are as follows:
• The period type for the tbool and ttext types, where only the temporal extent is considered.
• A tbox (temporal box) type for the tint and tfloat types, where the value extent is defined in the X dimension and the
temporal extent in the T dimension.
• A stbox (spatiotemporal box) type for the tgeompoint and tgeogpoint types, where the spatial extent is defined in the
X, Y, and Z dimensions, and the temporal extent in the T dimension.
A rich set of functions and operators is available to perform various operations on temporal types. They are explained in
Chapter 5. Some of these operations, in particular those related to indexes, manipulate bounding boxes for efficiency reasons.
Values of temporal types must satisfy several constraints so that they are well defined. These constraints are given next.
• The constraints on the corresponding base type and timestamptz types must be satisfied.
• A temporal sequence value must be composed of at least one instant value.
• An instantaneous temporal sequence value must have inclusive lower and upper bounds.
• In a temporal sequence value, the timestamps of the composing instants must be distinct and ordered.
• In a temporal sequence value with stepwise interpolation, the last two values must be equal if upper bound is exclusive.
• A temporal set value must be composed of at least one temporal unit value.
• In a temporal instant set value, the composing instants must be distinct and ordered. This implies that the temporal extent of a
temporal instant set value is an ordered set of timestamptz values without duplicates.
• In a temporal sequence set value, the composing sequence values must be non overlapping and ordered. This implies that the
temporal extent of a temporal sequence set value is an ordered set of disjoint periods.
An error is raised whenever one of these constraints are not satisfied. Examples of incorrect temporal values are as follows.
Chapter 4
A tbox is composed of value and/or time dimensions. For each dimension, minimum and maximum values are given. Examples
of input of tbox values are as follows:
An stbox is composed of values and/or time dimensions, where the coordinates of the value dimension may be 2D or 3D. For
each dimension, minimum and maximum values are given. The coordinates may be Cartesian (planar) or geodetic (spherical).
The SRID of the coordinates may be specified; if it is not the case, a value of 0 (unknown) and 4326 (corresponding to WGS84)
is assumed, respectively, for planar and geodetic boxes. Examples of input of stbox values are as follows:
Type tbox has several constructor functions depending on whether the value and/or the time dimensions are given. These
functions have two arguments for the minimum and maximum float values and/or two arguments for the minimum and
maximum timestamptz values.
Type stbox has several constructor functions depending on whether the coordinates and/or the time dimensions are given.
Furthermore, the coordinates can be 2D or 3D and can be either Cartesian or geodetic. These functions have several arguments
for the minimum and maximum coordinate values and/or two arguments for the minimum and maximum timestamptz values.
The SRID can be specified in an optional last argument. If not given, a value 0 (respectively 4326) is assumed by default for
planar (respectively geodetic) boxes.
4.3 Casting
• Minimum X value
Xmin({tbox, stbox}): float
• Maximum X value
Xmax({tbox, stbox}): float
• Minimum Y value
Ymin(stbox): float
• Maximum Y value
Ymax(stbox): float
• Minimum Z value
Zmin(stbox): float
• Maximum Z value
Zmax(stbox): float
• Minimum timestamp
Tmin({tbox, stbox}): timestamptz
• Maximum timestamp
Tmax({tbox, stbox}): timestamptz
The functions given next expand the bounding boxes on the value and the time dimension or set the precision of the value
dimension. These functions raise an error if the corresponding dimension is not present.
• SRID
SRID(stbox): int
SELECT SRID(stbox ’STBOX ZT((1.0, 2.0, 3.0, 2000-01-01), (4.0, 5.0, 6.0, 2000-01-02))’);
-- 0
SELECT SRID(stbox ’SRID=5676;STBOX T((1.0, 2.0, 2000-01-01), (4.0, 5.0, 2000-01-02))’);
-- 5676
SELECT SRID(geodstbox ’GEODSTBOX T((, , 2000-01-01), (, , 2000-01-02))’);
-- 4326
These operators test whether the bounding boxes satisfy the predicate and result in a Boolean value.
A first set of operators consider the relative position of the bounding boxes. The operators <<, >>, &<, and &> consider the value
dimension for the tbox type and the X coordinates for the stbox type, the operators <<|, |>>, &<|, and |&> consider the Y
coordinates for the stbox type, the operators <</, />>, &</, and /&> consider the Z coordinates for the stbox type, and the
operators <<#, #>>, #&<, and #&> consider the time dimension for both tbox and stbox types. The operators raise an error
if the boxes do not have both the required dimension.
The operators for the value dimension of the tbox type are given next.
• Is the first bounding box always strictly less than the second one?
tbox << tbox: boolean
• Is the first bounding box always strictly greater than the second one?
tbox >> tbox: boolean
• Is the first bounding box never greater than the second one?
tbox &< tbox: boolean
• Is the first bounding box never less than the second one?
tbox &> tbox: boolean
The operators for the value dimension of the stbox type are given next.
• Is the first bounding box strictly to the left of the second one?
stbox << stbox: boolean
• Is the first bounding box strictly to the right of the second one?
stbox >> stbox: boolean
• The first bounding box does not extend to the right of the second one?
stbox &< stbox: boolean
• The first bounding box does not extend to the left of the second one?
stbox &> stbox: boolean
• The first bounding box does not extend above of the second one?
stbox &<| stbox: boolean
• The first bounding box does not extend below of the second one?
stbox |&> stbox: boolean
• The first bounding box does not extend in front of the second one?
stbox &</ stbox: boolean
• The first bounding box does not extend back of the second one?
stbox /&> stbox: boolean
The operators for the time dimension of the tbox and stbox types are as follows.
Another set of operators consider the topological relationships between the bounding boxes. There are five topological operators:
overlaps (&&), contains (@>), contained (<@), same (~=), and adjacent (-|-). The operators verify the topological relationship
taking into account the value and/or the time dimension for as many dimensions that are present on both arguments.
The topological operators for temporal alphanumeric types are given next.
• Are the bounding boxes adjacent? Two boxes are adjacent if they share n dimensions and their intersection is at most of n-1
dimensions.
{tbox, stbox} -|- {tbox, stbox}: boolean
Finally, the set operators for box types are union (+) and intersection (*). In the case of union, the operands must have exactly
the same dimensions, otherwise an error is raided. Furthermore, if the operands do not overlap on all the dimensions and error
is raised, since in this would result in a box with disjoint values, which cannot be represented. The operator computes the
union on all dimensions that are present in both arguments. In the case of intersection, the operands must have at least one
common dimension, otherwise an error is raised. The operator computes the intersection on all dimensions that are present in
both arguments.
• Union
tbox + tbox: tbox, stbox + stbox: stbox
• Intersection
tbox * tbox: tbox, stbox * stbox: stbox
MobilityDB Manual 27 / 104
The traditional comparison operators (=, <, and so on) can be applied to box types. Excepted equality and inequality, the other
comparison operators are not useful in the real world but allow B-tree indexes to be constructed on bos types. These operators
compare first the timestamps and if those are equal, compare the values.
• Equal
{tbox, stbox} = {tbox, stbox}: boolean
• Not equal
{tbox, stbox} <> {tbox, stbox}: boolean
• Less than
{tbox, stbox} < {tbox, stbox}: boolean
• Greater than
{tbox, stbox} > {tbox, stbox}: boolean
GiST and SP-GiST indexes can be created for table columns of the tbox and stbox types. An example of creation of a GiST
index in a column bbox of type stbox in a table Trips is as follows:
CREATE TABLE Trips(TripID integer PRIMARY KEY, Trip tgeompoint, bbox stbox);
CREATE INDEX trips_bbox_idx ON Trips USING GIST(bbox);
A GiST or SP-GiST index can accelerate queries involving the following operators: &&, <@, @>, ~=, -|-, <<, >>, &<, &>, <<|,
|>>, &<|, |&>, <</, />>, &</, /&>, <<#, #>>, &<#, and #&>.
In addition, B-tree indexes can be created for table columns of a box type. For these index types, basically the only useful
operation is equality. There is a B-tree sort ordering defined for values of time types, with corresponding < and > operators, but
the ordering is rather arbitrary and not usually useful in the real world. The B-tree support is primarily meant to allow sorting
internally in queries, rather than creation of actual indexes.
MobilityDB Manual 29 / 104
Chapter 5
We present next the functions and operators for temporal types. These functions and operators are polymorphic, that is, their
arguments may be of several types, and the result type may depend on the type of the arguments. To express this, we use the
following notation:
• torder represents any temporal type whose base type has a total order defined, that is, tint, tfloat, or ttext,
• tpoint represents a temporal point type, that is, tgeompoint or tgeogpoint,
• ttypeinst represents any temporal type with instant duration,
• ttypei represents any temporal type with instant set duration,
A common way to generalize the traditional operations to the temporal types is to apply the operation at each instant, which
yields a temporal value as result. In that case, the operation is only defined on the intersection of the emporal extents of the
operands; if the temporal extents are disjoint, then the result is null. For example, the temporal comparison operators, such as
#<, test whether the values taken by their operands at each instant satisfy the condition and return a temporal Boolean. Examples
of the various generalizations of the operators are given next.
MobilityDB Manual 30 / 104
-- Temporal comparison
SELECT tint ’[2@2001-01-01, 2@2001-01-03)’ #< tfloat ’[1@2001-01-01, 3@2001-01-03)’;
-- "{[f@2001-01-01, f@2001-01-02], (t@2001-01-02, t@2001-01-03)}"
SELECT tfloat ’[1@2001-01-01, 3@2001-01-03)’ #< tfloat ’[3@2001-01-03, 1@2001-01-05)’;
-- NULL
-- Temporal addition
SELECT tint ’[1@2001-01-01, 1@2001-01-03)’ + tint ’[2@2001-01-02, 2@2001-01-05)’;
-- "[3@2001-01-02, 3@2001-01-03)"
-- Temporal intersects
SELECT tintersects(tgeompoint ’[Point(0 1)@2001-01-01, Point(3 1)@2001-01-04)’,
geometry ’Polygon((1 0,1 2,2 2,2 0,1 0))’);
-- "{[f@2001-01-01, t@2001-01-02, t@2001-01-03], (f@2001-01-03, f@2001-01-04]}"
-- Temporal distance
SELECT tgeompoint ’[Point(0 0)@2001-01-01 08:00:00, Point(0 1)@2001-01-03 08:10:00)’ <->
tgeompoint ’[Point(0 0)@2001-01-02 08:05:00, Point(1 1)@2001-01-05 08:15:00)’;
-- "[0.5@2001-01-02 08:05:00+00, 0.745184033794557@2001-01-03 08:10:00+00)"
Another common requirement is to determine whether the operands ever or always satisfy a condition with respect to an operation.
These can be obtained by applying the ever/always comparison operators. These operators are denoted by prefixing the traditional
comparison operators with, respectively, ? (ever) and % (always). Examples of ever and always comparison operators are given
next.
For efficiency reasons, some common operations with the ever or the always semantics are natively provided. For example, the
intersects function determines whether there is an instant at which the two arguments spatially intersect.
We describe next the functions and operators for temporal types. For conciseness, in the examples we mostly use sequences
composed of two instants.
A temporal instant value is a couple of the form v@t, where v is a value of the base type and t is a timestamptz value. A
temporal sequence value is a set of values v1@t1,...,vn@tn delimited by a lower and an upper bounds that can be inclusive
(represented by ‘[’ and ‘]’) or exclusive (represented by ‘(’ and ‘)’). Examples of input of temporal unit values are as follows:
The temporal extent of a temporal instant value is a single instant while the temporal extent of temporal sequence value is a
period defined by the first and last instants as well as the upper and lower bounds.
A temporal set value is a set {v1,...,vn} where every vi is a temporal unit value of the corresponding type. Examples of
input of temporal set values are as follows:
The temporal extent of a temporal instant set value is a set of instants while the temporal extent of temporal sequence set value is
a set of periods.
Temporal values of sequence or sequence set duration whose base type is continuous may specify that the interpolation is
stepwise. If this is not specified, it is supposed that the interpolation is linear by default.
For temporal values of sequence set duration all component sequences are supposed to be in the same interpolation, either
stepwise or linear, as in the examples above.
For temporal points, it is possible to specify the spatial reference identifier (SRID) using the Extended Well-Known text (EWKT)
representation as follows:
All components geometries will then be of the given SRID. Furthermore, each component geometry can specify its SRID with
the EWKT format as in the following example
An error is raised if the component geometries are not all in the same SRID or if the SRID of a component geometry is different
from the one of the temporal point
Each temporal type has two constructor functions with the same name as the type and with a suffix for the duration, where the
suffix ‘inst’, ‘i’, ‘seq’, and ‘s’ correspond, respectively, to the durations instant, instant set, sequence, and sequence set.
Examples are tintseq or tgeompoints. Using the constructor function is frequently more convenient than writing a literal
constant.
A first set of constructor functions have two arguments, a base type and a time type, where the latter is a timestamptz, a
timestampset, a period, or a periodset value for constructing, respectively, a temporal instant, instant set, sequence,
or sequence set value. The constructors for temporal sequence or sequence set values with continuous base type have in addition
an optional third argument which is a Boolean for stating whether the resulting temporal value has linear interpolation or not. By
default this argument is true if it is not specified.
Another set of constructor functions for temporal instant set values have a single argument, which is an array of values of the
corresponding instant values.
• Constructor for temporal types of instant set duration from an array of instants
ttypei(ttypeinst[]]): ttypei
MobilityDB Manual 33 / 104
Another set of constructor functions for temporal sequence values have one argument for the array of values of the corresponding
instant duration and two optional Boolean arguments stating, respectively, whether the left and right bounds are inclusive or
exclusive. It the these arguments are not specified they are assumed to be true by default. In addition, the constructor functions
for temporal sequence values with continuous base type have an additional Boolean argument stating whether the interpolation
is linear or not. It this argument is not specified it is assumed to be true by default.
Another set of constructor functions for temporal sequence set values have a single argument, which is an array of values of
the corresponding sequence values. For temporal sequence values with continuous base type, the interpolation of the resulting
temporal value depends on the interpolation of the composing sequences. An error is raised if the sequences composing the array
have different interpolation.
• Constructor for temporal types of sequence set duration from an array of sequences
ttypes(ttypeseq[]): ttypes
5.3 Casting
A temporal value can be converted into a temporal value of a compatible type. This can be done using the notation CAST(ttype1
AS ttype2) or ttype1::ttype2.
A common way to store temporal points in PostGIS is to represent them as geometries of type LINESTRING M and abuse 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.
MobilityDB Manual 35 / 104
A temporal value can be transformed to another duration. An error is raised if the durations are incompatible.
• Transform a temporal value with continuous base type from stepwise to linear interpolation
toLinear(ttype) : ttype
• Append two temporal values of the same duration. The values may share a single instant, in that case the temporal values are
joined in the result.
append(ttype, ttype) : ttype
• Append an array of temporal values of the same duration. Two consecutive values may share a single instant, in that case the
temporal values are joined in the result.
append(ttype[]) : ttype
• Duration
duration(ttype): {’Instant’, ’InstantSet’, ’Sequence’, ’SequenceSet’}
• Interpolation
interpolation(ttype): {’Discrete’, ’Stepwise’, ’Linear’}
• Value
getValue(ttypeinst): base
• Values
getValues(ttype): {base[], floatrange[], geo}
• Start value
startValue(ttype): base
The function does not take into account whether the bounds are inclusive or not.
• End value
endValue(ttype): base
The function does not take into account whether the bounds are inclusive or not.
• Minimum value
minValue(torder): base
The function does not take into account whether the bounds are inclusive or not.
• Maximum value
maxValue(torder): base
MobilityDB Manual 39 / 104
The function does not take into account whether the bounds are inclusive or not.
• Value range
valueRange(tnumber): numrange
The function does not take into account whether the bounds are inclusive or not.
• Time
getTimestamp(ttypeinst): timestamptz
• Time
getTime(ttype): periodset
• Timespan
timespan(ttype): interval
• Period on which the temporal value is defined ignoring the potential time gaps
period(ttype): period
• Start instant
startInstant(ttype): ttypeinst
The function does not take into account whether the bounds are inclusive or not.
• End instant
endInstant(ttype): ttypeinst
The function does not take into account whether the bounds are inclusive or not.
• N-th distinct instant
instantN(ttype, int): ttypeinst
• Distinct instants
instants(ttype): ttypeinst[]
• Start timestamp
startTimestamp(ttype): timestamptz
The function does not take into account whether the bounds are inclusive or not.
• End timestamp
endTimestamp(ttype): timestamptz
The function does not take into account whether the bounds are inclusive or not.
• N-th distinct timestamp
timestampN(ttype, int): timestamptz
• Distinct timestamps
timestamps(ttype): timestamptz[]
• Number of sequences
numSequences({ttypeseq,ttypes}): int
• Start sequence
startSequence({ttypeseq,ttypes}): ttypeseq
• End sequence
endSequence({ttypeseq,ttypes}): ttypeseq
• N-th sequence
sequenceN({ttypeseq,ttypes}, int): ttypeseq
• Sequences
sequences({ttypeseq,ttypes}): ttypeseq[]
• Shift
shift(ttype): ttype
• Time-weighted average
twAvg(tnumber): float
In the following, we specify with the symbol that the function supports 3D points and with the symbol that the function
is available for geographies.
• Well-Known Binary (WKB) representation using either little-endian (NDR) or big-endian (XDR) encoding. If no encoding is
• Extended Well-Known Binary (EWKB) representation using either little-endian (NDR) or big-endian (XDR) encoding. If no
• Hexadecimal Extended Well-Known Binary (EWKB) representation (as text) using either little-endian (NDR) or big-endian
SELECT asEWKT(fromEWKB(bytea
’\0011\346\020\000\000\000\000\000\000\000\000\360?\000\000\000\000\000\000\000@ ←-
\000\000\000\000\000\000\010@\000\374\340\023jX\001\000’));
-- "SRID=4326;POINT Z (1 2 3)@2012-01-01 00:00:00+01"
SELECT cumulativeLength(tgeompoint
’{[Point(0 0)@2000-01-01, Point(1 1)@2000-01-02, Point(1 0)@2000-01-03],
[Point(1 0)@2000-01-04, Point(0 0)@2000-01-05]}’);
-- "{[0@2000-01-01, 1.4142135623731@2000-01-02, 2.41421356237309@2000-01-03],
[2.41421356237309@2000-01-04, 3.41421356237309@2000-01-05]}"
SELECT cumulativeLength(tgeompoint ’Interp=Stepwise;[Point(0 0 0)@2000-01-01, Point(1 1 1) ←-
@2000-01-02,
Point(0 0 0)@2000-01-03]’);
-- "Interp=Stepwise;[0@2000-01-01, 0@2000-01-02, 0@2000-01-03]"
• Time-weighted centroid
twCentroid(tgeompoint): point
• Temporal azimuth
azimuth(tpoint): tfloat
• Instant of the first temporal point at which the two arguments are at the nearest distance. The function will only return the first
instant that it finds if there are more than one. The resulting instant may be at an exclusive bound.
nearestApproachInstant({geo, tpoint}, {geo, tpoint}): tpoint
SELECT to_timestamp(ST_ClosestPointOfApproach(
tgeompoint ’[Point(0 0 0)@2012-01-01, Point(1 1 1)@2012-01-03,
Point(0 0 0)@2012-01-05)’::geometry,
tgeompoint ’[Point(2 0 0)@2012-01-02, Point(1 1 1)@2012-01-04,
Point(2 2 2)@2012-01-06)’::geometry));
-- "2012-01-03 12:00:00+00"
Function nearestApproachDistance has an associated operator |=| that can be used for doing nearest neightbor
searches using a GiST index (see Section 5.9). This function corresponds to the function ST_DistanceCPA provided by
PostGIS, altough the latter requires both arguments to be a trajectory.
SELECT ST_DistanceCPA(
tgeompoint ’[Point(0 0 0)@2012-01-01, Point(1 1 1)@2012-01-03,
Point(0 0 0)@2012-01-05)’::geometry,
tgeompoint ’[Point(2 0 0)@2012-01-02, Point(1 1 1)@2012-01-04,
Point(2 2 2)@2012-01-06)’::geometry);
-- "0.5"
MobilityDB Manual 48 / 104
• Line connecting the nearest approach point between the two arguments. The function will only return the first line that it finds
Function shortestLine can be used to obtain the result provided by the PostGIS function ST_CPAWithin when both
arguments are trajectories as shown next.
SELECT ST_Length(shortestLine(
tgeompoint ’[Point(0 0 0)@2012-01-01, Point(1 1 1)@2012-01-03,
Point(0 0 0)@2012-01-05)’,
tgeompoint ’[Point(2 0 0)@2012-01-02, Point(1 1 1)@2012-01-04,
Point(2 2 2)@2012-01-06)’)) <= 0.5;
-- true
SELECT ST_CPAWithin(
tgeompoint ’[Point(0 0 0)@2012-01-01, Point(1 1 1)@2012-01-03,
Point(0 0 0)@2012-01-05)’::geometry,
tgeompoint ’[Point(2 0 0)@2012-01-02, Point(1 1 1)@2012-01-04,
Point(2 2 2)@2012-01-06)’::geometry, 0.5);
-- true
A typical use for the simplify function is to reduce the size of a dataset, in particular for visualization purposes. For example,
the reduction obtained by applying the simplify function to the BerlinMOD dataset used in Chapter 6 is illustrated next.
• Construct a geometry/geography with M measure from a temporal point and a temporal float
geoMeasure(tpoint, tfloat, segmentize = false): geo
The last segmentize argument states whether a resulting Linestring M value is transformed into a MultiLinestring M, where
each component is a segment of two points.
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. Figure 5.1 shows the result of the query below using a color ramp in QGIS.
WITH Temp(t) AS (
SELECT tgeompoint ’[Point(0 0)@2012-01-01, Point(1 1)@2012-01-05,
Point(2 0)@2012-01-08, Point(3 1)@2012-01-10, Point(4 0)@2012-01-11]’
MobilityDB Manual 50 / 104
)
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
)
)
Figure 5.1: Visualizing the speed of a moving object using a color ramp in QGIS.
These functions restrict the temporal value with respect to a value extent or a time extent.
• Restrict to value
atValue(ttype, base): ttype
• Restrict to values
atValues(ttype, base[]): ttype
• Restrict to range
atRange(tnumber, numrange): ttype
MobilityDB Manual 51 / 104
• Restrict to ranges
atRanges(tnumber, numrange[]): ttype
The function does not take into account whether the bounds are inclusive or not.
• Restrict to maximum value
atMax(torder): torder
The function does not take into account whether the bounds are inclusive or not.
• Restrict to geometry
atGeometry(tgeompoint, geometry): tgeompoint
• Value at timestamp
valueAtTimestamp(ttype, timestamptz): base
• Restrict to timestamp
atTimestamp(ttype, timestamptz): ttypeinst
• Restrict to period
atPeriod(ttype, period): ttype
These functions restrict the temporal value with respect to the complement of a value/range or a time extent.
The traditional comparison operators (=, <, and so on) require that the left and right operands be of the same base type. Excepted
equality and inequality, the other comparison operators are not useful in the real world but allow B-tree indexes to be constructed
on temporal types. These operators compare the bounding boxes (see Section 4.8) and if those are equal, then the comparison
depends on the duration. For temporal instant values, they compare first the timestamps and if those are equal, compare the
values. For temporal instant set and sequence values, they compare the first N instants, where N is the minimum of the number
of composing instants of both values. Finally, for temporal sequence set values, they compare the first N sequence values, where
N is the minimum of the number of composing sequences of both values.
The equality and inequality operators consider the normal forms of the temporal types and the equivalent representation for
different durations as shown next.
MobilityDB Manual 55 / 104
• Equal
ttype = ttype: boolean
• Not equal
ttype <> ttype: boolean
• Less than
ttype < ttype: boolean
• Greater than
ttype > ttype: boolean
A possible generalization of the traditional comparison operators (=, <>, <, <=, etc.) to temporal types, is to determine whether
the comparison is ever or always true. In this case, the result is a Boolean value. MobilityDB provides operators to test whether
the comparison of a temporal value and a value of the base type is ever or always true. These operators are denoted by prefixing the
traditional comparison operators with, respectively, ? (ever) and % (always). Some examples are ?=, %<>, or ?<=. Ever/always
equality and non-equality are available for all temporal types, while ever/always inequalities are only available for temporal types
whose base type has a total order defined, that is, tint, tfloat, or ttext. The ever and always comparisons are inverse
operators: for example, ?= is the inverse of %<>, and ?> is the inverse of %<=.
The function does not take into account whether the bounds are inclusive or not.
• Is ever different from the value?
ttype ?<> base: bool
The function does not take into account whether the bounds are inclusive or not.
• Is always different to the value?
ttype @<> base: bool
Another possible generalization of the traditional comparison operators (=, <>, <, <=, etc.) to temporal types, is to determine
whether the comparison is true or false at each instant. In this case, the result is a temporal Boolean. The temporal comparison
operators are denoted by prefixing the traditional comparison operators with #. Some examples are #= or #<=. Temporal
equality and non-equality are available for all temporal types, while temporal inequalities are only available for temporal types
whose base type has a total order defined, that is, tint, tfloat, or ttext.
• Temporal equal
{base, ttype} #= {base, ttype}: tbool
• Addition
{number, tnumber} + {number, tnumber}: tnumber
• Substraction
{number, tnumber} - {number, tnumber}: tnumber
• Multiplication
{number, tnumber} * {number, tnumber}: tnumber
• Division
{number, tnumber} / {number, tnumber}: tnumber
The function will raise an error if the denominator will ever be equal to zero during the common timespan of the arguments.
• Radians to degrees
degrees(tfloat): tfloat
• Boolean and
{bool, tbool} & {bool, tbool}: tbool
• Boolean or
{bool, tbool} | {bool, tbool}: tbool
• Boolean not
~ {bool, tbool}: tbool
• Concatenation
{text, ttext} || {text, ttext}: ttext
• Transform to uppercase
upper(ttext): ttext
• Transform to lowercase
lower(ttext): ttext
These operators test whether the bounding boxes of their arguments satisfy the predicate and result in a Boolean value. As stated
in Chapter 3, the bounding box associated to a temporal type depends on the base type: It is the period type for the tbool and
ttext types, the tbox type for the tint and tfloat types, and the stbox type for the tgeompoint and tgeogpoint
types.
A first set of operators consider the relative position of the bounding boxes. The operators <<, >>, &<, and &> consider the value
dimension for tint and tfloat types and the X coordinates for the tgeompoint and tgeogpoint types, the operators
MobilityDB Manual 62 / 104
<<|, |>>, &<|, and |&> consider the Y coordinates for the tgeompoint and tgeogpoint types, the operators <</, />>,
&</, and /&> consider the Z coordinates for the tgeompoint and tgeogpoint types, and the operators <<#, #>>, #&<,
and #&> consider the time dimension for all temporal types. The operators for the value dimension of the tint and tfloat
types are given next.
• Is the first bounding box always strictly less than the second one?
{base, numrange, ttype} << {base, numrange, ttype}: boolean
• Is the first bounding box always strictly greater than the second one?
{base, numrange, ttype} >> {base, numrange, ttype}: boolean
• Is the first bounding box never greater than the second one?
{base, numrange, ttype} &< {base, numrange, ttype}: boolean
• Is the first bounding box never less than the second one?
{base, numrange, ttype} &> {base, numrange, ttype}: boolean
The operators for the value dimension of the temporal point types are given next.
• Is the first bounding box strictly to the left of the second one?
{geo, tgeompoint} << {geo, tgeompoint}: boolean
• Is the first bounding box strictly to the right of the second one?
{geo, tgeompoint} >> {geo, tgeompoint}: boolean
• The first bounding box does not extend to the right of the second one?
{geo, tgeompoint} &< {geo, tgeompoint}: boolean
• The first bounding box does not extend to the left of the second one?
{geo, tgeompoint} &> {geo, tgeompoint}: boolean
• The first bounding box does not extend above of the second one?
{geo, tgeompoint} &<| {geo, tgeompoint}: boolean
• The first bounding box does not extend below of the second one?
{geo, tgeompoint} |&> {geo, tgeompoint}: boolean
• The first bounding box does not extend in front of the second one?
{geo, tgeompoint} &</ {geo, tgeompoint}: boolean
• The first bounding box does not extend back of the second one?
{geo, tgeompoint} /&> {geo, tgeompoint}: boolean
The operators for the time dimension of all the temporal types are as follows.
Another set of operators consider the topological relationships between the bounding boxes. There are five topological operators:
overlaps (&&), contains (@>), contained (<@), same (~=), and adjacent (-|-). The arguments of these operators can be a base
type, a box, or a temporal type and the operators verify the topological relationship taking into account the value and/or the time
dimension depending on the type of the arguments.
The topological operators for temporal alphanumeric types are given next.
The topological operators for temporal point types are given next.
There are two distance operators. The first one computes the distance between either a temporal point and a geometry or between
two temporal points at their nearest point of approach, which is a float. This is the same as the function nearestApproachDistance
discussed before but as an operator it can be used for doing nearest neightbor searches using a GiST index (see Section 5.20).
On the other hand, the temporal distance operator computes the distance at each instant of the intersection of the temporal extents
of their arguments and results in a temporal float. Computing temporal distance is useful in many mobility applications. For
example, a moving cluster (also known as convoy or flock) is defined as a set of objects that move close to each other for a long
time interval. This requires to compute temporal distance between two moving objects.
The temporal distance operator accepts a geometry/geography restricted to a point or a temporal point as arguments. Notice
that the temporal types only consider linear interpolation between values, while the distance is a root of a quadratic function.
Therefore, the temporal distance operator gives a linear approximation of the actual distance value for temporal sequence points.
In this case, the arguments are synchronized in the time dimension, and for each of the composing line segments of the arguments,
the spatial distance between the start point, the end point, and the nearest point of approach is computed, as shown in the examples
below.
• Distance
{point, tpoint} <-> {point, tpoint}: tfloat
The topological relationships such as ST_Intersects and ST_Relate can be generalized for temporal points. The argu-
ments of these generalized functions are either a temporal point or a base type (that is, a geometry or a geography), but these
functions do not allow a base type in both arguments. Furthermore, both arguments must be of the same base type, that is, these
functions do not allow to have a temporal geometry point (or a geometry) and a temporal geography point (or a geography) as
arguments.
There are two versions of the temporal topological relationships:
• The first version applies the traditional topological function to the union of all values taken by the temporal point (which is a
geometry or geography) and returns a boolean or a text. Examples are the intersects and relate functions.
• The second version is defined with the temporal semantics, that is, the traditional topological function is computed at each
instant and results in a tbool or a ttext. Examples are the tintersects and trelate functions.
All spatial relationships in the two versions are defined for temporal geometry points, while only four of them are defined for
temporal geography points, namely, covers, coveredby, intersects, and dwithin, and the corresponding temporal
versions.
The semantics conveyed by the first version of the relationships varies depending on the relationship and the type of the argu-
ments. For example, the following query
tests whether the temporal point ever intersected the geometry, since the query is conceptually equivalent to the following one.
MobilityDB Manual 69 / 104
where the second geometry is obtained by applying the trajectory function to the temporal point. On the other hand, the
query
tests whether the geometry always contains the temporal point. Finally, the following query
tests whether the temporal points may intersect, since the query above is conceptually equivalent to the following one
The first versions of the relationships are typically used in combination with a spatio-temporal index when computing the tem-
poral relationships. For example, the following query
which verifies whether a trip T (which is a temporal point) intersects a region R (which is a geometry), will benefit rom a
spatio-temporal index on the column T.Trip since the intersects function will automatically perform the bounding box
comparison T.Trip && R.Geom. This is further explained later in this document.
Three topological relationships available in PostGIS are not provided in the temporal version.
• tcontainsproperly since it would always be equal to tcontains: ST_Contains returns true if and only if no points
of B lie in the exterior of A, and at least one point of the interior of B lies in the interior of A. ST_ContainsProperly
returns true if B intersects the interior of A but not the boundary (or exterior).
• tcrosses since it would always returns false: ST_Crosses returns true if the supplied geometries have some, but not all,
interior points in common.
• toverlaps since it would always returns false: ST_Overlaps returns true if the geometries share space, are of the same
dimension, but are not completely contained by each other.
Similarly, only a few temporal topological relationships are meaningful when the two arguments are temporal points. Therefore,
the relationships supported for two temporal geometry points are tdisjoint, tequals, tintersects, tdwithin, and
trelate (with 2 and 3 arguments), while only tintersects and tdwithin are supported for two temporal geography
points.
The relate and the trelate functions have two forms with either two or three arguments. The two-argument forms consider
the spatial relationship between the interior, the boundary, and the exterior of the arguments and return a text or a ttext value
representing the maximum intersection matrix pattern. This pattern is defined using the Dimensionally Extended 9 Intersection
Model or DE-9IM (see the PostGIS documentation for more details). The three-argument forms determine whether the first two
arguments satisfy the intersection matrix pattern given as third argument (a text value) and return a Boolean or a temporal
Boolean.
MobilityDB Manual 70 / 104
• May contain
contains({geo, tgeompoint}, {geo, tgeompoint}): boolean
• May cover
covers({geo, tpoint}, {geo, tpoint}): boolean
• May be covered by
coveredby({geo, tpoint}, {geo, tpoint}): boolean
• May cross
crosses({geo, tgeompoint}, {geo, tgeompoint}): boolean
• May be disjoint
disjoint({geo, tgeompoint}, {geo, tgeompoint}): boolean
• May be equal
equals({geo, tgeompoint}, {geo, tgeompoint}): boolean
MobilityDB Manual 71 / 104
• May intersect
intersects({geo, tpoint}, {geo, tpoint}): boolean
• May overlap
overlaps({geo, tgeompoint}, {geo, tgeompoint}): boolean
• May touch
touches({geo, tgeompoint}, {geo, tgeompoint}): boolean
• May be within
within({geo, tgeompoint}, {geo, tgeompoint}): boolean
• May relate
relate({geo, tgeompoint}, {geo, tgeompoint}): text
MobilityDB Manual 72 / 104
• May relate
relate({geo, tgeompoint}, {geo, tgeompoint}, text): boolean
• Temporal contains
tcontains({geo, tgeompoint}, {geo, tgeompoint}): tbool
• Temporal covers
tcovers({geo, tpoint}, {geo, tpoint}): tbool
• Temporal covered by
tcoveredby({geo, tpoint}, {geo, tpoint}): tbool
• Temporal disjoint
tdisjoint({geo, tgeompoint}, {geo, tgeompoint}): tbool
• Temporal equals
tequals({geo, tgeompoint}, {geo, tgeompoint}): tbool
MobilityDB Manual 73 / 104
• Temporal intersects
tintersects({geo, tpoint}, {geo, tpoint}): tbool
• Temporal touches
ttouches({geo, tgeompoint}, {geo, tgeompoint}): tbool
• Temporal within
twithin({geo, tgeompoint}, {geo, tgeompoint}): tbool
• Temporal relate
trelate({geo, tgeompoint}, {geo, tgeompoint}): ttext
• Temporal relate
trelate({geo, tgeompoint}, {geo, tgeompoint}, text): tbool
The temporal aggregate functions generalize the traditional aggregate functions. Their semantics is that they compute the value
of the function at every instant t in the union of the temporal extents of the values to aggregate.
For all temporal types, the function tcount generalize the traditional function count. The temporal count can be used to
compute at each point in time the number of available or reporting objects (for example, number of cars in an area). For Boolean
types, the functions tand and tor generalize the traditional functions and and or. For numeric types, two types of temporal
aggregate functions are available. The functions tmin, tmax, tsum, and tavg generalize the traditional functions min, max,
sum, and avg. Furthermore, the functions wmin, wmax, wcount, wsum, and wavg are window (or cumulative) versions
of the traditional functions that, given a time interval w, compute the value of the function at an instant t by considering the
values during the interval [t-w, t]. All window aggregate functions are available for temporal integers, while for temporal floats
only window minimum and maximum are meaningful. For temporal text, the functions Finally, for temporal points the function
tcentroid, generalizes the corresponding function ST_Centroid provided by PostGIS. For example, given set of objects
that move together (that is, a convoy or a flock) the temporal centroid will produce a temporal point that represents at each instant
the geometric center (or the center of mass) of all the moving objects.
In addition to the above, function extent returns a bounding box that encloses a set of temporal values. Depending on the
base type, the result of this function can be a period, a tbox or an stbox. This function is an “aggregate” function in SQL
terminology since it operates on lists of data, in the same way the SUM() and AVG() functions do.
In the examples that follow, we suppose the tables Department and Trip contain the two tuples introduced in Section 3.1.
• Temporal count
tcount(ttype): {tinti, tints}
• Temporal and
tand(tbool): tbool
• Temporal or
tor(tbool): tbool
• Temporal minimum
tmin(ttype): {ttypei, ttypes}
• Temporal maximum
tmax(ttype): {ttypei, ttypes}
• Temporal sum
tsum(tnumber): {tnumi, tnums}
• Temporal average
tavg(tnumber): {tfloati, tfloats}
• Window minimum
wmin(tnumber, interval): {tnumi, tnums}
• Window maximum
wmax(tnumber, interval): {tnumi, tnums}
• Window count
wcount(tnumber, interval): {tinti, tints}
• Window sum
wsum(tint, interval): {tinti, tints}
• Window average
wavg(tint, interval): {tfloati, tfloats}
• Temporal centroid
tcentroid(tgeompoint): tgeompoint
SELECT mobdb_lib_version();
-- "MobilityDB 1.0"
SELECT mobdb_full_version();
-- "MobilityDB 1.0 PostgreSQL 11.5 PostGIS 2.5"
MobilityDB Manual 77 / 104
GiST and SP-GiST indexes can be created for table columns of temporal types. The GiST index implements an R-tree for
temporal alphanumeric types and a TB-tree for temporal point types. The SP-GiST index implements a Quad-tree for temporal
alphanumeric types and an Oct-tree for temporal point types. Examples of index creation are as follows:
The GiST and SP-GiST indexes store the bounding box for the temporal types. As explained in Chapter 3, these are
• the period period type for the tbool and ttext types,
• the tbox type for the tint and tfloat types,
• the stbox type for the tgeompoint and tgeogpoint types.
A GiST or SP-GiST index can accelerate queries involving the following operators (see Section 5.9 for more information):
• <<, &<, &>, >>, which only consider the value dimension in temporal alphanumeric types,
• <<, &<, &>, >>, <<|, &<|, |&>, |>>, &</, <</, />>, and /&>, which only consider the spatial dimension in temporal point
types,
• &<#, <<#, #>>, #&>, which only consider the time dimension for all temporal types,
• &&, @>, <@, and ~=, which consider as many dimensions as they are shared by the indexed column and the query argument.
These operators work on bounding boxes (that is, period, tbox, or stbox), not the entire values.
In addition, a GiST index can accelerate nearest neighbor queries involving the |=| operator.
For example, given the index defined above on the Department table and a query that involves a condition with the &&
(overlaps) operator, if the right argument is a temporal float then both the value and the time dimensions are considered for
filtering the tuples of the relation, while if the right argument is a float value, a float range, or a time type, then either the value
or the time dimension will be used for filtering the tuples of the relation. Furthermore, a bounding box can be constructed from
a value/range and/or a timestamp/period, which can be used for filtering the tuples of the relation. Examples of queries using the
index on the Department table defined above are given next.
Similarly, examples of queries using the index on the Trips table defined above are given next.
SELECT * FROM Trips WHERE Trip && geometry ’Polygon((0 0,0 1,1 1,1 0,0 0))’;
SELECT * FROM Trips WHERE Trip && timestamptz ’2001-01-01’;
SELECT * FROM Trips WHERE Trip && period ’[2001-01-01, 2001-01-05)’;
SELECT * FROM Trips WHERE Trip &&
stbox(geometry ’Polygon((0 0,0 1,1 1,1 0,0 0))’, period ’[2001-01-01, 2001-01-05]’);
SELECT * FROM Trips WHERE Trip &&
tgeompoint ’{[Point(0 0)@2001-01-01, Point(1 1)@2001-01-02, Point(1 1)@2001-01-05)}’;
MobilityDB Manual 78 / 104
Finally, B-tree indexes can be created for table columns of all temporal types. For this index type, the only useful operation is
equality. There is a B-tree sort ordering defined for values of temporal types, with corresponding <, <=, >, >= and operators, but
the ordering is rather arbitrary and not usually useful in the real world. B-tree support for temporal types is primarily meant to
allow sorting internally in queries, rather than creation of actual indexes.
In order to speed up several of the functions in Chapter 5, a bounding box comparison that make uses of the available indexes
can be added in the WHERE clause of queries. For example, this would be typically the case for the functions that project the
temporal types to the value/spatial and/or time dimensions. This will filter out the tuples with an index as shown in the following
query.
In the case of temporal points, all spatial relationships with the “ever” semantics (see Section 5.17), excepted disjointand
relate, will automatically include a bounding box comparison that will make use of any indexes that are available on the
temporal points. For this reason, the first version of the relationships is typically used for filtering the tuples with the help of an
index when computing the temporal relationships as shown in the following query.
The PostgreSQL planner relies on statistical information about the contents of tables in order to generate the most efficient
execution plan for queries. These statistics include a list of some of the most common values in each column and a histogram
showing the approximate data distribution in each column. For large tables, a random sample of the table contents is taken, rather
than examining every row. This enables large tables to be analyzed in a small amount of time. The statistical information is
gathered by the ANALYZE command and stored in the pg_statistic catalog table. Since different kinds of statistics may be
appropriate for different kinds of data, the table only stores very general statistics (such as number of null values) in dedicated
columns. Everything else is stored in five “slots”, which are couples of array columns that store the statistics for a column of an
arbitrary type.
The statistics collected for time types and temporal types are based on those collected by PostgreSQL for scalar types and range
types. For scalar types, like float, the following statistics are collected:
For range types, like tstzrange, three additional histograms are collected:
MobilityDB Manual 79 / 104
9. number of dimensions of the values, N-dimensional bounding box, number of rows in the table, number of rows in the
sample, number of non-null values,
10. N-dimensional histogram that divides the bounding box into a number of cells and keeps the proportion of values that
intersects with each cell.
The statistics collected for the new time types timestampset, period, and periodset replicate those collected by Post-
greSQL for the tstzrange. This is clear for the period type, which is equivalent to tszrange, excepted that periods
cannot be empty. For the timestampset and the periodset types, a value is converted into its bounding box which is a
period, then the period statistics are collected.
The statistics collected for temporal types depend on their duration and their base type. In addition to statistics (1)–(3) that are
collected for all temporal types, statistics are collected for the value dimension and the time dimension independently. More
precisely, the following statistics are collected for the time dimension:
• For temporal instant values, the statistics (4)–(6) are collected for the timestamps.
• For all other durations, the statistics (7)–(8) are collected for the (bounding box) periods.
• For temporal types with stepwise interpolation (that is, temporal types whose base type is tbool, ttext, or tint):
– For the instant duration, the statistics (4)–(6) are collected for the values.
– For all other durations, the statistics (7)–(8) are collected for the values.
Boolean operators in PostgreSQL can be associated with two selectivity functions, which compute how likely a value of a given
type will match a given criterion. These selectivity functions rely on the statistics collected. There are two types of selectivity
functions. The restriction selectivity functions try to estimate the percentage of the rows in a table that satisfy a WHERE-clause
condition of the form column OP constant. The join selectivity functions try to estimate the percentage of the rows in a
table that satisfy a WHERE-clause condition of the form table1.column1 OP table2.column2.
MobilityDB defines 23 classes of Boolean operators (such as =, <, &&, <<, etc.), each of which can have as left or right arguments
a built-in type (such as int, timestamptz, etc.) or a new type (such as period, tintseq, etc.). As a consequence, there is
a very high number of operators with different arguments to be considered for the selectivity functions. The approach taken was
to group these combinations into classes corresponding to the value and temporal features. The classes correspond to the type of
statistics collected as explained in the previous section.
Currently, only restriction selectivity functions are implemented for temporal types, while join selectivity functions give a default
selectivity value depending on the operator. It is planned to implement joint selectivity functions in the future.
MobilityDB Manual 80 / 104
Chapter 6
MobilityDB Tutorial
To illustrate the capabilities of MobilityDB, we give an example use case that loads, explores, and query mobility data. The data
used is based on the BerlinMOD benchmark for moving object databases and is available as a ZIP file.
6.1 Installation
For this tutorial we will use a Docker image containing MobilityDB and all its dependencies (including PostgreSQL and Post-
GIS). The container has a default database called mobilitydb with the MobilityDB extension installed where user = pw =
docker. This presupposes that you have installed Docker into your computer. In that case, you can run the following command.
• docker pull downloads the Docker image of mobilitydb. If the image has been downloaded before, this checks whether
a more recent image has been published in the docker repository, and downloads it. It is better to call this command every time,
to ensures that you have the latest most up-to-date version of this image.
• docker volume create mobilitydb_data creates a volume container on the host, that we will use to persist the
PostgreSQL database files outside of the MobilityDB container. You need to run this command only once, during the first use
of the image
• docker run --name=mobilitydb tells Docker our new container will be named mobilitydb.
• -d runs the container in the background (detached mode).
• -p 25432:5432 maps TCP port 5432 in the container to port 25432 on the Docker host (to prevent potential conflicts with
any local database instance you may have). This is required because the PostgreSQL database server in the container listens
for connections on port 5432 by default.
• -v mobilitydb_data:/var/lib/postgresql tells the container filesystem to mount the mobilitydb_data vol-
ume that we have just created to the path /var/lib/postgresql. This means that any database objects that the container saves or
creates (by default in /var/lib/postgresql) will instead be persisted in the mobilitydb_data directory, which is
stored in the host. This options ensures that your data will not be lost when the container is removed.
• codewit/mobilitydb tells Docker to pull the docker image with that name from Docker Hub.
MobilityDB Manual 81 / 104
Now we can launch any PostgreSQL administrative front-end to start using MobilityDB. Two traditional ones are the command-
line tool psql and the graphical tool pgAdmin. We can launch psql as follows.
• docker exec -t -i mobilitydb psql tells Docker to allocate a pseudo-TTY, to keep STDIN open, and to execute
in the container mobilitydb the command psql.
• -h localhost -p 5432 -d mobilitydb -U docker tells psql, respectively, the database server host, the server
port, the database name, and the user name.
Note that you will be prompted to provide the password, which is also docker.
In order to launch pgAdmin, there are two options to create a connection. The first option is to set the host to the localhost
(127.0.0.1), and the port to the mapped one on the host, as per the docker run command. In this example the port is 25432.
Now we can launch pgAdmin and establish a new connection to the docker container. This is done as shown in Figure 6.1.
The second option is to know the IP address used by docker container with the following command.
docker-machine ip
192.168.99.101
Notice that the address obtained in your computer may be different from the one above. Now we can launch pgAdmin and
establish a new connection to the docker container. This is done as shown in Figure 6.1. The second option is to set the host to
the localhost (127.0.0.1), and the port to the mapped one on the host, as per the docker run command. In this example the
port would be 25432.
Now you can use pgAdmin to query the mobilitydb database, as will be further explained in the following sections. Here are few
more docker commands that you will eventually need:
• docker stop shuts down the docker container. You need to issue this command, for example, if you need to re-start the
host.
• docker start launches back the docker container. You need to issue this command, for example, after re-starting the host.
• docker rm removes/deletes docker container. You need to issue this command, for example, if you need to docker pull
a more recent MobilityDB image. If the databases are stored in a docker volume as explained above, it will still be
available after downloading and running the new image.
The ZIP file with the data for this tutorial contains a set of CSV files as follows:
• datamcar.csv with fields Moid, Licence, Type, and Model contains the vehicle descriptions (without position history).
• trips.csv with fields Moid, Tripid, Pos_x, Pos_y, and Instant contains vehicles movements and pauses.
MobilityDB Manual 82 / 104
• queryinstants.csv with fields Id and Instant contains timestamps used for queries.
• queryperiods.csv with fields Id, Begin, and End contains periods used for the queries.
• querypoints.csv with fields Id, Pos_x, and Pos_y contains points used for queries.
• queryregions.csv with fields Id, SegNo, Xstart, Ystart, Xend, and Yend contains the polygons used for queries.
We decompress the file with the data into a directory. This can be done using the command.
unzip berlinmod_data.zip
By using CASCADE we load the required PostGIS extension prior to loading MobilityDB.
We create the tables to be loaded with the data in the CSV files as follows.
We created one table for each CSV file renaming attributes for better readability. In addition, we created a table Regions in
order to assemble all segments composing a region into a single geometry and a table Trips in order to assemble all points
composing a trip into a single temporal point.
We can load the CSV files into the corresponding tables as follows.
The following query is used to load table Regions from the data in table RegionsInput.
The following query is used to load table Trips from the data in table TripsInput.
MobilityDB Manual 85 / 104
• Function ST_MakePoint construct a point from the Lon and Lat values.
• Function ST_SetSRID sets the SRID of the point to 4326, that is, to the standard WGS 84 GPS coordinates.
• Function ST_Transform transforms the spherical GPS coordinates to plannar coordinates fitted for Germany.
• Function tgeompointinst gets the point and the time values to create a temporal point of instant duration.
• Function array_agg collects in an array all temporal points of a given car and a given trip (as specified by the GROUP BY
clause) and sort them by time (as specified by the ORDER BY clause)
• Function tgeompointseq gets the array of temporal points and construct a temporal point of sequence duration.
Finally, we create indexes on traditional, spatial, temporal or spatiotemporal attributes as well as views to select a subset of the
rows from the corresponding tables. This can be done as follows.
PostgreSQL provides partitioning mechanisms so that large tables can be split in smaller physical tables. This may result in
increased performance when querying and manipulating large tables. We will split the Trips table given in the previous section
using list partitioning, where each partitition will contain all the trips that start at a particular date. For doing this, we use the
procedure given next for automatically creating the partitions according to a date range.
FROM information_schema.tables
WHERE table_name = lower(TableName))
THEN
RAISE EXCEPTION ’Table % does not exist’, TableName;
END IF;
IF StartDate >= EndDate THEN
RAISE EXCEPTION ’The start date % must be before the end date %’, StartDate, EndDate;
END IF;
d = StartDate;
WHILE d <= EndDate
LOOP
PartitionName = TableName || ’_’ || to_char(d, ’YYYY_MM_DD’);
IF NOT EXISTS
(SELECT 1
FROM information_schema.tables
WHERE table_name = lower(PartitionName))
THEN
EXECUTE format(’CREATE TABLE %s PARTITION OF %s FOR VALUES IN (’’%s’’);’,
PartitionName, TableName, to_char(d, ’YYYY-MM-DD’));
RAISE NOTICE ’Partition % has been created’, PartitionName;
END IF;
d = d + ’1 day’::interval;
END LOOP;
RETURN;
END
$$ LANGUAGE plpgsql;
In order to partition table Trips by date we need to add an addition column TripDate to table TripsInput.
Notice that the UPDATE statement above takes into account the fact that a trip may finish at a day later than the starting day.
The following statements create table Trips partitioned by date and the associated partitions.
To see the partitions that have been created automatically we can use the following statement.
FROM pg_inherits I
WHERE i.inhparent = ’trips’::regclass;
"trips_2007_05_27"
"trips_2007_05_28"
"trips_2007_05_29"
"trips_2007_05_30"
We modify the query that loads table Trips from the data in table TripsInput as follows.
Then, we can define the indexes and the views on the table Trips as shown in the previous section.
An important advantange of the partitioning mechanism in PostgreSQL is that the constraints and the indexes defined on the
Trips table are propagated to the partitions as shown next.
EXPLAIN SELECT COUNT(*) from Trips where Trip && period ’[2007-05-28, 2007-05-29]’;
In order to visualize the data with traditional tools such as QGIS we add to table Trip a column Traj of type geometry
containing the trajectory of the trips.
The visualization of the trajectories in QGIS is given in Figure 6.2. In the figure red lines correspond to the trajectories of moving
cars, while yellow points correspond to the position of stationary cars. In order to know the total number of trips as well as the
number of moving and stationary trips we can issue the following queries.
We can also determine the spatiotemporal extent of the data using the following query.
We continue investigating the data set by computing the maximum number of concurrent trips over the whole period
This average duration is too long. To investigate more we use the following query
The query shows very many trips with zero length and a duration of more than one day. That would imply that there are stationary
trips, representing parking overnight and even over the weekend. The previous query can hence be refined as follows:
We discuss next four categories of queries: range queries, distance queries, temporal aggregate queries, and nearest-neighbor
queries1 .
1A web interface to explore the temporal types on a database containing the BerlinMOD benchmark data generated at scale 0.005 is available at the address
here.
MobilityDB Manual 90 / 104
The queries in this category restrict Trips with respect to a spatial, temporal, or spatio-temporal point or range. In the examples,
the spatial points and ranges are given, respectively, in tables Points and Regions, while temporal points and ranges are
given, respectively, in tables Instants and Periods.
This is a spatial range query. The query verifies that the trajectory of the car intersects the region. PostGIS performs an
implicit bounding box comparison trajectory(T.Trip) && R.Geom using the spatial index on table Regions
when executing the predicate ST_Intersects.
2. List the cars that were within a region from Regions during a period from Periods.
This is a spatio-temporal range query. The query performs a bounding box comparison with the && operator using the
spatio-temporal index on table Trips. After that, the query verifies that the location of the car during the period intersects
the region. Notice that the predicate _intersects is used instead of intersects to avoid an implicit index test with
the bounding box comparison atPeriod(Trip, P.Period) && R.Geom is performed using the spatio-temporal
index.
3. List the pairs of cars that were both located within a region from Regions during a period from Periods.
This is a spatio-temporal range join query. The query selects two trips of different cars and performs bounding box
comparisons of each trip with a region and a period using the spatio-temporal index of the Trips table. The query then
verifies that both cars were located within the region during the period.
4. List the first time at which a car visited a point in Points.
The query selects a trip and a point and verifies that the car passed by the point by testing that the trajectory of the
trip contains the point. Notice that PostGIS will perform the bounding box containment trajectory(T.Trip) ~
P.Geom using the spatial index on table Points before executing ST_Contains. Then, the query projects the trip to
the point with the atValue function, get the first timestamp of the projected trip with the startTimestamp function,
and applies the traditional MIN aggregate function for all trips of the car and the point.
• Instant temporal aggregate queries in which, from a conceptual perspective, the traditional aggregate function is applied at each
instant.
• Window temporal aggregate queries (also known as cumulative queries), which, given a time interval w, compute the value of
the aggregate at a time instant t from the values during the time period [t-w, t].
• Span temporal aggregate queries, which, first, split the time line into predefined intervals independently of the target data, and
then, for each of these intervals, aggregate the data that overlap the interval.
This an instant temporal aggregate query. For each period, the query projects the trips to the given period and applies the
temporal count to the projected trips. The condition in the WHERE clause is used for filtering the trips with the spatio-
temporal index on table Trips.
6. For each region in Regions, give the window temporal count of trips with a 10-minute interval.
This is a window temporal aggregate query. Suppose that we are computing pollution levels by region. Since the effect
of a car passing at a location lasts some time interval, this is a typical case for window aggregates. For each region, the
query computes the spatial projection of the trips to the given region and apply the window temporal count to the projected
trips. The condition in the WHERE clause is used for filtering the trips with the spatio-temporal index. The condition in the
HAVING clause is used for removing regions that do not intersect with any trip.
MobilityDB Manual 92 / 104
7. Count the number of trips that were active during each hour in May 29, 2007.
WITH TimeSplit(Period) AS (
SELECT period(H, H + interval ’1 hour’)
FROM generate_series(timestamptz ’2007-05-29 00:00:00’,
timestamptz ’2007-05-29 23:00:00’, interval ’1 hour’) AS H )
SELECT Period, COUNT(*)
FROM TimeSplit S, Trips T
WHERE S.Period && T.Trip AND atPeriod(Trip, Period) IS NOT NULL
GROUP BY S.Period
ORDER BY S.Period;
This is a span temporal aggregate query. The query defines the intervals to consider in the TimeSplit temporary table.
For each of these intervals, the main query applies the traditional count function for counting the trips that overlap the
interval.
The queries in this category deal with either the distance travelled by a single object or the distance between two objects. The
complexity of the latter queries depend, on the one hand, on whether the reference objects are static or moving, and on the other,
on whether the operation required is either the minimum distance ever or the temporal distance computed at each instant.
8. List the overall traveled distances of the cars during the periods from Periods.
The query performs a bounding box comparison with the && operator using the spatio-temporal index on the Trips table.
It then projects the trip to the period, computes the length of the projected trip, and sum the lengths of all the trips of the
same car during the period.
9. List the minimum distance ever between each car and each point from Points.
The query projects the trip to the spatial dimension with the trajectory function and computes the traditional distance
between the trajectory of the trip and the point. The traditional minimum function is then applied for computing the
minimum distance between all trips of the car and the point.
10. List the minimum temporal distance between each pair of cars.
The query selects two trips T1 and T2 from distinct cars that were both traveling during a common period of time, computes
the temporal distance between the trips, and then computes the temporal minimum distance between all trips of the two
cars. The query uses the spatio-temporal index to filter the pairs of trips that were both traveling during a common period
of time.
11. List the nearest approach time, distance, and shortest line between each pair of trips.
This query shows similar functionality as that provided by the PostGIS functions ST_ClosestPointOfApproach and
ST_DistanceCPA. The query selects two trips T1 and T2 from distinct cars that were both traveling during a common
period of time and computes the required results.
12. List when and where a pairs of cars have been at 10 m or less from each other.
The query performs for each pair of trips T1 and T2 of distinct cars a bounding box comparison with the && operator using
the spatio-temporal index on the Trips table, where the bounding box of T2 is expanded by 10 m. Then, the period
expression computes the periods during which the cars were within 10 m. from each other and the atPeriodSet
function projects the trips to those periods. Notice that the expression tdwithin(T1.Trip, T2.Trip, 10.0) is
conceptually equivalent to dwithin(T1.Trip, T2.Trip) #<= 10.0. However, in this case the spatio-temporal
index cannot be used for filtering values.
• Given two sets of points P and Q, aggregate nearest-neighbor (ANN) queries find the points from P that have minimum
aggregated distance to all points from Q.
The above types of queries are generalized to temporal points. However, the complexity of these queries depend on whether the
reference object and the candidate objects are static or moving. In the examples that follow we only consider the nontemporal
version of the nearest-neighbor queries, that is, the one in which the calculation is performed on the projection of temporal points
on the spatial dimension. The temporal version of the nearest-neighbor queries remains to be done.
13. For each trip from Trips, list the three points from Points that have been closest to that car.
MobilityDB Manual 94 / 104
WITH TripsTraj AS (
SELECT *, trajectory(Trip) AS Trajectory FROM Trips )
SELECT T.CarId, P1.PointId, P1.Distance
FROM TripsTraj T CROSS JOIN LATERAL (
SELECT P.PointId, T.Trajectory <-> P.Geom AS Distance
FROM Points P
ORDER BY Distance LIMIT 3 ) AS P1
ORDER BY T.TripId, T.CarId, P1.Distance;
This is a nearest-neighbor query with moving reference objects and static candidate objects. The query above uses Post-
greSQL’s lateral join, which intuitively iterates over each row in a result set and evaluates a subquery using that row as a
parameter. The query starts by computing the trajectory of the trips in the temporary table TripsTraj. Then, given a
trip T in the outer query, the subquery computes the traditional distance between the trajectory of T and each point P. The
ORDER BY and LIMIT clauses in the inner query select the three closest points. PostGIS will use the spatial index on the
Points table for selecting the three closest points.
14. For each trip from Trips, list the three cars that are closest to that car
This is a nearest-neighbor query where both the reference and the candidate objects are moving. Therefore, it is not
possible to proceed as in the previous query to first project the moving points to the spatial dimension and then compute
the traditional distance. Given a trip T1 in the outer query, the subquery computes the temporal distance between T1 and
a trip T2 of another car distinct from the car from T1 and then computes the minimum value in the temporal distance.
Finally, the ORDER BY and LIMIT clauses in the inner query select the three closest cars.
15. For each trip from Trips, list the points from Points that have that car among their three nearest neighbors.
WITH TripsTraj AS (
SELECT *, trajectory(Trip) AS Trajectory FROM Trips ),
PointTrips AS (
SELECT P.PointId, T2.CarId, T2.TripId, T2.Distance
FROM Points P CROSS JOIN LATERAL (
SELECT T1.CarId, T1.TripId, P.Geom <-> T1.Trajectory AS Distance
FROM TripsTraj T
ORDER BY Distance LIMIT 3 ) AS T2 )
SELECT T.CarId, T.TripId, P.PointId, PT.Distance
FROM Trips T CROSS JOIN Points P JOIN PointTrips PT
ON T.CarId = PT.CarId AND T.TripId = PT.TripId AND P.PointId = PT.PointId
ORDER BY T.CarId, T.TripId, P.PointId;
This is a reverse nearest-neighbor query with moving reference objects and static candidate objects. The query starts by
computing the corresponding nearest-neighbor query in the temporary table PointTrips as it is done in Query 13. Then,
in the main query it verifies for each trip T and point P that both belong to the PointTrips table.
16. For each trip from Trips, list the cars having the car of the trip among the three nearest neighbors.
WITH TripDistances AS (
SELECT T1.CarId AS CarId1, T1.TripId AS TripId1, T3.CarId AS CarId2,
T3.TripId AS TripId2, T3.Distance
FROM Trips T1 CROSS JOIN LATERAL (
MobilityDB Manual 95 / 104
This is a reverse nearest-neighbor query where both the reference and the candidate objects are moving. The query starts
by computing the corresponding nearest-neighbor query in the temporary table TripDistances as it is done in Query
14. Then, in the main query it verifies for each pair of trips T1 and T2 that both belong to the TripDistances table.
17. For each group of ten disjoint cars, list the point(s) from Points, having the minimum aggregated distance from the given
group of ten cars during the given period.
WITH Groups AS (
SELECT ((ROW_NUMBER() OVER (ORDER BY C.CarId))-1)/10 + 1 AS GroupId, C.CarId
FROM Cars C ),
SumDistances AS (
SELECT G.GroupId, P.PointId,
SUM(ST_Distance(trajectory(T.Trip), P.Geom)) AS SumDist
FROM Groups G, Points P, Trips T
WHERE T.CarId = G.CarId
GROUP BY G.GroupId, P.PointId )
SELECT S1.GroupId, S1.PointId, S1.SumDist
FROM SumDistances S
WHERE S1.SumDist <= ALL (
SELECT SumDist
FROM SumDistances S
WHERE S1.GroupId = S2.GroupId )
ORDER BY S1.GroupId, S1.PointId;
This is an aggregate nearest-neighbor query. The temporary table Groups splits the cars in groups where the GroupId
column takes the values from 1 to total number of groups. The temporary table SumDistances computes for each group
G and point P the sum of the distances between a trip of a car in the group and the point. The main query then selects for
each group in table SumDistances the points(s) that have the minimum aggregated distance.
MobilityDB Manual 96 / 104
Appendix A
BerlinMOD is a standard benchmark for moving object DBMSs. It provides a data generator, pregenerated benchmark data for
different scale factors, and set of queries of two types: 17 range-style queries (called BerlinMOD/R), and 9 nearest-neighbours
queries (called BerlinMOD/NN). The MobilityDB tutorial presented in Chapter 6 and its associated data were based on Berlin-
MOD. However, its purpose was to show the capabilities of MobilityDB. In this appendix, we show how to load pregenerated
BerlinMOD data on MobilityDB and how to express the 17 queries in BerlinMOD/R. Some of these queries were already pre-
sented in Chapter 6.
-------------------------------------------------------------------------------
-- Loads the BerlinMOD data in projected (2D) coordinates with SRID 5676
-- https://epsg.io/5676
-------------------------------------------------------------------------------
Instant timestamptz
);
EXECUTE format(’COPY Instants(InstantId, Instant) FROM ’’%squeryinstants.csv’’
DELIMITER ’’,’’ CSV HEADER’, fullpath);
LIMIT 10;
The script above creates a procedure to load pregenerated BerlinMOD data (in CSV format and WGS84 coordinates) at various
scale factors. The procedure has two parameters: the scale factor and the directory where the CSV files are located. It supposes by
default that the scale factor is 0.005 and that the CSV files are located in the directory /usr/local/BerlinMOD/<scale
factor>/. Notice that the procedure creates GiST indexes for the tables. Alternatively, SP-GiST indexes could be used. The
procedure can be called, for example, as follows.
SELECT berlinmod_load(’0.05’);
As we discussed in Chapter 6, partioning allows one to split a large table into smaller physical pieces. We show next how to
modify the scripts given in the previous section to take advantage of partioning. We will partition the Trips table by date
using list partitioning, where each partitition will contain all the trips that start at a particular date. We will use the procedure
create_partitions_by_date shown in Chapter 6 for automatically creating the partitions according to the date range of
the corresponding scale factor.
[...]
DROP TABLE IF EXISTS TripsInput CASCADE;
CREATE TABLE TripsInput (
CarId integer,
TripId integer,
TripDate date,
TStart timestamp without time zone,
TEnd timestamp without time zone,
XStart double precision,
YStart double precision,
XEnd double precision,
YEnd double precision,
Geom geometry(LineString)
);
EXECUTE format(’COPY TripsInput(CarId, TripId, TStart, TEnd, XStart, YStart, XEnd, YEnd)
FROM ’’%strips.csv’’ DELIMITER ’’,’’ CSV HEADER’, fullpath);
UPDATE TripsInput
SET Geom = ST_Transform(ST_SetSRID(ST_MakeLine(ARRAY[ST_MakePoint(XStart, YStart),
ST_MakePoint(XEnd, YEnd)]), 4326), 5676);
UPDATE TripsInput T1
SET TripDate = T2.TripDate
FROM (SELECT DISTINCT TripId, date_trunc(’day’, MIN(TStart) OVER
(PARTITION BY TripId)) AS TripDate FROM TripsInput) T2
WHERE T1.TripId = T2.TripId;
[...]
MobilityDB Manual 101 / 104
With respect to the script given in the previous section, we need to add an additional column TripDate to the tables TripsInput,
TripsInputInstants (not shown), and Trips that will be used for partitioning.
The script for querying BerlinMOD data loaded in MobilityDB with the BerlinMOD/R queries is available here.
1. What are the models of the vehicles with licence plate numbers from Licences?
3. Where have the vehicles with licences from Licences1 been at each of the instants from Instants1?
5. What is the minimum distance between places, where a vehicle with a licence from Licences1 and a vehicle with a
licence from Licences2 have been?
6. What are the pairs of trucks that have ever been as close as 10m or less to each other?
7. What are the licence plate numbers of the passenger cars that have reached the points from Points first of all passenger
cars during the complete observation period?
WITH Timestamps AS (
SELECT DISTINCT C.Licence, P.PointId, P.Geom,
MIN(startTimestamp(atValue(T.Trip,P.Geom))) AS Instant
FROM Trips T, Cars C, Points1 P
WHERE T.CarId = C.CarId AND C.Type = ’passenger’
AND T.Trip && P.Geom AND ST_Intersects(trajectory(T.Trip), P.Geom)
GROUP BY C.Licence, P.PointId, P.Geom
)
SELECT T1.Licence, T1.PointId, T1.Geom, T1.Instant
FROM Timestamps T1
WHERE T1.Instant <= ALL (
SELECT T2.Instant
FROM Timestamps T2
WHERE T1.PointId = T2.PointId )
ORDER BY T1.PointId, T1.Licence;
8. What are the overall travelled distances of the vehicles with licence plate numbers from Licences1 during the periods
from Periods1?
9. What is the longest distance that was travelled by a vehicle during each of the periods from Periods?
WITH Distances AS (
SELECT P.PeriodId, P.Period, T.CarId, SUM(length(atPeriod(T.Trip, P.Period))) AS Dist
FROM Trips T, Periods P
WHERE T.Trip && P.Period
GROUP BY P.PeriodId, P.Period, T.CarId
)
SELECT PeriodId, Period, MAX(Dist) AS MaxDist
FROM Distances
GROUP BY PeriodId, Period
ORDER BY PeriodId;
10. When and where did the vehicles with licence plate numbers from Licences1 meet other vehicles (distance < 3m) and
what are the latter licences?
WITH Values AS (
SELECT DISTINCT L1.Licence AS QueryLicence, C2.Licence AS OtherLicence,
atPeriodSet(T1.Trip, getTime(atValue(tdwithin(T1.Trip, T2.Trip, 3.0), TRUE))) AS Pos
FROM Trips T1, Licences1 L1, Trips T2, Licences2 C2
WHERE T1.CarId = L1.CarId AND T2.CarId = C2.CarId AND T1.CarId < T2.CarId
AND expandSpatial(T1.Trip, 3) && expandSpatial(T2.Trip, 3)
AND dwithin(T1.Trip, T2.Trip, 3.0)
)
SELECT QueryLicence, OtherLicence, array_agg(Pos ORDER BY startTimestamp(Pos)) AS Pos
FROM Values
GROUP BY QueryLicence, OtherLicence
ORDER BY QueryLicence, OtherLicence;
11. Which vehicles passed a point from Points1 at one of the instants from Instants1?
12. Which vehicles met at a point from Points1 at an instant from Instants1?
13. Which vehicles travelled within one of the regions from Regions1 during the periods from Periods1?
14. Which vehicles travelled within one of the regions from Regions1 at one of the instants from Instants1?
15. Which vehicles passed a point from Points1 during a period from Periods1?
16. List the pairs of licences for vehicles, the first from Licences1, the second from Licences2, where the corresponding
vehicles are both present within a region from Regions1 during a period from QueryPeriod1, but do not meet each
other there and then.
17. Which point(s) from Points have been visited by a maximum number of different vehicles?
WITH PointCount AS (
SELECT P.PointId, COUNT(DISTINCT T.CarId) AS Hits
FROM Trips T, Points P
WHERE ST_Intersects(trajectory(T.Trip), P.Geom)
GROUP BY P.PointId
)
SELECT PointId, Hits
FROM PointCount AS P
WHERE P.Hits = ( SELECT MAX(Hits) FROM PointCount );