/*****************************************************************************
 *
 * This MobilityDB code is provided under The PostgreSQL License.
 * Copyright (c) 2016-2025, Université libre de Bruxelles and MobilityDB
 * contributors
 *
 * MobilityDB includes portions of PostGIS version 3 source code released
 * under the GNU General Public License (GPLv2 or later).
 * Copyright (c) 2001-2025, PostGIS contributors
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without a written
 * agreement is hereby granted, provided that the above copyright notice and
 * this paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL UNIVERSITE LIBRE DE BRUXELLES BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
 * EVEN IF UNIVERSITE LIBRE DE BRUXELLES HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * UNIVERSITE LIBRE DE BRUXELLES SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON
 * AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 *****************************************************************************/

-- Generated by MobilityDB version 1.3.0
-- Generated on: 2025-11-21 21:01:46
-- 
-- This file is automatically generated. DO NOT EDIT!
-- Submit changes to the original source files instead.
--
-- For bug reports and feature requests please visit:
-- https://github.com/MobilityDB/MobilityDB/issues

-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION mobilitydb_datagen" to load this file. \quit

-- Begin contents of temporal/random_temporal.sql

/*****************************************************************************
 *
 * This MobilityDB code is provided under The PostgreSQL License.
 * Copyright (c) 2016-2025, Université libre de Bruxelles and MobilityDB
 * contributors
 *
 * MobilityDB includes portions of PostGIS version 3 source code released
 * under the GNU General Public License (GPLv2 or later).
 * Copyright (c) 2001-2025, PostGIS contributors
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without a written
 * agreement is hereby granted, provided that the above copyright notice and
 * this paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL UNIVERSITE LIBRE DE BRUXELLES BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
 * EVEN IF UNIVERSITE LIBRE DE BRUXELLES HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * UNIVERSITE LIBRE DE BRUXELLES SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON
 * AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 *****************************************************************************/

/*
 * random_temporal.sql
 * Basic synthetic data generator functions for some PostgreSQL data types
 * and for set, span, spanset, and temporal data types.
 *
 * These functions use lower and upper bounds for the generated values, e.g.,
 * lowvalue and highvalue for values, lowtime and hightime for timestamps.
 * When generating series of values, the maxdelta argument states the maximum
 * difference between two consecutive values, while maxminutes states the
 * the maximum number of minutes between two consecutive timestamps as well as
 * the maximum number of minutes for time gaps between two consecutive
 * components of temporal instant/sequence sets.
 */

-------------------------------------------------------------------------------
-- Base types
-------------------------------------------------------------------------------

/**
 * @brief Generate a random boolean
 */
DROP FUNCTION IF EXISTS random_bool;
CREATE FUNCTION random_bool()
  RETURNS boolean AS $$
BEGIN
  IF random() > 0.5 THEN RETURN TRUE; ELSE RETURN FALSE; END IF;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_bool() AS i
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random integer in a range
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 */
DROP FUNCTION IF EXISTS random_int;
CREATE FUNCTION random_int(lowvalue int, highvalue int)
  RETURNS int AS $$
BEGIN
  IF lowvalue > highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  RETURN floor(random() * (highvalue - lowvalue + 1) + lowvalue);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT random_int(1,7), COUNT(*)
FROM generate_series(1, 1e3)
GROUP BY 1
ORDER BY 1;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random bigint in a range
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 */
DROP FUNCTION IF EXISTS random_bigint;
CREATE FUNCTION random_bigint(lowvalue bigint, highvalue bigint)
  RETURNS bigint AS $$
BEGIN
  IF lowvalue > highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  RETURN floor(random() * (highvalue - lowvalue + 1) + lowvalue);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT random_bigint(1,7), COUNT(*)
FROM generate_series(1, 1e3)
GROUP BY 1
ORDER BY 1;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random float in a range
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 */
DROP FUNCTION IF EXISTS random_float;
CREATE FUNCTION random_float(lowvalue float, highvalue float)
  RETURNS float AS $$
BEGIN
  IF lowvalue > highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  RETURN random() * (highvalue - lowvalue) + lowvalue;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_float(-100, 100) AS f
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random ASCII character
 */
DROP FUNCTION IF EXISTS random_ascii;
CREATE FUNCTION random_ascii()
  RETURNS char AS $$
BEGIN
  -- ascii('A') = 65, ascii('Z') = 90,
  RETURN chr(random_int(65, 90));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_ascii() AS m
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random text value
 * @param[in] maxlength Maximum length of the text value
 */
DROP FUNCTION IF EXISTS random_text;
CREATE FUNCTION random_text(maxlength int)
  RETURNS text AS $$
DECLARE
  result text;
BEGIN
  SELECT string_agg(random_ascii(),'') INTO result
  FROM generate_series(1, random_int(1, maxlength)) AS x;
  result = replace(result, '"', '\"');
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_text(20) AS text
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random date in a period
 * @param[in] lowdate, higdate Inclusive bounds of the period
 */
DROP FUNCTION IF EXISTS random_date;
CREATE FUNCTION random_date(lowdate date, highdate date)
  RETURNS date AS $$
BEGIN
  IF lowdate > highdate THEN
    RAISE EXCEPTION 'lowdate must be less than or equal to highdate: %, %',
      lowdate, highdate;
  END IF;
  RETURN lowdate + floor(random() * (highdate - lowdate)) * interval '1 day';
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_date('2001-01-01', '2002-01-01') AS d
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random timestamptz in a period
 * @param[in] lowtime, hightime Inclusive bounds of the period
 */
DROP FUNCTION IF EXISTS random_timestamptz;
CREATE FUNCTION random_timestamptz(lowtime timestamptz, hightime timestamptz)
  RETURNS timestamptz AS $$
BEGIN
  IF lowtime > hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN date_trunc('minute',
    (lowtime + random() * (hightime - lowtime)))::timestamptz(0);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_timestamptz('2001-01-01', '2002-01-01') AS t
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random interval of minutes
 * @param[in] lowvalue, highvalue Inclusive bounds of the number of minutes
 */
DROP FUNCTION IF EXISTS random_minutes;
CREATE FUNCTION random_minutes(lowvalue int, highvalue int)
  RETURNS interval AS $$
BEGIN
  RETURN random_int(lowvalue, highvalue) * interval '1 minute';
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_minutes(1, 20) AS m
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------
-- Arrays of base types
-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random integers in a range
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between two consecutive values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_int_array;
CREATE FUNCTION random_int_array(lowvalue int, highvalue int, maxdelta int,
  mincard int, maxcard int)
  RETURNS int[] AS $$
DECLARE
  result int[];
  card int;
  delta int;
  v int;
BEGIN
  IF lowvalue > highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  card = random_int(mincard, maxcard);
  v = random_int(lowvalue, highvalue);
  FOR i IN 1..card
  LOOP
    result[i] = v;
    IF i = card THEN EXIT; END IF;
    delta = random_int(-1 * maxdelta, maxdelta);
    /* If neither of these conditions is satisfied the same value is kept */
    IF (v + delta >= lowvalue AND v + delta <= highvalue) THEN
      v = v + delta;
    ELSIF (v - delta >= lowvalue AND v - delta <= highvalue) THEN
      v = v - delta;
    END IF;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_int_array(-100, 100, 10, 5, 10) AS iarr
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random floats in a range
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between two consecutive values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_float_array;
CREATE FUNCTION random_float_array(lowvalue float, highvalue float,
  maxdelta float, mincard int, maxcard int)
  RETURNS float[] AS $$
DECLARE
  result float[];
  card int;
  delta float;
  v float;
BEGIN
  IF lowvalue > highvalue - maxdelta THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - maxdelta: %, %, %',
      lowvalue, highvalue, maxdelta;
  END IF;
  card = random_int(mincard, maxcard);
  v = random_float(lowvalue, highvalue - maxdelta);
  FOR i IN 1..card
  LOOP
    result[i] = v;
    IF i = card THEN EXIT; END IF;
    delta = random_float(-1 * maxdelta, maxdelta);
    /* If neither of these conditions is satisfied the same value is kept */
    IF (v + delta >= lowvalue AND v + delta <= highvalue) THEN
      v = v + delta;
    ELSIF (v - delta >= lowvalue AND v - delta <= highvalue) THEN
      v = v - delta;
    END IF;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_float_array(-100, 100, 10, 5, 10) AS farr
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random text values
 * @param[in] maxlength Maximum length of the text value
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_text_array;
CREATE FUNCTION random_text_array(maxlength int, mincard int, maxcard int)
  RETURNS text[] AS $$
DECLARE
  textarr text[];
BEGIN
  SELECT array_agg(random_text(maxlength)) INTO textarr
  FROM generate_series(mincard, mincard + random_int(mincard, maxcard)) AS t;
  RETURN textarr;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_text_array(20, 5, 10) AS text
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an ordered array of random date in a datespan
 * @param[in] lowtime, hightime Inclusive bounds of the datespan
 * @param[in] maxdays Maximum number of minutes between two consecutive
 *    timestamps
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_date_array;
CREATE FUNCTION random_date_array(lowtime date, hightime date, maxdays int,
  mincard int, maxcard int, fixstart bool DEFAULT false)
  RETURNS date[] AS $$
DECLARE
  result date[];
  card int;
  d date;
BEGIN
  IF lowtime >= hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  IF lowtime > hightime - maxdays * (maxcard - mincard) THEN
    RAISE EXCEPTION 'The duration between lowtime and hightime is not enough to generate the temporal value';
  END IF;
  card = random_int(mincard, maxcard);
  if fixstart THEN
    d = lowtime;
  ELSE
    d = random_date(lowtime, hightime - maxdays * card);
  END IF;
  FOR i IN 1..card
  LOOP
    result[i] = d;
    d = d + random_int(1, maxdays);
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_date_array('2001-01-01', '2002-01-01', 10, 5, 10) AS tarr
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an ordered array of random timestamptz in a tstzspan
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between two consecutive
 * timestamps
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] fixstart True when this function is called for generating a
 * sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_timestamptz_array;
CREATE FUNCTION random_timestamptz_array(lowtime timestamptz,
  hightime timestamptz, maxminutes int, mincard int, maxcard int,
  fixstart bool DEFAULT false)
  RETURNS timestamptz[] AS $$
DECLARE
  result timestamptz[];
  card int;
  t timestamptz;
BEGIN
  IF lowtime >= hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  IF lowtime > hightime - interval '1 minute' * maxminutes * (maxcard - mincard) THEN
    RAISE EXCEPTION 'The duration between lowtime and hightime is not enough to generate the temporal value';
  END IF;
  card = random_int(mincard, maxcard);
  if fixstart THEN
    t = lowtime;
  ELSE
    t = random_timestamptz(lowtime, hightime - interval '1 minute' *
      maxminutes * card);
  END IF;
  FOR i IN 1..card
  LOOP
    result[i] = t;
    t = t + random_minutes(1, maxminutes);
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_timestamptz_array('2001-01-01', '2002-01-01', 10, 5, 10) AS tarr
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------
-- Set types
-------------------------------------------------------------------------------

/**
 * @brief Generate an ordered set of random integers in a range
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between two consecutive values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the set
 */
DROP FUNCTION IF EXISTS random_intset;
CREATE FUNCTION random_intset(lowvalue int, highvalue int, maxdelta int,
  mincard int, maxcard int)
  RETURNS intset AS $$
DECLARE
  iarr int[];
  v int;
  card int;
  i int;
BEGIN
  IF lowvalue >= highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  IF lowvalue > highvalue - maxdelta * (maxcard - mincard) THEN
    RAISE EXCEPTION 'The difference between lowvalue and highvalue is not enough to generate the intset';
  END IF;
  card = random_int(mincard, maxcard);
  v = random_int(lowvalue, highvalue - maxdelta * card);
  FOR i IN 1..card
  LOOP
    iarr[i] = v;
    v = v + random_int(1, maxdelta);
  END LOOP;
  RETURN set(iarr);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_intset(1, 100, 5, 5, 10) AS is
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an ordered set of random bigint in a range
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between two consecutive values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the set
 */
DROP FUNCTION IF EXISTS random_bigintset;
CREATE FUNCTION random_bigintset(lowvalue bigint, highvalue bigint,
  maxdelta int, mincard int, maxcard int)
  RETURNS bigintset AS $$
DECLARE
  iarr bigint[];
  v bigint;
  card int;
  i int;
BEGIN
  IF lowvalue >= highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  IF lowvalue > highvalue - maxdelta * (maxcard - mincard) THEN
    RAISE EXCEPTION 'The difference between lowvalue and highvalue is not enough to generate the bigintset';
  END IF;
  card = random_int(mincard, maxcard);
  v = random_bigint(lowvalue, highvalue - maxdelta * card);
  FOR i IN 1..card
  LOOP
    iarr[i] = v;
    v = v + random_int(1, maxdelta);
  END LOOP;
  RETURN set(iarr);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_bigintset(1, 100, 5, 5, 10) AS is
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an ordered set of random floats in a range
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between two consecutive values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the set
 */
DROP FUNCTION IF EXISTS random_floatset;
CREATE FUNCTION random_floatset(lowvalue float, highvalue float,
  maxdelta int, mincard int, maxcard int)
  RETURNS floatset AS $$
DECLARE
  farr float[];
  v float;
  card int;
  i int;
BEGIN
  IF lowvalue >= highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  IF lowvalue > highvalue - maxdelta * (maxcard - mincard) THEN
    RAISE EXCEPTION 'The difference between lowvalue and highvalue is not enough to generate the floatset';
  END IF;
  card = random_int(mincard, maxcard);
  v = random_float(lowvalue, highvalue - maxdelta * card);
  FOR i IN 1..card
  LOOP
    farr[i] = v;
    v = v + random_float(1, maxdelta);
  END LOOP;
  RETURN set(farr);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_floatset(1, 100, 5, 5, 10) AS is
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a set of random text values
 * @param[in] maxlength Maximum length of the text value
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_textset;
CREATE FUNCTION random_textset(maxlength int, mincard int, maxcard int)
  RETURNS textset AS $$
DECLARE
  textarr text[];
BEGIN
  textarr := '{}'::text[];
  SELECT array_agg(DISTINCT random_text(maxlength)) INTO textarr
  FROM generate_series(mincard, mincard + random_int(mincard, maxcard)) AS t;
  RETURN set(textarr);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_textset(20, 5, 10) AS text
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random dateset within a datespan
 * @param[in] lowtime, hightime Inclusive bounds of the maximal tstzspan
 * @param[in] maxdays Maximum number of days between two consecutive days
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_dateset;
CREATE FUNCTION random_dateset(lowtime date, hightime date, maxdays int,
  mincard int, maxcard int)
  RETURNS dateset AS $$
BEGIN
  RETURN set(random_date_array(lowtime, hightime, maxdays, mincard, maxcard));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_dateset('2001-01-01', '2002-01-01', 10, 5, 10) AS ps
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tstzset within a tstzspan
 * @param[in] lowtime, hightime Inclusive bounds of the maximal tstzspan
 * @param[in] maxminutes Maximum number of minutes between two consecutive timestamps
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_tstzset;
CREATE FUNCTION random_tstzset(lowtime timestamptz, hightime timestamptz,
  maxminutes int, mincard int, maxcard int)
  RETURNS tstzset AS $$
BEGIN
  RETURN set(random_timestamptz_array(lowtime, hightime, maxminutes,
    mincard, maxcard));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tstzset('2001-01-01', '2002-01-01', 10, 5, 10) AS ps
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------
-- Span types
-------------------------------------------------------------------------------

/**
 * @brief Generate a random integer span
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between the lower and upper bounds
 */
DROP FUNCTION IF EXISTS random_intspan;
CREATE FUNCTION random_intspan(lowvalue int, highvalue int, maxdelta int)
  RETURNS intspan AS $$
DECLARE
  v int;
BEGIN
  IF lowvalue > highvalue - maxdelta THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - maxdelta: %, %, %',
      lowvalue, highvalue, maxdelta;
  END IF;
  v = random_int(lowvalue, highvalue - maxdelta);
  RETURN span(v, v + random_int(1, maxdelta));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_intspan(-100, 100, 10) AS ir
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random bigint span
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between the lower and upper bounds
 */
DROP FUNCTION IF EXISTS random_bigintspan;
CREATE FUNCTION random_bigintspan(lowvalue bigint, highvalue bigint, maxdelta int)
  RETURNS bigintspan AS $$
DECLARE
  v int;
BEGIN
  IF lowvalue > highvalue - maxdelta THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - maxdelta: %, %, %',
      lowvalue, highvalue, maxdelta;
  END IF;
  v = random_bigint(lowvalue, highvalue - maxdelta);
  RETURN span(v, v + random_bigint(1, maxdelta));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_bigintspan(-100, 100, 10) AS s
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random float span
 * @param[in] lowvalue, highvalue Inclusive bounds of the span
 * @param[in] maxdelta Maximum difference between two consecutive values
 */
DROP FUNCTION IF EXISTS random_floatspan;
CREATE FUNCTION random_floatspan(lowvalue float, highvalue float, maxdelta int)
  RETURNS floatspan AS $$
DECLARE
  v float;
BEGIN
  IF lowvalue > highvalue - maxdelta THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - maxdelta: %, %, %',
      lowvalue, highvalue, maxdelta;
  END IF;
  v = random_float(lowvalue, highvalue - maxdelta);
  RETURN span(v, v + random_float(1, maxdelta));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_floatspan(-100, 100, 10) AS fr
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random datespan within a datespan
 * @param[in] lowdate, highdate Inclusive bounds of the maximal datespan
 * @param[in] maxdays Maximum number of days between the dates
 * @param[in] fixstart True when this function is called for generating
 * a datespan set and in this case the start date is already fixed
 * @note The date spans are generated canonicalized
 */
DROP FUNCTION IF EXISTS random_datespan;
CREATE FUNCTION random_datespan(lowdate date, highdate date, maxdays int,
  fixstart bool DEFAULT false)
  RETURNS datespan AS $$
DECLARE
  d date;
  d1 date;
BEGIN
  IF lowdate > highdate - maxdays THEN
    RAISE EXCEPTION 'lowdate must be less than or equal to highdate - maxdays days: %, %, %',
      lowdate, highdate, maxdays;
  END IF;
  if fixstart THEN
    d = lowdate;
  ELSE
    d = random_date(lowdate, highdate - maxdays);
  END IF;
  /* Generate instantaneous periods with 0.1 probability */
  IF random() < 0.1 THEN
    RETURN span(d, d + 1, true, false);
  ELSE
    d1 = d + random_int(1, maxdays);
    RETURN span(d, d1, true, false);
  END IF;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_datespan('2001-01-01', '2002-01-01', 10) AS p
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tstzspan within a tstzspan
 * @param[in] lowtime, hightime Inclusive bounds of the maximal tstzspan
 * @param[in] maxminutes Maximum number of minutes between the timestamps
 * @param[in] fixstart True when this function is called for generating
 *   a tstzspan set and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tstzspan;
CREATE FUNCTION random_tstzspan(lowtime timestamptz, hightime timestamptz,
  maxminutes int, fixstart bool DEFAULT false)
  RETURNS tstzspan AS $$
DECLARE
  t timestamptz;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  IF lowtime > hightime - interval '1 minute' * maxminutes THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime - maxminutes minutes: %, %, %',
      lowtime, hightime, maxminutes;
  END IF;
  if fixstart THEN
    t = lowtime;
  ELSE
    t = random_timestamptz(lowtime, hightime - interval '1 minute' * maxminutes);
  END IF;
  /* Generate instantaneous periods with 0.1 probability */
  IF random() < 0.1 THEN
    RETURN span(t, t, true, true);
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
    RETURN span(t, t + random_minutes(1, maxminutes), lower_inc, upper_inc);
  END IF;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tstzspan('2001-01-01', '2002-01-01', 10) AS p
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------
-- Arrays of span types
-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random intspans within a span
 * @param[in] lowvalue, highvalue Inclusive bounds of the maximal span
 * @param[in] maxdelta Maximum value difference between consecutive values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_intspan_array;
CREATE FUNCTION random_intspan_array(lowvalue int, highvalue int,
  maxdelta int, mincard int, maxcard int)
  RETURNS intspan[] AS $$
DECLARE
  result intspan[];
  card int;
  v1 int;
  v2 int;
BEGIN
  IF lowvalue > highvalue - maxdelta * 2 * (maxcard - mincard) THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - '
      'maxdelta * 2 * (maxcard - mincard): %, %, %, %, %',
      lowvalue, highvalue, maxdelta, mincard, maxcard;
  END IF;
  card = random_int(mincard, maxcard);
  v1 = lowvalue;
  v2 = highvalue - maxdelta * (card - 1) * 2;
  FOR i IN 1..card
  LOOP
    result[i] = random_intspan(v1, v2, maxdelta);
    v1 = upper(result[i]) + random_int(1, maxdelta);
    v2 = v2 + maxdelta * 2;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_intspan_array(1, 1000, 10, 5, 10) AS iarr
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random bigintspans within a span
 * @param[in] lowvalue, highvalue Inclusive bounds of the maximal span
 * @param[in] maxdelta Maximum value difference between consecutive values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_bigintspan_array;
CREATE FUNCTION random_bigintspan_array(lowvalue bigint, highvalue bigint,
  maxdelta int, mincard int, maxcard int)
  RETURNS bigintspan[] AS $$
DECLARE
  result bigintspan[];
  card int;
  v1 bigint;
  v2 bigint;
BEGIN
  IF lowvalue > highvalue - maxdelta * 2 * (maxcard - mincard) THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - '
      'maxdelta * 2 * (maxcard - mincard): %, %, %, %, %',
      lowvalue, highvalue, maxdelta, mincard, maxcard;
  END IF;
  card = random_bigint(mincard, maxcard);
  v1 = lowvalue;
  v2 = highvalue - maxdelta * (card - 1) * 2;
  FOR i IN 1..card
  LOOP
    result[i] = random_bigintspan(v1, v2, maxdelta);
    v1 = upper(result[i]) + random_bigint(1, maxdelta);
    v2 = v2 + maxdelta * 2;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_bigintspan_array(1, 1000, 10, 5, 10) AS biarr
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random floatspans within a span
 * @param[in] lowvalue, highvalue Inclusive bounds of the maximal span
 * @param[in] maxdelta Maximum value difference between consecutive values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_floatspan_array;
CREATE FUNCTION random_floatspan_array(lowvalue float, highvalue float,
  maxdelta int, mincard float, maxcard float)
  RETURNS floatspan[] AS $$
DECLARE
  result floatspan[];
  card float;
  v1 float;
  v2 float;
BEGIN
  IF lowvalue > highvalue - maxdelta * 2 * (maxcard - mincard) THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - '
      'maxdelta * 2 * (maxcard - mincard): %, %, %, %, %',
      lowvalue, highvalue, maxdelta, mincard, maxcard;
  END IF;
  card = random_float(mincard, maxcard);
  v1 = lowvalue;
  v2 = highvalue - maxdelta * (card - 1) * 2;
  FOR i IN 1..card
  LOOP
    result[i] = random_floatspan(v1, v2, maxdelta);
    v1 = upper(result[i]) + random_float(1, maxdelta);
    v2 = v2 + maxdelta * 2;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_floatspan_array(1, 1000, 10, 5, 10) AS iarr
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random periods within a datespan
 * @param[in] lowtime, hightime Inclusive bounds of the maximal datespan
 * @param[in] maxdays Maximum number of dates between the timestamps
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_datespan_array;
CREATE FUNCTION random_datespan_array(lowtime date, hightime date,
  maxdays int, mincard int, maxcard int)
  RETURNS datespan[] AS $$
DECLARE
  result datespan[];
  card int;
  d1 date;
  d2 date;
BEGIN
  IF lowtime > hightime - maxdays * 2 * (maxcard - mincard) THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime - '
      'maxdays * 2 * (maxcard - mincard) days: %, %, %, %, %',
      lowtime, hightime, maxdays, mincard, maxcard;
  END IF;
  card = random_int(mincard, maxcard);
  d1 = lowtime;
  d2 = hightime -  maxdays * (card - 1) * 2;
  FOR i IN 1..card
  LOOP
    result[i] = random_datespan(d1, d2, maxdays, i > 1);
    d1 = upper(result[i]) + random_int(1, maxdays);
    d2 = d2 + maxdays * 2;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_datespan_array('2001-01-01', '2002-01-01', 10, 5, 10) AS parr
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random periods within a tstzspan
 * @param[in] lowtime, hightime Inclusive bounds of the maximal tstzspan
 * @param[in] maxminutes Maximum number of minutes between the timestamps
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_tstzspan_array;
CREATE FUNCTION random_tstzspan_array(lowtime timestamptz, hightime timestamptz,
  maxminutes int, mincard int, maxcard int)
  RETURNS tstzspan[] AS $$
DECLARE
  result tstzspan[];
  card int;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  IF lowtime > hightime - interval '1 minute' *
    maxminutes * 2 * (maxcard - mincard) THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime - '
      'maxminutes * 2 * (maxcard - mincard) minutes: %, %, %, %, %',
      lowtime, hightime, maxminutes, mincard, maxcard;
  END IF;
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' * maxminutes * (card - 1) * 2;
  FOR i IN 1..card
  LOOP
    result[i] = random_tstzspan(t1, t2, maxminutes, i > 1);
    t1 = upper(result[i]) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * 2;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tstzspan_array('2001-01-01', '2002-01-01', 10, 5, 10) AS parr
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------
-- Spanset types
-------------------------------------------------------------------------------

/**
 * @brief Generate a random integer spanset
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between the lower and upper bounds
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_intspanset;
CREATE FUNCTION random_intspanset(lowvalue int, highvalue int, maxdelta int,
  mincard int, maxcard int)
  RETURNS intspanset AS $$
BEGIN
  RETURN spanset(random_intspan_array(lowvalue, highvalue, maxdelta,
    mincard, maxcard));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_intspanset(-100, 100, 10, 5, 10) AS ir
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random bigint span
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between the lower and upper bounds
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_bigintspanset;
CREATE FUNCTION random_bigintspanset(lowvalue bigint, highvalue bigint,
  maxdelta int, mincard int, maxcard int)
  RETURNS bigintspanset AS $$
BEGIN
  RETURN spanset(random_bigintspan_array(lowvalue, highvalue, maxdelta,
    mincard, maxcard));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_bigintspanset(-100, 100, 10, 5, 10) AS bs
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random float spanset
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between the lower and upper bounds
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_floatspanset;
CREATE FUNCTION random_floatspanset(lowvalue float, highvalue float,
  maxdelta int, mincard float, maxcard float)
  RETURNS floatspanset AS $$
DECLARE
  v float;
BEGIN
  RETURN spanset(random_floatspan_array(lowvalue, highvalue, maxdelta,
    mincard, maxcard));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_floatspanset(-100, 100, 10, 5, 10) AS ir
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random datespanset within a datespan
 * @param[in] lowtime, hightime Inclusive bounds of the maximal datespan
 * @param[in] maxdays Maximum number of minutes between two consecutive timestamps
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_datespanset;
CREATE FUNCTION random_datespanset(lowtime date, hightime date, maxdays int,
  mincard int, maxcard int)
  RETURNS datespanset AS $$
BEGIN
  RETURN spanset(random_datespan_array(lowtime, hightime, maxdays, mincard,
    maxcard));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_datespanset('2001-01-01', '2002-01-01', 10, 5, 10) AS ps
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tstzspanset within a tstzspan
 * @param[in] lowtime, hightime Inclusive bounds of the maximal tstzspan
 * @param[in] maxminutes Maximum number of minutes between two consecutive timestamps
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_tstzspanset;
CREATE FUNCTION random_tstzspanset(lowtime timestamptz, hightime timestamptz,
  maxminutes int, mincard int, maxcard int)
  RETURNS tstzspanset AS $$
BEGIN
  RETURN spanset(random_tstzspan_array(lowtime, hightime, maxminutes, mincard,
    maxcard));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tstzspanset('2001-01-01', '2002-01-01', 10, 5, 10) AS ps
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------
-- Range and multirange types
-------------------------------------------------------------------------------

/**
 * @brief Generate a random integer range
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between the lower and upper bounds
 */
DROP FUNCTION IF EXISTS random_int4range;
CREATE FUNCTION random_int4range(lowvalue int, highvalue int, maxdelta int)
  RETURNS int4range AS $$
DECLARE
  v int;
BEGIN
  IF lowvalue > highvalue - maxdelta THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - maxdelta: %, %, %',
      lowvalue, highvalue, maxdelta;
  END IF;
  v = random_int(lowvalue, highvalue - maxdelta);
  RETURN int4range(v, v + random_int(1, maxdelta));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_int4range(-100, 100, 10) AS ir
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

CREATE TYPE float8range AS RANGE (
  subtype = float8,
  subtype_diff = float8mi
);

/**
 * @brief Generate a random float range
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] maxdelta Maximum difference between the lower and upper bounds
 */
DROP FUNCTION IF EXISTS random_float8range;
CREATE FUNCTION random_float8range(lowvalue float, highvalue float, maxdelta float)
  RETURNS float8range AS $$
DECLARE
  v float;
BEGIN
  IF lowvalue > highvalue - maxdelta THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - maxdelta: %, %, %',
      lowvalue, highvalue, maxdelta;
  END IF;
  v = random_float(lowvalue, highvalue - maxdelta);
  RETURN float8range(v, v + random_float(1, maxdelta));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_float8range(-100.0, 100.0, 10.0) AS ir
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random daterange within a datespan
 * @param[in] lowtime, hightime Inclusive bounds of the maximal datespan
 * @param[in] maxdays Maximum number of days between the dates
 */
DROP FUNCTION IF EXISTS random_daterange;
CREATE FUNCTION random_daterange(lowtime date, hightime date, maxdays int)
  RETURNS daterange AS $$
BEGIN
  RETURN random_datespan(lowtime, hightime, maxdays)::daterange;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_daterange('2001-01-01', '2002-01-01', 10) AS r
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tstzrange within a tstzspan
 * @param[in] lowtime, hightime Inclusive bounds of the maximal tstzspan
 * @param[in] maxminutes Maximum number of minutes between the timestamps
 */
DROP FUNCTION IF EXISTS random_tstzrange;
CREATE FUNCTION random_tstzrange(lowtime timestamptz, hightime timestamptz,
  maxminutes int)
  RETURNS tstzrange AS $$
BEGIN
  RETURN random_tstzspan(lowtime, hightime, maxminutes)::tstzrange;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tstzrange('2001-01-01', '2002-01-01', 10) AS r
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------
-- Tbox Type
-------------------------------------------------------------------------------

/**
 * @brief Generate a random tboxint within a range and a tstzspan
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value between the bounds
 * @param[in] maxminutes Maximum number of minutes between the bounds
 */
DROP FUNCTION IF EXISTS random_tboxint;
CREATE FUNCTION random_tboxint(lowvalue int, highvalue int,
   lowtime timestamptz, hightime timestamptz, maxdelta int, maxminutes int)
  RETURNS tbox AS $$
DECLARE
  xmin int;
  tmin timestamptz;
BEGIN
  IF lowvalue > highvalue - maxdelta THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - maxdelta: %, %, %',
      lowvalue, highvalue, maxdelta;
  END IF;
  IF lowtime > hightime - interval '1 minute' * maxminutes THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime - maxminutes minutes: %, %, %',
      lowtime, hightime, maxminutes;
  END IF;
  xmin = random_int(lowvalue, highvalue - maxdelta);
  tmin = random_timestamptz(lowtime, hightime - interval '1 minute' * maxminutes);
  RETURN tbox(span(xmin, xmin + random_int(1, maxdelta)),
    span(tmin, tmin + random_minutes(1, maxminutes)));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tboxint(-100, 100, '2001-01-01', '2002-01-01', 10, 10) AS b
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tboxfloat within a range and a tstzspan
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value between the bounds
 * @param[in] maxminutes Maximum number of minutes between the bounds
 */
DROP FUNCTION IF EXISTS random_tboxfloat;
CREATE FUNCTION random_tboxfloat(lowvalue float, highvalue float,
   lowtime timestamptz, hightime timestamptz, maxdelta float, maxminutes int)
  RETURNS tbox AS $$
DECLARE
  xmin float;
  tmin timestamptz;
BEGIN
  IF lowvalue > highvalue - maxdelta THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue - maxdelta: %, %, %',
      lowvalue, highvalue, maxdelta;
  END IF;
  IF lowtime > hightime - interval '1 minute' * maxminutes THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime - maxminutes minutes: %, %, %',
      lowtime, hightime, maxminutes;
  END IF;
  xmin = random_float(lowvalue, highvalue - maxdelta);
  tmin = random_timestamptz(lowtime, hightime - interval '1 minute' * maxminutes);
  RETURN tbox(span(xmin, xmin + random_float(1, maxdelta)),
    span(tmin, tmin + random_minutes(1, maxminutes)));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tboxfloat(-100, 100, '2001-01-01', '2002-01-01', 10, 10) AS b
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------
-- Temporal Instant
-------------------------------------------------------------------------------

/**
 * @brief Generate a random tbool instant
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 */
DROP FUNCTION IF EXISTS random_tbool_inst;
CREATE FUNCTION random_tbool_inst(lowtime timestamptz, hightime timestamptz)
  RETURNS tbool AS $$
BEGIN
  IF lowtime > hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN tbool(random_bool(), random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tbool_inst('2001-01-01', '2002-01-01') AS inst
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tint instant
 * @param[in] lowvalue, highvalue Inclusive bounds of the range of values
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 */
DROP FUNCTION IF EXISTS random_tint_inst;
CREATE FUNCTION random_tint_inst(lowvalue int, highvalue int,
  lowtime timestamptz, hightime timestamptz)
  RETURNS tint AS $$
BEGIN
  IF lowvalue > highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  IF lowtime > hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN tint(random_int(lowvalue, highvalue),
    random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tint_inst(1, 20, '2001-01-01', '2002-01-01') AS inst
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tfloat instant
 * @param[in] lowvalue, highvalue Inclusive bounds of the range of values
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 */
DROP FUNCTION IF EXISTS random_tfloat_inst;
CREATE FUNCTION random_tfloat_inst(lowvalue float, highvalue float,
  lowtime timestamptz, hightime timestamptz)
  RETURNS tfloat AS $$
BEGIN
  IF lowvalue > highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  IF lowtime > hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN tfloat(random_float(lowvalue, highvalue),
    random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tfloat_inst(1, 20, '2001-01-01', '2002-01-01') AS inst
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random ttext instant
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxlength Maximum length of the text value
 */
DROP FUNCTION IF EXISTS random_ttext_inst;
CREATE FUNCTION random_ttext_inst(lowtime timestamptz, hightime timestamptz,
  maxlength int)
  RETURNS ttext AS $$
BEGIN
  IF lowtime > hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN ttext(random_text(maxlength), random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_ttext_inst('2001-01-01', '2002-01-01', 20) AS inst
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------
-- Temporal Discrete Sequence
-------------------------------------------------------------------------------

/**
 * @brief Generate a random tbool discrete sequence
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence
 */
DROP FUNCTION IF EXISTS random_tbool_discseq;
CREATE FUNCTION random_tbool_discseq(lowtime timestamptz, hightime timestamptz,
  maxminutes int, mincard int, maxcard int)
  RETURNS tbool AS $$
DECLARE
  tsarr timestamptz[];
  result tbool[];
  card int;
BEGIN
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, mincard, maxcard)
  INTO tsarr;
  card = array_length(tsarr, 1);
  FOR i IN 1..card
  LOOP
    result[i] = tbool(random_bool(), tsarr[i]);
  END LOOP;
  RETURN tboolSeq(result, text 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tbool_discseq('2001-01-01', '2002-01-01', 10, 5, 10) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tint discrete sequence
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence
 */
DROP FUNCTION IF EXISTS random_tint_discseq;
CREATE FUNCTION random_tint_discseq(lowvalue int, highvalue int, lowtime timestamptz,
  hightime timestamptz, maxdelta int, maxminutes int, mincard int, maxcard int)
  RETURNS tint AS $$
DECLARE
  intarr int[];
  tsarr timestamptz[];
  result tint[];
  card int;
BEGIN
  SELECT random_int_array(lowvalue, highvalue, maxdelta, mincard, maxcard) INTO intarr;
  card = array_length(intarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card)
  INTO tsarr;
  FOR i IN 1..card
  LOOP
    result[i] = tint(intarr[i], tsarr[i]);
  END LOOP;
  RETURN tintSeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tint_discseq(-100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tfloat discrete sequence
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence
 */
DROP FUNCTION IF EXISTS random_tfloat_discseq;
CREATE FUNCTION random_tfloat_discseq(lowvalue float, highvalue float,
  lowtime timestamptz, hightime timestamptz, maxdelta float, maxminutes int,
  mincard int, maxcard int)
  RETURNS tfloat AS $$
DECLARE
  floatarr float[];
  tsarr timestamptz[];
  result tfloat[];
  card int;
BEGIN
  SELECT random_float_array(lowvalue, highvalue, maxdelta, mincard, maxcard)
  INTO floatarr;
  card = array_length(floatarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card)
  INTO tsarr;
  FOR i IN 1..card
  LOOP
    result[i] = tfloat(floatarr[i], tsarr[i]);
  END LOOP;
  RETURN tfloatSeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tfloat_discseq(-100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random ttext discrete sequence
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxlength Maximum length of the text value
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence
 */
DROP FUNCTION IF EXISTS random_ttext_discseq;
CREATE FUNCTION random_ttext_discseq(lowtime timestamptz, hightime timestamptz,
  maxlength int, maxminutes int, mincard int, maxcard int)
  RETURNS ttext AS $$
DECLARE
  tsarr timestamptz[];
  result ttext[];
  card int;
BEGIN
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, mincard, maxcard)
  INTO tsarr;
  card = array_length(tsarr, 1);
  FOR i IN 1..card
  LOOP
    result[i] = ttext(random_text(maxlength), tsarr[i]);
  END LOOP;
  RETURN ttextSeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_ttext_discseq('2001-01-01', '2002-01-01', 10, 10, 5, 10) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------
-- Temporal Continuous Sequence
-------------------------------------------------------------------------------

/**
 * @brief Generate a random tbool sequence
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tbool_seq;
CREATE FUNCTION random_tbool_seq(lowtime timestamptz, hightime timestamptz,
  maxminutes int, mincard int, maxcard int, fixstart bool DEFAULT false)
  RETURNS tbool AS $$
DECLARE
  tsarr timestamptz[];
  result tbool[];
  card int;
  v bool;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, mincard,
    maxcard, fixstart) INTO tsarr;
  card = array_length(tsarr, 1);
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  v = random_bool();
  FOR i IN 1..card - 1
  LOOP
    result[i] = tbool(v, tsarr[i]);
    v = NOT v;
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc THEN
    v = NOT v;
  END IF;
  result[card] = tbool(v, tsarr[card]);
  RETURN tboolSeq(result, 'step', lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tbool_seq('2001-01-01', '2002-01-01', 10, 5, 10) AS seq
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tint sequence
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tint_seq;
CREATE FUNCTION random_tint_seq(lowvalue int, highvalue int, lowtime timestamptz,
  hightime timestamptz, maxdelta int, maxminutes int, mincard int, maxcard int,
  fixstart bool DEFAULT false)
  RETURNS tint AS $$
DECLARE
  intarr int[];
  tsarr timestamptz[];
  result tint[];
  card int;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_int_array(lowvalue, highvalue, maxdelta, mincard, maxcard)
  INTO intarr;
  card = array_length(intarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card,
    fixstart) INTO tsarr;
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    result[i] = tint(intarr[i], tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc THEN
    result[card] = tint(intarr[card - 1], tsarr[card]);
  ELSE
    result[card] = tint(intarr[card], tsarr[card]);
  END IF;
  RETURN tintSeq(result, 'step', lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tint_seq(-100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS seq
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tfloat sequence
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @parap[in] linear True for linear sequence, false for step sequence
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tfloat_seq;
CREATE FUNCTION random_tfloat_seq(lowvalue float, highvalue float,
  lowtime timestamptz, hightime timestamptz, maxdelta float, maxminutes int,
  mincard int, maxcard int, linear bool DEFAULT true, fixstart bool DEFAULT false)
  RETURNS tfloat AS $$
DECLARE
  floatarr float[];
  tsarr timestamptz[];
  result tfloat[];
  card int;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_float_array(lowvalue, highvalue, maxdelta, mincard, maxcard)
  INTO floatarr;
  card = array_length(floatarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card,
    fixstart) INTO tsarr;
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    result[i] = tfloat(floatarr[i], tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc AND NOT linear THEN
    result[card] = tfloat(floatarr[card - 1], tsarr[card]);
  ELSE
    result[card] = tfloat(floatarr[card], tsarr[card]);
  END IF;
  IF linear THEN
    interp = 'Linear';
  ELSE
    interp = 'Step';
  END IF;
  RETURN tfloatSeq(result, interp, lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tfloat_seq(-100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS seq
FROM generate_series(1, 15) AS k;

-- Step interpolation
SELECT k, random_tfloat_seq(-100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, false) AS seq
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random ttext sequence
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxlength Maximum length of the text value
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_ttext_seq;
CREATE FUNCTION random_ttext_seq(lowtime timestamptz, hightime timestamptz,
  maxlength int, maxminutes int, mincard int, maxcard int,
  fixstart bool DEFAULT false)
  RETURNS ttext AS $$
DECLARE
  tsarr timestamptz[];
  result ttext[];
  card int;
  v text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, mincard,
    maxcard, fixstart) INTO tsarr;
  card = array_length(tsarr, 1);
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    v = random_text(maxlength);
    result[i] = ttext(v, tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card = 1 OR upper_inc THEN
    v = random_text(maxlength);
  END IF;
  result[card] = ttext(v, tsarr[card]);
  RETURN ttextSeq(result, 'step', lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_ttext_seq('2001-01-01', '2002-01-01', 10, 10, 5, 10) AS seq
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------
-- Temporal Sequence Set
-------------------------------------------------------------------------------

/**
 * @brief Function ensuring that the duration defined by hightime - lowtime is enough
 * for generating the sequence set given the other parameters
 * lowtime must be less than hightime -
 * ( (maxminutes * cardseq * card) + (card * maxminutes) ) minutes
 * where cardseq = (maxcardseq - mincardseq) and card = (maxcard - mincard)
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 */
DROP FUNCTION IF EXISTS tsequenceset_valid_duration;
CREATE FUNCTION tsequenceset_valid_duration(lowtime timestamptz, hightime timestamptz,
  maxminutes int, mincardseq int, maxcardseq int, mincard int, maxcard int)
  RETURNS void AS $$
BEGIN
  IF lowtime >= hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  IF mincardseq > maxcardseq THEN
    RAISE EXCEPTION 'mincardseq must be less than or equal to maxcardseq: %, %',
      mincardseq, maxcardseq;
  END IF;
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  IF lowtime > hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) + ((maxcard - mincard) * maxminutes) ) THEN
    RAISE EXCEPTION
      'The duration between lowtime and hightime is not enough for generating the temporal sequence set';
  END IF;
END;
$$ LANGUAGE PLPGSQL STRICT;

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tbool sequence set
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 */
DROP FUNCTION IF EXISTS random_tbool_seqset;
CREATE FUNCTION random_tbool_seqset(lowtime timestamptz, hightime timestamptz,
  maxminutes int, mincardseq int, maxcardseq int, mincard int, maxcard int)
  RETURNS tbool AS $$
DECLARE
  result tbool[];
  seq tbool;
  card int;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter is set to true for all i except 1
    SELECT random_tbool_seq(t1, t2, maxminutes, mincardseq, maxcardseq, i > 1)
    INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tboolSeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tbool_seqset('2001-01-01', '2002-01-01', 10, 5, 10, 5, 10) AS ts
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tint sequence set
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 */
DROP FUNCTION IF EXISTS random_tint_seqset;
CREATE FUNCTION random_tint_seqset(lowvalue int, highvalue int, lowtime timestamptz,
  hightime timestamptz, maxdelta int, maxminutes int, mincardseq int,
  maxcardseq int, mincard int, maxcard int)
  RETURNS tint AS $$
DECLARE
  result tint[];
  seq tint;
  card int;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  IF lowvalue > highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i except 1
    SELECT random_tint_seq(lowvalue, highvalue, t1, t2, maxdelta,
      maxminutes, mincardseq, maxcardseq, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tintSeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tint_seqset(1, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10) AS ts
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random tfloat sequence set
 * @param[in] lowvalue, highvalue Inclusive bounds of the range
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 */
DROP FUNCTION IF EXISTS random_tfloat_seqset;
CREATE FUNCTION random_tfloat_seqset(lowvalue float, highvalue float,
  lowtime timestamptz, hightime timestamptz, maxdelta float, maxminutes int,
  mincardseq int, maxcardseq int, mincard int, maxcard int,
  linear bool DEFAULT true)
  RETURNS tfloat AS $$
DECLARE
  result tfloat[];
  seq tfloat;
  card float;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  IF lowvalue > highvalue THEN
    RAISE EXCEPTION 'lowvalue must be less than or equal to highvalue: %, %',
      lowvalue, highvalue;
  END IF;
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i except 1
    SELECT random_tfloat_seq(lowvalue, highvalue, t1, t2, maxdelta,
      maxminutes, mincardseq, maxcardseq, linear, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tfloatSeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tfloat_seqset(1, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10) AS ts
FROM generate_series(1, 15) AS k;

-- Step interpolation
SELECT k, random_tfloat_seqset(1, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10, false) AS ts
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random ttext sequence set
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxlength Maximum length of the text value
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 */
DROP FUNCTION IF EXISTS random_ttext_seqset;
CREATE FUNCTION random_ttext_seqset(lowtime timestamptz, hightime timestamptz,
  maxlength int, maxminutes int, mincardseq int, maxcardseq int,
  mincard int, maxcard int)
  RETURNS ttext AS $$
DECLARE
  result ttext[];
  seq ttext;
  card int;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i except 1
    SELECT random_ttext_seq(t1, t2, maxlength, maxminutes, mincardseq, maxcardseq, i > 1)
    INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN ttextSeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_ttext_seqset('2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10) AS ts
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------


-- Begin contents of geo/random_tpoint.sql

/*****************************************************************************
 *
 * This MobilityDB code is provided under The PostgreSQL License.
 * Copyright (c) 2016-2025, Université libre de Bruxelles and MobilityDB
 * contributors
 *
 * MobilityDB includes portions of PostGIS version 3 source code released
 * under the GNU General Public License (GPLv2 or later).
 * Copyright (c) 2001-2025, PostGIS contributors
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without a written
 * agreement is hereby granted, provided that the above copyright notice and
 * this paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL UNIVERSITE LIBRE DE BRUXELLES BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
 * EVEN IF UNIVERSITE LIBRE DE BRUXELLES HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * UNIVERSITE LIBRE DE BRUXELLES SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON
 * AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 *****************************************************************************/

/*
 * random_tpoint.sql
 * Basic synthetic data generator functions for geometry/geography types
 * and temporal point types.
 *
 * These functions use lower and upper bounds for the generated values:
 * lowx/lowy/lowz and highx/highy/highz for coordinates, lowtime and hightime
 * for timestamps. When generating series of values, the maxdelta argument
 * states the maximum difference between two consecutive coordinate values,
 * while maxminutes states the maximum number of minutes between two
 * consecutive timestamps as well as the maximum number of minutes for time
 * gaps between two consecutive components of temporal instant/sequence sets.
 */

-------------------------------------------------------------------------------
-- STBox Type
-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D stbox
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value between the bounds for the x,y,z coordinates
 * @param[in] maxminutes Maximum number of minutes between the bounds
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_stbox;
CREATE FUNCTION random_stbox(lowx float, highx float, lowy float, highy float,
  lowtime timestamptz, hightime timestamptz, maxdelta float, maxminutes int,
  srid int DEFAULT 0)
  RETURNS stbox AS $$
DECLARE
  xmin float;
  ymin float;
  tmin timestamptz;
BEGIN
  IF lowx > highx - maxdelta THEN
    RAISE EXCEPTION 'lowx must be less than or equal to highx - maxdelta: %, %, %',
      lowx, highx, maxdelta;
  END IF;
  IF lowy > highy - maxdelta THEN
    RAISE EXCEPTION 'lowy must be less than or equal to highy - maxdelta: %, %, %',
      lowy, highy, maxdelta;
  END IF;
  IF lowtime > hightime - interval '1 minute' * maxminutes THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime - maxminutes minutes: %, %, %',
      lowtime, hightime, maxminutes;
  END IF;
  xmin = random_float(lowx, highx - maxdelta);
  ymin = random_float(lowy, highy - maxdelta);
  tmin = random_timestamptz(lowtime, hightime - interval '1 minute' * maxminutes);
  RETURN stboxXT(xmin, xmin + random_float(1, maxdelta), ymin,
    ymin + random_float(1, maxdelta),
    span(tmin, tmin + random_minutes(1, maxminutes)), srid);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_stbox(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10) AS b
FROM generate_series(1,10) k;

SELECT k, random_stbox(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 3812) AS b
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D stbox
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value between the bounds for the x,y,z coordinates
 * @param[in] maxminutes Maximum number of minutes between the bounds
 * @param[in] geodetic True if the stbox is geodetic
 * @param[in] geodZ True if the stbox is geodetic and has Z coordinates
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_stbox3D;
CREATE FUNCTION random_stbox3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxdelta float, maxminutes int,
  geodetic boolean DEFAULT false, geodZ boolean DEFAULT false, srid int DEFAULT 0)
  RETURNS stbox AS $$
DECLARE
  xmin float;
  ymin float;
  zmin float;
  tmin timestamptz;
BEGIN
  IF lowx > highx - maxdelta THEN
    RAISE EXCEPTION 'lowx must be less than or equal to highx - maxdelta: %, %, %',
      lowx, highx, maxdelta;
  END IF;
  IF lowy > highy - maxdelta THEN
    RAISE EXCEPTION 'lowy must be less than or equal to highy - maxdelta: %, %, %',
      lowy, highy, maxdelta;
  END IF;
  IF lowz > highz - maxdelta THEN
    RAISE EXCEPTION 'lowz must be less than or equal to highz - maxdelta: %, %, %',
      lowz, highz, maxdelta;
  END IF;
  IF lowtime > hightime - interval '1 minute' * maxminutes THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime - maxminutes minutes: %, %, %',
      lowtime, hightime, maxminutes;
  END IF;
  xmin = random_float(lowx, highx - maxdelta);
  ymin = random_float(lowy, highy - maxdelta);
  zmin = random_float(lowz, highz - maxdelta);
  tmin = random_timestamptz(lowtime, hightime - interval '1 minute' * maxminutes);
  IF geodetic THEN
    IF geodZ THEN
      RETURN geodstboxZT(xmin, xmin + random_float(1, maxdelta), ymin,
        ymin + random_float(1, maxdelta), zmin, zmin + random_float(1, maxdelta),
        span(tmin, tmin + random_minutes(1, maxminutes)), srid);
    ELSE
      RETURN geodstboxZT(xmin, xmin + random_float(1, maxdelta),ymin, zmin,
        ymin + random_float(1, maxdelta), zmin + random_float(1, maxdelta),
        span(tmin, tmin + random_minutes(1, maxminutes)), srid);
    END IF;
  ELSE
    RETURN stboxZT(xmin, xmin + random_float(1, maxdelta), ymin,
      ymin + random_float(1, maxdelta), zmin, zmin + random_float(1, maxdelta),
      span(tmin + random_minutes(1, maxminutes)), srid);
  END IF;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_stbox3D(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10) AS b
FROM generate_series(1,10) k;

SELECT k, random_stbox3D(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, srid:=3812) AS b
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geodetic stbox
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value between the bounds for the x,y,z coordinates
 * @param[in] maxminutes Maximum number of minutes between the bounds
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geodstbox;
CREATE FUNCTION random_geodstbox(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxdelta float, maxminutes int, srid int DEFAULT 4326)
  RETURNS stbox AS $$
BEGIN
  RETURN random_stbox3D(lowx, highx, lowy, highy, lowz, highz, lowtime,
    hightime, maxdelta, maxminutes, true, false, srid);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_geodstbox(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10) AS b
FROM generate_series(1,10) k;

SELECT k, random_geodstbox(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 7844) AS b
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geodetic stbox
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value between the bounds for the x,y,z coordinates
 * @param[in] maxminutes Maximum number of minutes between the bounds
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geodstbox3D;
CREATE FUNCTION random_geodstbox3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxdelta float, maxminutes int, srid int DEFAULT 4326)
  RETURNS stbox AS $$
BEGIN
  RETURN random_stbox3D(lowx, highx, lowy, highy, lowz, highz, lowtime,
    hightime, maxdelta, maxminutes, true, true, srid);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_geodstbox3D(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10) AS b
FROM generate_series(1,10) k;

SELECT k, random_geodstbox3D(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 7844) AS b
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------
-- Geometry/Geography
-------------------------------------------------------------------------------
/**
 * @brief Generate a random 2D geometric point
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_point;
CREATE FUNCTION random_geom_point(lowx float, highx float, lowy float,
  highy float, srid int DEFAULT 0)
  RETURNS geometry AS $$
BEGIN
  IF lowx > highx THEN
    RAISE EXCEPTION 'lowx must be less than or equal to highx: %, %',
      lowx, highx;
  END IF;
  IF lowy > highy THEN
    RAISE EXCEPTION 'lowy must be less than or equal to highy: %, %',
      lowy, highy;
  END IF;
  RETURN ST_Point(random_float(lowx, highx), random_float(lowy, highy), srid);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_point(-100, 100, -100, 100))
FROM generate_series(1,10) k;

SELECT k, st_asEWKT(random_geom_point(-100, 100, -100, 100, 3812))
FROM generate_series(1,10) k;

SELECT k, random_geom_point(-100, 100, -100, 100) AS g
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geometric point
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_point3D;
CREATE FUNCTION random_geom_point3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, srid int DEFAULT 0)
  RETURNS geometry AS $$
BEGIN
  IF lowx > highx THEN
    RAISE EXCEPTION 'lowx must be less than or equal to highx: %, %',
      lowx, highx;
  END IF;
  IF lowy > highy THEN
    RAISE EXCEPTION 'lowy must be less than or equal to highy: %, %',
      lowy, highy;
  END IF;
  IF lowz > highz THEN
    RAISE EXCEPTION 'lowz must be less than or equal to highz: %, %',
      lowz, highz;
  END IF;
  RETURN ST_PointZ(random_float(lowx, highx), random_float(lowy, highy),
    random_float(lowz, highz), srid);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_point3D(-100, 100, -100, 100, 0, 100))
FROM generate_series(1,10) k;

SELECT k, st_asEWKT(random_geom_point3D(-100, 100, -100, 100, 0, 100, 3812))
FROM generate_series(1,10) k;

SELECT k, random_geom_point3D(-100, 100, -100, 100, 0, 100) AS g
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geographic point
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_point;
CREATE FUNCTION random_geog_point(lowx float, highx float,
  lowy float, highy float, srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_point(lowx, highx, lowy, highy, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_point(-180, 180, 90, 90))
FROM generate_series(1,10) k;

SELECT k, st_asEWKT(random_geog_point(-180, 180, 90, 90, 7844))
FROM generate_series(1,10) k;

SELECT k, random_geog_point(-180, 180, 90, 90) AS g
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geographic point
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_point3D;
CREATE FUNCTION random_geog_point3D(lowx float, highx float,
  lowy float, highy float, lowz float, highz float, srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_point3D(lowx, highx, lowy, highy, lowz, highz, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_point3D(0, 90, 0, 90, 0, 90))
FROM generate_series(1,10) k;

SELECT k, st_asEWKT(random_geog_point3D(0, 90, 0, 90, 0, 90, 7844))
FROM generate_series(1,10) k;

SELECT k, random_geog_point3D(0, 90, 0, 90, 0, 90) AS g
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random 2D geometric points
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_point_array;
CREATE FUNCTION random_geom_point_array(lowx float, highx float, lowy float,
  highy float, maxdelta float, mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS geometry[] AS $$
DECLARE
  result geometry[];
  card int;
  x float;
  y float;
  deltax float;
  deltay float;
  p geometry;
BEGIN
  IF lowx > highx THEN
    RAISE EXCEPTION 'lowx must be less than or equal to highx: %, %',
      lowx, highx;
  END IF;
  IF lowy > highy THEN
    RAISE EXCEPTION 'lowy must be less than or equal to highy: %, %',
      lowy, highy;
  END IF;
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  card = random_int(mincard, maxcard);
  p = random_geom_point(lowx, highx, lowy, highy, srid);
  FOR i IN 1..card
  LOOP
    result[i] = p;
    IF i = card THEN EXIT; END IF;
    x = st_x(p);
    y = st_y(p);
    deltax = random_float(-1 * maxdelta, maxdelta);
    deltay = random_float(-1 * maxdelta, maxdelta);
    /* If neither of these conditions is satisfied the same value is kept */
    IF (x + deltax >= lowx and x + deltax <= highx) THEN
      x = x + deltax;
    ELSIF (x - deltax >= lowx AND x - deltax <= highx) THEN
      x = x - deltax;
    END IF;
    IF (y + deltay >= lowy and y + deltay <= highy) THEN
      y = y + deltay;
    ELSIF (y - deltay >= lowy AND y - deltay <= highy) THEN
      y = y - deltay;
    END IF;
    p = ST_Point(x, y, srid);
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_geom_point_array(-100, 100, -100, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_geom_point_array(-100, 100, -100, 100, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_point_array(-100, 100, -100, 100, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random 3D geometric points
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_point3D_array;
CREATE FUNCTION random_geom_point3D_array(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, maxdelta float, mincard int, maxcard int,
  srid int DEFAULT 0)
  RETURNS geometry[] AS $$
DECLARE
  result geometry[];
  card int;
  x float;
  y float;
  z float;
  deltax float;
  deltay float;
  deltaz float;
  p geometry;
BEGIN
  IF lowx > highx THEN
    RAISE EXCEPTION 'lowx must be less than or equal to highx: %, %',
      lowx, highx;
  END IF;
  IF lowy > highy THEN
    RAISE EXCEPTION 'lowy must be less than or equal to highy: %, %',
      lowy, highy;
  END IF;
  IF lowz > highz THEN
    RAISE EXCEPTION 'lowz must be less than or equal to highz: %, %',
      lowz, highz;
  END IF;
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  card = random_int(mincard, maxcard);
  p = random_geom_point3D(lowx, highx, lowy, highy, lowz, highz, srid);
  FOR i IN 1..card
  LOOP
    result[i] = p;
    IF i = card THEN EXIT; END IF;
    x = st_x(p);
    y = st_y(p);
    z = st_z(p);
    deltax = random_float(-1 * maxdelta, maxdelta);
    deltay = random_float(-1 * maxdelta, maxdelta);
    deltaz = random_float(-1 * maxdelta, maxdelta);
    /* If neither of these conditions is satisfied the same value is kept */
    IF (x + deltax >= lowx and x + deltax <= highx) THEN
      x = x + deltax;
    ELSIF (x - deltax >= lowx AND x - deltax <= highx) THEN
      x = x - deltax;
    END IF;
    IF (y + deltay >= lowy and y + deltay <= highy) THEN
      y = y + deltay;
    ELSIF (y - deltay >= lowy AND y - deltay <= highy) THEN
      y = y - deltay;
    END IF;
    IF (z + deltaz >= lowz and z + deltaz <= highz) THEN
      z = z + deltaz;
    ELSIF (z - deltaz >= lowz AND z - deltaz <= highz) THEN
      z = z - deltaz;
    END IF;
    p = ST_PointZ(x, y, z, srid);
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_geom_point3D_array(-100, 100, -100, 100, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_geom_point3D_array(-100, 100, -100, 100, 0, 100, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_point3D_array(-100, 100, -100, 100, 0, 100, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random 2D geographic points
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_point_array;
CREATE FUNCTION random_geog_point_array(lowx float, highx float, lowy float,
  highy float, maxdelta float, mincard int, maxcard int, srid int DEFAULT 4326)
  RETURNS geography[] AS $$
DECLARE
  pointarr geometry[];
  result geography[];
  card int;
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  SELECT random_geom_point_array(lowx, highx, lowy, highy, maxdelta, mincard,
    maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  FOR i in 1..card
  LOOP
    result[i] = pointarr[i]::geography;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_geog_point_array(-180, 180, -90, 90, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_geog_point_array(-180, 180, -90, 90, 10, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_point_array(-180, 180, -90, 90, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random 3D geographic points
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_point3D_array;
CREATE FUNCTION random_geog_point3D_array(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, maxdelta float, mincard int, maxcard int,
  srid int DEFAULT 4326)
  RETURNS geography[] AS $$
DECLARE
  pointarr geometry[];
  result geography[];
  card int;
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  SELECT random_geom_point3D_array(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, mincard, maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  FOR i in 1..card
  LOOP
    result[i] = pointarr[i]::geography;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_geog_point3D_array(-180, 180, -90, 90, 0, 10000, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_point3D_array(-180, 180, -90, 90, 0, 10000, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random 2D geometric points
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_point_set;
CREATE FUNCTION random_geom_point_set(lowx float, highx float, lowy float,
  highy float, maxdelta float, mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS geomset AS $$
DECLARE
  garr geometry[];
BEGIN
  RETURN set(random_geom_point_array(lowx, highx, lowy, highy, maxdelta,
    mincard, maxcard, srid));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_geom_point_set(-100, 100, -100, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_geom_point_set(-100, 100, -100, 100, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_point_set(-100, 100, -100, 100, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random 2D geographic points
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_point_set;
CREATE FUNCTION random_geog_point_set(lowx float, highx float, lowy float,
  highy float, maxdelta float, mincard int, maxcard int, srid int DEFAULT 4326)
  RETURNS geogset AS $$
DECLARE
  garr geography[];
BEGIN
  RETURN set(random_geog_point_array(lowx, highx, lowy, highy, maxdelta,
    mincard, maxcard, srid));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_geog_point_set(-180, 180, -90, 90, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_geog_point_set(-180, 180, -90, 90, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_point_set(-180, 180, -90, 90, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geometric linestring
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_linestring;
CREATE FUNCTION random_geom_linestring(lowx float, highx float, lowy float,
  highy float, maxdelta float, minvertices int, maxvertices int, srid int DEFAULT 0)
  RETURNS geometry AS $$
BEGIN
  RETURN ST_SetSRID(st_makeline(random_geom_point_array(lowx, highx, lowy, highy, maxdelta,
    minvertices, maxvertices)), srid);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_linestring(-100, 100, -100, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geom_linestring(-100, 100, -100, 100, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT distinct st_issimple(random_geom_linestring(-100, 100, -100, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_length(random_geom_linestring(-100, 100, -100, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_linestring(-100, 100, -100, 100, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geometric linestring
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_linestring3D;
CREATE FUNCTION random_geom_linestring3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, maxdelta float, minvertices int,
  maxvertices int, srid int DEFAULT 0)
  RETURNS geometry AS $$
BEGIN
  RETURN ST_SetSRID(st_makeline(random_geom_point3D_array(lowx, highx, lowy,
    highy, lowz, highz, maxdelta, minvertices, maxvertices)), srid);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_linestring3D(-100, 100, -100, 100, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geom_linestring3D(-100, 100, -100, 100, 0, 100, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT distinct st_issimple(random_geom_linestring3D(-100, 100, -100, 100, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_length(random_geom_linestring3D(-100, 100, -100, 100, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_linestring3D(-100, 100, -100, 100, 0, 100, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geographic linestring
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_linestring;
CREATE FUNCTION random_geog_linestring(lowx float, highx float, lowy float,
  highy float, maxdelta float, minvertices int, maxvertices int,
  srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_linestring(lowx, highx, lowy, highy, maxdelta,
    minvertices, maxvertices, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_linestring(-180, 180, -90, 90, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geog_linestring(-180, 180, -90, 90, 10, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_length(random_geog_linestring(-180, 180, -90, 90, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_linestring(-180, 180, -90, 90, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geographic linestring
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_linestring3D;
CREATE FUNCTION random_geog_linestring3D(lowx float, highx float,
    lowy float, highy float, lowz float, highz float, maxdelta float,
    minvertices int, maxvertices int, srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_linestring3D(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, minvertices, maxvertices, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_linestring3D(-180, 180, -90, 90, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geog_linestring3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_length(random_geog_linestring3D(-180, 180, -90, 90, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_linestring3D(0, 80, 0, 0, 80, 80, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geometric polygon without holes
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_polygon;
CREATE FUNCTION random_geom_polygon(lowx float, highx float, lowy float,
  highy float, maxdelta float, minvertices int, maxvertices int,
  srid int DEFAULT 0)
  RETURNS geometry AS $$
DECLARE
  pointarr geometry[];
  noVertices int;
BEGIN
  IF minvertices < 3 THEN
    raise exception 'A polygon requires at least 3 vertices';
  END IF;
  IF minvertices > maxvertices THEN
    raise exception 'minvertices must be greater than or equal to maxvertices';
  END IF;
  SELECT random_geom_point_array(lowx, highx, lowy, highy, maxdelta, 
    minvertices, maxvertices) INTO pointarr;
  noVertices = array_length(pointarr, 1);
  pointarr[noVertices + 1] = pointarr[1];
  RETURN ST_SetSRID(st_makepolygon(st_makeline(pointarr)), srid);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_polygon(-100, 100, -100, 100, 10, 5, 10)) AS g
FROM generate_series(1,10) k;

SELECT k, st_asEWKT(random_geom_polygon(-100, 100, -100, 100, 10, 5, 10, 3812)) AS g
FROM generate_series(1,10) k;

SELECT k, random_geom_polygon(-100, 100, -100, 100, 10, 5, 10) AS g
FROM generate_series(1,10) k;

SELECT distinct st_isvalid(random_geom_polygon(-100, 100, -100, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geometric polygon without holes
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_polygon3D;
CREATE FUNCTION random_geom_polygon3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, maxdelta float, minvertices int,
  maxvertices int, srid int DEFAULT 0)
  RETURNS geometry AS $$
DECLARE
  pointarr geometry[];
  noVertices int;
BEGIN
  IF minvertices < 3 THEN
    raise exception 'A polygon requires at least 3 vertices';
  END IF;
  IF minvertices > maxvertices THEN
    raise exception 'minvertices must be greater than or equal to maxvertices';
  END IF;
  SELECT random_geom_point3D_array(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, minvertices, maxvertices, srid) INTO pointarr;
  noVertices = array_length(pointarr, 1);
  pointarr[noVertices + 1] = pointarr[1];
  RETURN st_makepolygon(st_makeline(pointarr));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_polygon3D(-100, 100, -100, 100, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1,10) k;

SELECT k, st_asEWKT(random_geom_polygon3D(-100, 100, -100, 100, 0, 100, 10, 5, 10, 3812)) AS g
FROM generate_series(1,10) k;

SELECT k, random_geom_polygon3D(-100, 100, -100, 100, 0, 100, 10, 5, 10) AS g
FROM generate_series(1,10) k;

SELECT distinct st_isvalid(random_geom_polygon3D(-100, 100, -100, 100, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geographic polygon without holes
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_polygon;
CREATE FUNCTION random_geog_polygon(lowx float, highx float, lowy float,
  highy float, maxdelta float, minvertices int, maxvertices int,
  srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_polygon(lowx, highx, lowy, highy, maxdelta, minvertices,
    maxvertices, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_polygon(-180, 180, -90, 90, 10, 5, 10)) AS g
FROM generate_series(1,10) k;

SELECT k, st_asEWKT(random_geog_polygon(-180, 180, -90, 90, 10, 5, 10, 7844)) AS g
FROM generate_series(1,10) k;

SELECT k, random_geog_polygon(-180, 180, -90, 90, 10, 5, 10) AS g
FROM generate_series(1,10) k;

SELECT k, st_area(random_geog_polygon(-180, 180, -90, 90, 10, 5, 10)) AS g
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geographic polygon without holes
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_polygon3D;
CREATE FUNCTION random_geog_polygon3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, maxdelta float, minvertices int,
  maxvertices int, srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_polygon3D(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, minvertices, maxvertices, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_polygon3D(-180, 180, -90, 90, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1,10) k;

SELECT k, st_asEWKT(random_geog_polygon3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 7844)) AS g
FROM generate_series(1,10) k;

SELECT k, random_geog_polygon3D(-180, 180, -90, 90, 0, 100, 10, 5, 10) AS g
FROM generate_series(1,10) k;

SELECT k, st_area(random_geog_polygon3D(-180, 180, -90, 90, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geometric multipoint
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] mincard, maxcard Inclusive bounds of the number of points
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_multipoint;
CREATE FUNCTION random_geom_multipoint(lowx float, highx float, lowy float,
  highy float, maxdelta float, mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS geometry AS $$
BEGIN
  RETURN st_collect(random_geom_point_array(lowx, highx, lowy, highy, maxdelta,
    mincard, maxcard, srid));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_multipoint(-100, 100, -100, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geom_multipoint(-100, 100, -100, 100, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_multipoint(-100, 100, -100, 100, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geometric multipoint
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] mincard, maxcard Inclusive bounds of the number of points
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_multipoint3D;
CREATE FUNCTION random_geom_multipoint3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, maxdelta float, mincard int, maxcard int,
  srid int DEFAULT 0)
  RETURNS geometry AS $$
BEGIN
  RETURN st_collect(random_geom_point3D_array(lowx, highx, lowy, highy, lowz,
    highz, maxdelta, mincard, maxcard, srid));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_multipoint3D(-100, 100, -100, 100, -100, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geom_multipoint3D(-100, 100, -100, 100, -100, 100, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_multipoint3D(-100, 100, -100, 100, -100, 100, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geographic multipoint
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] mincard, maxcard Inclusive bounds of the number of points
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_multipoint;
CREATE FUNCTION random_geog_multipoint(lowx float, highx float, lowy float,
  highy float, maxdelta float, mincard int, maxcard int, srid int DEFAULT 4326)
  RETURNS geography AS $$
DECLARE
  result geometry[];
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_multipoint(lowx, highx, lowy, highy, maxdelta, mincard,
    maxcard, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_multipoint(-180, 180, -90, 90, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geog_multipoint(-180, 180, -90, 90, 10, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_multipoint(-180, 180, -90, 90, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geographic multipoint
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] mincard, maxcard Inclusive bounds of the number of points
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_multipoint3D;
CREATE FUNCTION random_geog_multipoint3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, maxdelta float, mincard int, maxcard int,
  srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_multipoint3D(lowx, highx, lowy, highy, lowz, highz, maxdelta, mincard,
    maxcard, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_multipoint3D(-180, 180, -90, 90, 0, 100, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geog_multipoint3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_multipoint3D(-180, 180, -90, 90, 0, 100, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geometric multilinestring
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] mincard, maxcard Inclusive bounds of the number of linestrings
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_multilinestring;
CREATE FUNCTION random_geom_multilinestring(lowx float, highx float, lowy float,
  highy float, maxdelta float, minvertices int, maxvertices int, mincard int,
  maxcard int, srid int DEFAULT 0)
  RETURNS geometry AS $$
DECLARE
  result geometry[];
BEGIN
  SELECT array_agg(random_geom_linestring(lowx, highx, lowy, highy, maxdelta,
    minvertices, maxvertices, srid)) INTO result
  FROM generate_series(mincard, random_int(mincard, maxcard)) AS x;
  RETURN st_unaryunion(st_collect(result));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_multilinestring(-100, 100, -100, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geom_multilinestring(-100, 100, -100, 100, 10, 5, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_length(random_geom_multilinestring(-100, 100, -100, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_multilinestring(-100, 100, -100, 100, 10, 5, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geometric multilinestring
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] mincard, maxcard Inclusive bounds of the number of linestrings
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_multilinestring3D;
CREATE FUNCTION random_geom_multilinestring3D(lowx float, highx float,
  lowy float, highy float, lowz float, highz float, maxdelta float,
  minvertices int, maxvertices int, mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS geometry AS $$
DECLARE
  result geometry[];
BEGIN
  SELECT array_agg(random_geom_linestring3D(lowx, highx, lowy, highy, lowz,
    highz, maxdelta, minvertices, maxvertices, srid)) INTO result
  FROM generate_series(mincard, random_int(mincard, maxcard)) AS x;
  RETURN st_unaryunion(st_collect(result));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_multilinestring3D(-100, 100, -100, 100, 0, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geom_multilinestring3D(-100, 100, -100, 100, 0, 100, 10, 5, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_length(random_geom_multilinestring3D(-100, 100, -100, 100, 0, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_multilinestring3D(-100, 100, -100, 100, 0, 100, 10, 5, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geographic multilinestring
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] mincard, maxcard Inclusive bounds of the number of linestrings
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_multilinestring;
CREATE FUNCTION random_geog_multilinestring(lowx float, highx float, lowy float,
    highy float, maxdelta float, minvertices int, maxvertices int, mincard int,
    maxcard int, srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_multilinestring(lowx, highx, lowy, highy, maxdelta,
    minvertices, maxvertices, mincard, maxcard, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_multilinestring(-180, 180, -90, 90, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geog_multilinestring(-180, 180, -90, 90, 10, 5, 10, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_length(random_geog_multilinestring(-180, 180, -90, 90, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_multilinestring(-180, 180, -90, 90, 10, 5, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geographic multilinestring
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] mincard, maxcard Inclusive bounds of the number of linestrings
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_multilinestring3D;
CREATE FUNCTION random_geog_multilinestring3D(lowx float, highx float,
  lowy float, highy float, lowz float, highz float, maxdelta float,
  minvertices int, maxvertices int, mincard int, maxcard int, srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_multilinestring3D(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, minvertices, maxvertices, mincard, maxcard, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_multilinestring3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geog_multilinestring3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_length(random_geog_multilinestring3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_multilinestring3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geometric multipolygon without holes
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] mincard, maxcard Inclusive bounds of the number of polygons
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_multipolygon;
CREATE FUNCTION random_geom_multipolygon(lowx float, highx float, lowy float,
    highy float, maxdelta float, minvertices int, maxvertices int, mincard int,
    maxcard int, srid int DEFAULT 0)
  RETURNS geometry AS $$
DECLARE
  result geometry[];
BEGIN
  SELECT array_agg(random_geom_polygon(lowx, highx, lowy, highy, maxdelta,
    minvertices, maxvertices, srid)) INTO result
  FROM generate_series(mincard, random_int(mincard, maxcard)) AS x;
  RETURN st_collect(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_multipolygon(-100, 100, -100, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geom_multipolygon(-100, 100, -100, 100, 10, 5, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_area(random_geom_multipolygon(-100, 100, -100, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_multipolygon(-100, 100, -100, 100, 10, 5, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geometric multipolygon without holes
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] mincard, maxcard Inclusive bounds of the number of polygons
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geom_multipolygon3D;
CREATE FUNCTION random_geom_multipolygon3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, maxdelta float, minvertices int,
  maxvertices int, mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS geometry AS $$
DECLARE
  result geometry[];
BEGIN
  SELECT array_agg(random_geom_polygon3D(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, minvertices, maxvertices, srid)) INTO result
  FROM generate_series(mincard, random_int(mincard, maxcard)) AS x;
  RETURN st_collect(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geom_multipolygon3D(-100, 100, -100, 100, 0, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geom_multipolygon3D(-100, 100, -100, 100, 0, 100, 10, 5, 10, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_area(random_geom_multipolygon3D(-100, 100, -100, 100, 0, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geom_multipolygon3D(-100, 100, -100, 100, 0, 100, 10, 5, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D geographic multipolygon without holes
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] mincard, maxcard Inclusive bounds of the number of polygons
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_multipolygon;
CREATE FUNCTION random_geog_multipolygon(lowx float, highx float, lowy float,
  highy float, maxdelta float, minvertices int, maxvertices int, mincard int,
  maxcard int, srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_multipolygon(lowx, highx, lowy, highy, maxdelta,
    minvertices, maxvertices, mincard, maxcard, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_multipolygon(-180, 180, -90, 90, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geog_multipolygon(-180, 180, -90, 90, 10, 5, 10, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_area(random_geog_multipolygon(-180, 180, -90, 90, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_multipolygon(-180, 180, -90, 90, 10, 5, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D geographic multipolygon without holes
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] maxdelta Maximum difference between two consecutive coordinate values
 * @param[in] minvertices, maxvertices Inclusive bounds of the number of vertices
 * @param[in] mincard, maxcard Inclusive bounds of the number of polygons
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_geog_multipolygon3D;
CREATE FUNCTION random_geog_multipolygon3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, maxdelta float, minvertices int,
  maxvertices int, mincard int, maxcard int, srid int DEFAULT 4326)
  RETURNS geography AS $$
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geom_multipolygon3D(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, minvertices, maxvertices, mincard, maxcard, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, st_asEWKT(random_geog_multipolygon3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_asEWKT(random_geog_multipolygon3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, st_area(random_geog_multipolygon3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_geog_multipolygon3D(-180, 180, -90, 90, 0, 100, 10, 5, 10, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------
-- Temporal Instant
-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeompoint instant
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeompoint_inst;
CREATE FUNCTION random_tgeompoint_inst(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, srid int DEFAULT 0)
  RETURNS tgeompoint AS $$
BEGIN
  IF lowtime >= hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN tgeompoint(random_geom_point(lowx, highx, lowy, highy, srid),
    random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeompoint_inst(-100, 100, -100, 100, '2001-01-01', '2002-01-01')) AS inst
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeompoint_inst(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 3812)) AS inst
FROM generate_series(1,10) k;
*/

------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeompoint instant
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeompoint3D_inst;
CREATE FUNCTION random_tgeompoint3D_inst(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, srid int DEFAULT 0)
  RETURNS tgeompoint AS $$
BEGIN
  IF lowtime >= hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN tgeompoint(random_geom_point3D(lowx, highx, lowy, highy, lowz,
    highz, srid), random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeompoint3D_inst(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01')) AS inst
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeompoint3D_inst(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 3812)) AS inst
FROM generate_series(1,10) k;
*/

------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeogpoint instant
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeogpoint_inst;
CREATE FUNCTION random_tgeogpoint_inst(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, srid int DEFAULT 4326)
  RETURNS tgeogpoint AS $$
BEGIN
  IF lowtime >= hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN tgeogpoint(random_geog_point(lowx, highx, lowy, highy, srid),
    random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeogpoint_inst(-180, 180, -90, 90, '2001-01-01', '2002-01-01')) AS inst
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeogpoint_inst(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 7844)) AS inst
FROM generate_series(1,10) k;
*/

------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeogpoint instant
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeogpoint3D_inst;
CREATE FUNCTION random_tgeogpoint3D_inst(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, srid int DEFAULT 4326)
  RETURNS tgeogpoint AS $$
BEGIN
  IF lowtime >= hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN tgeogpoint(random_geog_point3D(lowx, highx, lowy, highy, lowz,
    highz, srid), random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeogpoint3D_inst(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01')) AS inst
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeogpoint3D_inst(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 7844)) AS inst
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------
-- Temporal Discrete Sequence
-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeompoint discrete sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality sequence
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeompoint_discseq;
CREATE FUNCTION random_tgeompoint_discseq(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, maxdelta float,
  maxminutes int, mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS tgeompoint AS $$
DECLARE
  pointarr geometry[];
  tsarr timestamptz[];
  result tgeompoint[];
  card int;
BEGIN
  SELECT random_geom_point_array(lowx, highx, lowy, highy, maxdelta, mincard,
    maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card)
  INTO tsarr;
  FOR i IN 1..card
  LOOP
    result[i] = tgeompoint(pointarr[i], tsarr[i]);
  END LOOP;
  RETURN tgeompointSeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeompoint_discseq(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10))
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeompoint_discseq(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 3812))
FROM generate_series(1,10) k;

SELECT k, random_tgeompoint_discseq(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeompoint discrete sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality sequence
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeompoint3D_discseq;
CREATE FUNCTION random_tgeompoint3D_discseq(lowx float, highx float,
  lowy float, highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxdelta float, maxminutes int, mincard int, maxcard int,
  srid int DEFAULT 0)
  RETURNS tgeompoint AS $$
DECLARE
  pointarr geometry[];
  tsarr timestamptz[];
  result tgeompoint[];
  card int;
BEGIN
  SELECT random_geom_point3D_array(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, mincard, maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card)
  INTO tsarr;
  FOR i IN 1..card
  LOOP
    result[i] = tgeompoint(pointarr[i], tsarr[i]);
  END LOOP;
  RETURN tgeompointSeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeompoint3D_discseq(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10)) AS ti
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeompoint3D_discseq(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 3812)) AS ti
FROM generate_series(1,10) k;

SELECT k, random_tgeompoint3D_discseq(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10)
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeogpoint discrete sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality sequence
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeogpoint_discseq;
CREATE FUNCTION random_tgeogpoint_discseq(lowx float, highx float,
  lowy float, highy float, lowtime timestamptz, hightime timestamptz,
  maxdelta float, maxminutes int, mincard int, maxcard int, srid int DEFAULT 4326)
  RETURNS tgeogpoint AS $$
DECLARE
  pointarr geography[];
  tsarr timestamptz[];
  result tgeogpoint[];
  card int;
BEGIN
  SELECT random_geog_point_array(lowx, highx, lowy, highy, maxdelta, mincard,
    maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card)
  INTO tsarr;
  FOR i IN 1..card
  LOOP
    result[i] = tgeogpoint(pointarr[i], tsarr[i]);
  END LOOP;
  RETURN tgeogpointSeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeogpoint_discseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10))
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeogpoint_discseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 7844))
FROM generate_series(1,10) k;

SELECT k, random_tgeogpoint_discseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeogpoint discrete sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality sequence
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeogpoint3D_discseq;
CREATE FUNCTION random_tgeogpoint3D_discseq(lowx float, highx float,
  lowy float, highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxdelta float, maxminutes int, mincard int, maxcard int,
  srid int DEFAULT 4326)
  RETURNS tgeogpoint AS $$
DECLARE
  pointarr geography[];
  tsarr timestamptz[];
  result tgeogpoint[];
  card int;
BEGIN
  SELECT random_geog_point3D_array(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, mincard, maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card)
  INTO tsarr;
  FOR i IN 1..card
  LOOP
    result[i] = tgeogpoint(pointarr[i], tsarr[i]);
  END LOOP;
  RETURN tgeogpointSeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeogpoint3D_discseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10))
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeogpoint3D_discseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 7844))
FROM generate_series(1,10) k;

SELECT k, random_tgeogpoint3D_discseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------
-- Temporal Continuous Sequence
-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeompoint sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence
 * @param[in] linear True if the sequence has linear interpolation
 * @param[in] srid SRID of the coordinates
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tgeompoint_contseq;
CREATE FUNCTION random_tgeompoint_contseq(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, maxdelta float,
  maxminutes int, mincard int, maxcard int, linear bool DEFAULT true,
  srid int DEFAULT 0, fixstart bool DEFAULT false)
  RETURNS tgeompoint AS $$
DECLARE
  pointarr geometry[];
  tsarr timestamptz[];
  result tgeompoint[];
  card int;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_geom_point_array(lowx, highx, lowy, highy, maxdelta, mincard,
    maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card,
    fixstart) INTO tsarr;
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    result[i] = tgeompoint(pointarr[i], tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc AND NOT linear THEN
    result[card] = tgeompoint(pointarr[card - 1], tsarr[card]);
  ELSE
    result[card] = tgeompoint(pointarr[card], tsarr[card]);
  END IF;
  IF linear THEN
    interp = 'Linear';
  ELSE
    interp = 'Step';
  END IF;
  RETURN tgeompointSeq(result, interp, lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeompoint_contseq(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10))
FROM generate_series(1, 15) AS k;

-- Step interpolation
SELECT k, asEWKT(random_tgeompoint_contseq(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, false))
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeompoint_contseq(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, srid:=3812))
FROM generate_series(1, 15) AS k;

SELECT k, random_tgeompoint_contseq(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS seq
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeompoint sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence
 * @param[in] linear True if the sequence has linear interpolation
 * @param[in] srid SRID of the coordinates
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tgeompoint3D_contseq;
CREATE FUNCTION random_tgeompoint3D_contseq(lowx float, highx float,
  lowy float, highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxdelta float, maxminutes int, mincard int, maxcard int,
  linear bool DEFAULT true, srid int DEFAULT 0, fixstart bool DEFAULT false)
  RETURNS tgeompoint AS $$
DECLARE
  pointarr geometry[];
  tsarr timestamptz[];
  result tgeompoint[];
  card int;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_geom_point3D_array(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, mincard, maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card,
    fixstart) INTO tsarr;
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    result[i] = tgeompoint(pointarr[i], tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc AND NOT linear THEN
    result[card] = tgeompoint(pointarr[card - 1], tsarr[card]);
  ELSE
    result[card] = tgeompoint(pointarr[card], tsarr[card]);
  END IF;
  IF linear THEN
    interp = 'Linear';
  ELSE
    interp = 'Step';
  END IF;
  RETURN tgeompointSeq(result, interp, lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeompoint3D_contseq(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10))
FROM generate_series(1, 15) AS k;

-- Step interpolation
SELECT k, asEWKT(random_tgeompoint3D_contseq(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, false))
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeompoint3D_contseq(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, srid:=3812))
FROM generate_series(1, 15) AS k;

SELECT k, random_tgeompoint3D_contseq(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS seq
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeogpoint sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence
 * @param[in] linear True if the sequence has linear interpolation
 * @param[in] srid SRID of the coordinates
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tgeogpoint_contseq;
CREATE FUNCTION random_tgeogpoint_contseq(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, maxdelta float,
  maxminutes int, mincard int, maxcard int, linear bool DEFAULT true,
  srid int DEFAULT 4326, fixstart bool DEFAULT false)
  RETURNS tgeogpoint AS $$
DECLARE
  pointarr geography[];
  tsarr timestamptz[];
  result tgeogpoint[];
  card int;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_geog_point_array(lowx, highx, lowy, highy, maxdelta, mincard,
    maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card,
    fixstart) INTO tsarr;
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    result[i] = tgeogpoint(pointarr[i], tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc AND NOT linear THEN
    result[card] = tgeogpoint(pointarr[card - 1], tsarr[card]);
  ELSE
    result[card] = tgeogpoint(pointarr[card], tsarr[card]);
  END IF;
  IF linear THEN
    interp = 'Linear';
  ELSE
    interp = 'Step';
  END IF;
  RETURN tgeogpointSeq(result, interp, lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeogpoint_contseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10))
FROM generate_series(1, 15) AS k;

-- Step interpolation
SELECT k, asEWKT(random_tgeogpoint_contseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10, false))
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeogpoint_contseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10, srid:=7844))
FROM generate_series(1, 15) AS k;

SELECT k, random_tgeogpoint_contseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS seq
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeogpoint sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence
 * @param[in] linear True if the sequence has linear interpolation
 * @param[in] srid SRID of the coordinates
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tgeogpoint3D_contseq;
CREATE FUNCTION random_tgeogpoint3D_contseq(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxdelta float, maxminutes int, mincard int,
  maxcard int, linear bool DEFAULT true, srid int DEFAULT 4326,
  fixstart bool DEFAULT false)
  RETURNS tgeogpoint AS $$
DECLARE
  pointarr geography[];
  tsarr timestamptz[];
  result tgeogpoint[];
  card int;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_geog_point3D_array(lowx, highx, lowy, highy, lowz, highz,
    maxdelta, mincard, maxcard, srid) INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card,
    fixstart) INTO tsarr;
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    result[i] = tgeogpoint(pointarr[i], tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc AND NOT linear THEN
    result[card] = tgeogpoint(pointarr[card - 1], tsarr[card]);
  ELSE
    result[card] = tgeogpoint(pointarr[card], tsarr[card]);
  END IF;
  IF linear THEN
    interp = 'Linear';
  ELSE
    interp = 'Step';
  END IF;
  RETURN tgeogpointSeq(result, interp, lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeogpoint3D_contseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10))
FROM generate_series(1, 15) AS k;

-- Step interpolation
SELECT k, asEWKT(random_tgeogpoint3D_contseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, false))
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeogpoint3D_contseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, srid:=7844))
FROM generate_series(1, 15) AS k;

SELECT k, random_tgeogpoint3D_contseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10) AS seq
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------
-- Temporal Sequence Set
-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeompoint sequence set
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 * @param[in] linear True if the sequence set has linear interpolation
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeompoint_seqset;
CREATE FUNCTION random_tgeompoint_seqset(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, maxdelta float,
  maxminutes int, mincardseq int, maxcardseq int, mincard int, maxcard int,
  linear bool DEFAULT true, srid int DEFAULT 0)
  RETURNS tgeompoint AS $$
DECLARE
  result tgeompoint[];
  card int;
  seq tgeompoint;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i > 1
    SELECT random_tgeompoint_contseq(lowx, highx, lowy, highy, t1, t2, maxdelta,
      maxminutes, mincardseq, maxcardseq, linear, srid, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tgeompointSeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeompoint_seqset(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10)) AS ts
FROM generate_series(1, 15) AS k;

-- Step interpolation
SELECT k, asEWKT(random_tgeompoint_seqset(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10, false)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeompoint_seqset(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10, srid:=3812)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, random_tgeompoint_seqset(-100, 100, -100, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10) AS ts
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeompoint sequence set
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 * @param[in] linear True if the sequence set has linear interpolation
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeompoint3D_seqset;
CREATE FUNCTION random_tgeompoint3D_seqset(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxdelta float, maxminutes int, mincardseq int,
  maxcardseq int, mincard int, maxcard int, linear bool DEFAULT true,
  srid int DEFAULT 0)
  RETURNS tgeompoint AS $$
DECLARE
  result tgeompoint[];
  card int;
  seq tgeompoint;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i > 1
    SELECT random_tgeompoint3D_contseq(lowx, highx, lowy, highy, lowz, highz,
      t1, t2, maxdelta, maxminutes, mincardseq, maxcardseq, linear, srid, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tgeompointSeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeompoint3D_seqset(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10)) AS ts
FROM generate_series(1, 15) AS k;

-- Step interpolation
SELECT k, asEWKT(random_tgeompoint3D_seqset(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10, false)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeompoint3D_seqset(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10, srid:=3812)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, random_tgeompoint3D_seqset(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, numSequences(random_tgeompoint3D_seqset(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10))
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(endSequence(random_tgeompoint3D_seqset(-100, 100, -100, 100, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10))) AS ts
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeogpoint sequence set
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 * @param[in] linear True if the sequence set has linear interpolation
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeogpoint_seqset;
CREATE FUNCTION random_tgeogpoint_seqset(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, maxdelta float,
  maxminutes int, mincardseq int, maxcardseq int, mincard int, maxcard int,
  linear bool DEFAULT true, srid int DEFAULT 4326)
  RETURNS tgeogpoint AS $$
DECLARE
  result tgeogpoint[];
  card int;
  seq tgeogpoint;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i > 1
    SELECT random_tgeogpoint_contseq(lowx, highx, lowy, highy, t1, t2, maxdelta,
      maxminutes, mincardseq, maxcardseq, linear, srid, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tgeogpointSeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeogpoint_seqset(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10)) AS ts
FROM generate_series(1, 15) AS k;

-- Step interpolation
SELECT k, asEWKT(random_tgeogpoint_seqset(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10, false)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeogpoint_seqset(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10, srid:=7844)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, random_tgeogpoint_seqset(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10) AS ts
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeogpoint sequence set
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxdelta Maximum value difference between consecutive instants
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 * @param[in] linear True if the sequence set has linear interpolation
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeogpoint3D_seqset;
CREATE FUNCTION random_tgeogpoint3D_seqset(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxdelta float, maxminutes int, mincardseq int,
  maxcardseq int, mincard int, maxcard int, linear bool DEFAULT true,
  srid int DEFAULT 4326)
  RETURNS tgeogpoint AS $$
DECLARE
  result tgeogpoint[];
  card int;
  seq tgeogpoint;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i > 1
    SELECT random_tgeogpoint3D_stepseq(lowx, highx, lowy, highy, lowz, highz,
      t1, t2, maxdelta, maxminutes, mincardseq, maxcardseq, linear, srid, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tgeogpointSeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeogpoint3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10)) AS ts
FROM generate_series(1, 15) AS k;

-- Step interpolation
SELECT k, asEWKT(random_tgeogpoint3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10, false)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeogpoint3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10, srid:=7844)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, random_tgeogpoint3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, numSequences(random_tgeogpoint3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10))
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(endSequence(random_tgeogpoint3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 10, 5, 10, 5, 10))) AS ts
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------


-- Begin contents of geo/random_tgeo.sql

/*****************************************************************************
 *
 * This MobilityDB code is provided under The PostgreSQL License.
 * Copyright (c) 2016-2025, Université libre de Bruxelles and MobilityDB
 * contributors
 *
 * MobilityDB includes portions of PostGIS version 3 source code released
 * under the GNU General Public License (GPLv2 or later).
 * Copyright (c) 2001-2025, PostGIS contributors
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation FOR any purpose, without fee, and without a written
 * agreement is hereby granted, provided that the above copyright notice and
 * this paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL UNIVERSITE LIBRE DE BRUXELLES BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
 * EVEN IF UNIVERSITE LIBRE DE BRUXELLES HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * UNIVERSITE LIBRE DE BRUXELLES SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON
 * AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 *****************************************************************************/

/*
 * Basic synthetic data generator functions for temporal geo types
 */

-------------------------------------------------------------------------------

/**
 * @brief Generate a random geometry which can be (currently) a point, a 
 * linestring or a polygon
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geometry;
CREATE FUNCTION random_geometry(lowx float, highx float, lowy float,
  highy float, srid int DEFAULT 0)
  RETURNS geometry AS $$
DECLARE
  i int;
BEGIN
  i = random_int(1, 3);
  IF i = 1 THEN
    RETURN random_geom_point(lowx, highx, lowy, highy, srid);
  ELSIF i = 2 THEN
    RETURN random_geom_linestring(lowx, highx, lowy, highy, 10, 2, 2, srid);
  ELSE -- i = 0 
    RETURN random_geom_multipoint(lowx, highx, lowy, highy, 10, 2, 2, srid);
  END IF;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, ST_AsText(random_geometry(-100, 100, -100, 100)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, ST_AsEWKT(random_geometry(-100, 100, -100, 100, 3812)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random geometry which can be (currently) a point, a 
 * linestring or a polygon
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geometry3D;
CREATE FUNCTION random_geometry3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, srid int DEFAULT 0)
  RETURNS geometry AS $$
DECLARE
  i int;
BEGIN
  i = random_int(1, 3);
  IF i = 1 THEN
    RETURN random_geom_point3D(lowx, highx, lowy, highy, lowz, highz, srid);
  ELSIF i = 2 THEN
    RETURN random_geom_linestring3D(lowx, highx, lowy, highy, lowz, highz,
      10, 2, 2, srid);
  ELSE -- i = 0 
    RETURN random_geom_multipoint3D(lowx, highx, lowy, highy, lowz, highz,
      10, 2, 2, srid);
  END IF;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, ST_AsText(random_geometry3D(-100, 100, -100, 100, -100, 100)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, ST_AsEWKT(random_geometry3D(-100, 100, -100, 100, -100, 100, 3812)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random geography which can be (currently) a point, a 
 * linestring or a polygon
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geography;
CREATE FUNCTION random_geography(lowx float, highx float, lowy float,
  highy float, srid int DEFAULT 4326)
  RETURNS geography AS $$
DECLARE
  i int;
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geometry(lowx, highx, lowy, highy, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, ST_AsText(random_geography(-180, 180, -90, 90)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, ST_AsEWKT(random_geography(-180, 180, -90, 90, 7844)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random geography which can be (currently) a point, a 
 * linestring or a polygon
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geography3D;
CREATE FUNCTION random_geography3D(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, srid int DEFAULT 4326)
  RETURNS geography AS $$
DECLARE
  i int;
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  RETURN random_geometry3D(lowx, highx, lowy, highy, lowz, highz, srid)::geography;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, ST_AsEWKT(random_geography3D(-180, 180, -90, 90, 0, 100)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, ST_AsEWKT(random_geography3D(-180, 180, -90, 90, 0, 100, 7844)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random geometries
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geometry_array;
CREATE FUNCTION random_geometry_array(lowx float, highx float, lowy float,
  highy float, mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS geometry[] AS $$
DECLARE
  result geometry[];
  card int;
BEGIN
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  card = random_int(mincard, maxcard);
  FOR i IN 1..card
  LOOP
    IF mod(i, 3) = 1 THEN
      result[i] = random_geom_point(lowx, highx, lowy, highy, srid);
    ELSIF mod(i, 3) = 2 THEN
      result[i] = random_geom_linestring(lowx, highx, lowy, highy, 10, 2, 2, srid);
    ELSE -- mod(i, 3) = 0 
      result[i] = random_geom_multipoint(lowx, highx, lowy, highy, 10, 2, 2, srid);
    END IF;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_geometry_array(-100, 100, -100, 100, 1, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asText(random_geometry_array(-100, 100, -100, 100, 1, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random geometries
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geometry3D_array;
CREATE FUNCTION random_geometry3D_array(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, mincard int, maxcard int,
  srid int DEFAULT 0)
  RETURNS geometry[] AS $$
DECLARE
  result geometry[];
  card int;
BEGIN
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  card = random_int(mincard, maxcard);
  FOR i IN 1..card
  LOOP
    IF mod(i, 3) = 1 THEN
      result[i] = random_geom_point3D(lowx, highx, lowy, highy, lowz, highz, srid);
    ELSIF mod(i, 3) = 2 THEN
      result[i] = random_geom_linestring3D(lowx, highx, lowy, highy, 
        lowz, highz, 10, 2, 2, srid);
    ELSE -- mod(i, 3) = 0 
      result[i] = random_geom_multipoint3D(lowx, highx, lowy, highy, 
        lowz, highz, 10, 2, 2, srid);
    END IF;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_geometry3D_array(-100, 100, -100, 100, 0, 100, 1, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asText(random_geometry3D_array(-100, 100, -100, 100, 0, 100, 1, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random geographies
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geography_array;
CREATE FUNCTION random_geography_array(lowx float, highx float, lowy float,
  highy float, mincard int, maxcard int, srid int DEFAULT 4326)
  RETURNS geography[] AS $$
DECLARE
  geomresult geometry[];
  result geometry[];
  i int;
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  geomresult = random_geometry_array(lowx, highx, lowy, highy, mincard, 
    maxcard, srid);
  FOR i IN 1..array_length(geomresult, 1)
  LOOP
    result[i] = geomresult[i]::geography;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_geography_array(-180, 180, -90, 90, 1, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asText(random_geography_array(-180, 180, -90, 90, 1, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random geographies
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geography3D_array;
CREATE FUNCTION random_geography3D_array(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, mincard int, maxcard int,
  srid int DEFAULT 4326)
  RETURNS geography[] AS $$
DECLARE
  geomresult geometry[];
  result geometry[];
  i int;
BEGIN
  IF lowx < -180 OR highx > 180 OR lowy < -90 OR highy > 90 THEN
    RAISE EXCEPTION 'Geography coordinates must be in the range [-180 -90, 180 90]';
  END IF;
  geomresult = random_geometry3D_array(lowx, highx, lowy, highy, lowz, highz, 
    mincard, maxcard, srid);
  FOR i IN 1..array_length(geomresult, 1)
  LOOP
    result[i] = geomresult[i]::geography;
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_geography3D_array(-180, 180, -90, 90, 0, 100, 1, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asText(random_geography3D_array(-180, 180, -90, 90, 0, 100, 1, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a set of random geometries
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the set
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geometry_set;
CREATE FUNCTION random_geometry_set(lowx float, highx float, lowy float,
  highy float, mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS geomset AS $$
BEGIN
  RETURN set(random_geometry_array(lowx, highx, lowy, highy,
      mincard, maxcard, srid));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_geometry_set(1, 100, 1, 100, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_geometry_set(1, 100, 1, 100, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a set of random geometries
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the set
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geometry3D_set;
CREATE FUNCTION random_geometry3D_set(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, mincard int, maxcard int,
  srid int DEFAULT 0)
  RETURNS geomset AS $$
BEGIN
  RETURN set(random_geometry3D_array(lowx, highx, lowy, highy, lowz, highz,
      mincard, maxcard, srid));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_geometry3D_set(1, 100, 1, 100, 1, 100, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_geometry3D_set(1, 100, 1, 100, 1, 100, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a set of random geographies
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the set
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geography_set;
CREATE FUNCTION random_geography_set(lowx float, highx float, lowy float,
  highy float, mincard int, maxcard int, srid int DEFAULT 4326)
  RETURNS geogset AS $$
BEGIN
  RETURN set(random_geography_array(lowx, highx, lowy, highy,
    mincard, maxcard, srid));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_geography_set(-180, 180, -90, 90, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_geography_set(-180, 180, -90, 90, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a set of random geographies
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the set
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_geography3D_set;
CREATE FUNCTION random_geography3D_set(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, mincard int, maxcard int,
  srid int DEFAULT 4326)
  RETURNS geogset AS $$
BEGIN
  RETURN set(random_geography3D_array(lowx, highx, lowy, highy, lowz, highz,
    mincard, maxcard, srid));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_geography3D_set(-180, 180, -90, 90, 0, 100, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_geography3D_set(-180, 180, -90, 90, 0, 100, 5, 10, 7844)) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------
-- Temporal Instant
------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal geometry of instant subtype
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_tgeometry_inst;
CREATE FUNCTION random_tgeometry_inst(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, srid int DEFAULT 0)
  RETURNS tgeometry AS $$
BEGIN
  RETURN tgeometry(random_geometry(lowx, highx, lowy, highy, srid), 
    random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_tgeometry_inst(1, 100, 1, 100, '2001-01-01',
  '2001-12-31')) AS inst
FROM generate_series(1,10) k;
*/

------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal geometry of instant subtype
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_tgeometry3D_inst;
CREATE FUNCTION random_tgeometry3D_inst(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz, 
  hightime timestamptz, srid int DEFAULT 0)
  RETURNS tgeometry AS $$
BEGIN
  RETURN tgeometry(random_geometry3D(lowx, highx, lowy, highy, lowz, highz, srid), 
    random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_tgeometry3D_inst(1, 100, 1, 100, 1, 100, '2001-01-01',
  '2001-12-31')) AS inst
FROM generate_series(1,10) k;
*/

------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeography instant
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeography_inst;
CREATE FUNCTION random_tgeography_inst(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, srid int DEFAULT 4326)
  RETURNS tgeography AS $$
BEGIN
  IF lowtime >= hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN tgeography(random_geography(lowx, highx, lowy, highy, srid),
    random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeography_inst(-180, 180, -90, 90, '2001-01-01', '2002-01-01')) AS inst
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeography_inst(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 7844)) AS inst
FROM generate_series(1,10) k;
*/

------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeography instant
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeography3D_inst;
CREATE FUNCTION random_tgeography3D_inst(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, srid int DEFAULT 4326)
  RETURNS tgeography AS $$
BEGIN
  IF lowtime >= hightime THEN
    RAISE EXCEPTION 'lowtime must be less than or equal to hightime: %, %',
      lowtime, hightime;
  END IF;
  RETURN tgeography(random_geography3D(lowx, highx, lowy, highy, lowz,
    highz, srid), random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeography3D_inst(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01')) AS inst
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeography3D_inst(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 7844)) AS inst
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------
-- Temporal Discrete Sequence
-------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal geo of discrete sequence subtype
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the number of instants
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_tgeometry_discseq;
CREATE FUNCTION random_tgeometry_discseq(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, maxminutes int, 
  mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS tgeometry AS $$
DECLARE
  result tgeometry[];
  card int;
  t timestamptz;
BEGIN
  card = random_int(1, maxcard);
  t = random_timestamptz(lowtime, hightime);
  FOR i IN 1..card
  LOOP
    result[i] = tgeometry(random_geometry(lowx, highx, lowy, highy,
      srid), t);
    t = t + random_minutes(1, maxminutes);
  END LOOP;
  RETURN tgeometrySeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_tgeometry_discseq(1, 100, 1, 100,
  '2001-01-01', '2001-12-31', 10, 1, 10)) AS ti
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeometry_discseq(1, 100, 1, 100,
  '2001-01-01', '2001-12-31', 10, 1, 10, 3812)) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal geo of discrete sequence subtype
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the number of instants
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_tgeometry3D_discseq;
CREATE FUNCTION random_tgeometry3D_discseq(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz, 
  hightime timestamptz, maxminutes int, mincard int, maxcard int, 
  srid int DEFAULT 0)
  RETURNS tgeometry AS $$
DECLARE
  result tgeometry[];
  card int;
  t timestamptz;
BEGIN
  card = random_int(1, maxcard);
  t = random_timestamptz(lowtime, hightime);
  FOR i IN 1..card
  LOOP
    result[i] = tgeometry(random_geometry3D(lowx, highx, lowy, highy, lowz, 
      highz, srid), t);
    t = t + random_minutes(1, maxminutes);
  END LOOP;
  RETURN tgeometrySeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_tgeometry3D_discseq(1, 100, 1, 100, 1, 100,
  '2001-01-01', '2001-12-31', 10, 1, 10)) AS ti
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeometry3D_discseq(1, 100, 1, 100, 1, 100,
  '2001-01-01', '2001-12-31', 10, 1, 10, 3812)) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeography discrete sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality sequence
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeography_discseq;
CREATE FUNCTION random_tgeography_discseq(lowx float, highx float,
  lowy float, highy float, lowtime timestamptz, hightime timestamptz,
  maxminutes int, mincard int, maxcard int, srid int DEFAULT 4326)
  RETURNS tgeography AS $$
DECLARE
  pointarr geography[];
  tsarr timestamptz[];
  result tgeography[];
  card int;
BEGIN
  SELECT random_geography_array(lowx, highx, lowy, highy, mincard,
    maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card)
  INTO tsarr;
  FOR i IN 1..card
  LOOP
    result[i] = tgeography(pointarr[i], tsarr[i]);
  END LOOP;
  RETURN tgeographySeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeography_discseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 5, 10))
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeography_discseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 5, 10, 7844))
FROM generate_series(1,10) k;

SELECT k, random_tgeography_discseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 5, 10) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeography discrete sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality sequence
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeography3D_discseq;
CREATE FUNCTION random_tgeography3D_discseq(lowx float, highx float,
  lowy float, highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxminutes int, mincard int, maxcard int,
  srid int DEFAULT 4326)
  RETURNS tgeography AS $$
DECLARE
  pointarr geography[];
  tsarr timestamptz[];
  result tgeography[];
  card int;
BEGIN
  SELECT random_geography3D_array(lowx, highx, lowy, highy, lowz, highz,
    mincard, maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card)
  INTO tsarr;
  FOR i IN 1..card
  LOOP
    result[i] = tgeography(pointarr[i], tsarr[i]);
  END LOOP;
  RETURN tgeographySeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeography3D_discseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 5, 10))
FROM generate_series(1,10) k;

SELECT k, asEWKT(random_tgeography3D_discseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 5, 10, 7844))
FROM generate_series(1,10) k;

SELECT k, random_tgeography3D_discseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 5, 10) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------
-- Temporal Step Sequence
-------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal geo of sequence subtype
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the number of instants
 * @param[in] srid Optional SRID
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tgeometry_stepseq;
CREATE FUNCTION random_tgeometry_stepseq(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, maxminutes int, 
  mincard int, maxcard int, srid int DEFAULT 0, fixstart bool DEFAULT false)
  RETURNS tgeometry AS $$
DECLARE
  tsarr timestamptz[];
  result tgeometry[];
  card int;
  t1 timestamptz;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, mincard,
    maxcard, fixstart) INTO tsarr;
  card = array_length(tsarr, 1);
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    result[i] = tgeometry(random_geometry(lowx, highx, lowy, highy, srid), 
      tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc THEN
    result[card] = tgeometry(getValue(result[card - 1]), tsarr[card]);
  ELSE
    result[card] = tgeometry(random_geometry(lowx, highx, lowy, highy,
      srid), tsarr[card]);
  END IF;
  RETURN tgeometrySeq(result, 'Step', lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_tgeometry_stepseq(1, 100, 1, 100,
  '2001-01-01', '2001-12-31', 10, 10, 10)) AS seq
FROM generate_series (1, 15) AS k;

WITH temp AS (
  SELECT k, random_tgeometry_stepseq(1, 100, 1, 100,
    '2001-01-01', '2001-12-31', 10, 10, 10) AS seq
  FROM generate_series (1, 15) AS k )
SELECT DISTINCT srid(seq) FROM temp;

WITH temp AS (
  SELECT k, random_tgeometry_stepseq(1, 100, 1, 100,
    '2001-01-01', '2001-12-31', 10, 10, 10, 3812) AS seq
  FROM generate_series (1, 15) AS k )
SELECT DISTINCT srid(seq) FROM temp;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal geo of sequence subtype
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the number of instants
 * @param[in] srid Optional SRID
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 * @note Type tgeometry does not accept linear interpolation
 */
DROP FUNCTION IF EXISTS random_tgeometry3D_stepseq;
CREATE FUNCTION random_tgeometry3D_stepseq(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz, 
  hightime timestamptz, maxminutes int, mincard int, maxcard int, 
  srid int DEFAULT 0, fixstart bool DEFAULT false)
  RETURNS tgeometry AS $$
DECLARE
  tsarr timestamptz[];
  result tgeometry[];
  card int;
  t1 timestamptz;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, mincard,
    maxcard, fixstart) INTO tsarr;
  card = array_length(tsarr, 1);
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    result[i] = tgeometry(random_geometry3D(lowx, highx, lowy, highy, lowz, 
      highz, srid), tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc THEN
    result[card] = tgeometry(getValue(result[card - 1]), tsarr[card]);
  ELSE
    result[card] = tgeometry(random_geometry3D(lowx, highx, lowy, highy, 
      lowz, highz, srid), tsarr[card]);
  END IF;
  RETURN tgeometrySeq(result, 'Step', lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_tgeometry3D_stepseq(1, 100, 1, 100, 1, 100,
  '2001-01-01', '2001-12-31', 10, 10, 10)) AS seq
FROM generate_series (1, 15) AS k;

WITH temp AS (
  SELECT k, random_tgeometry3D_stepseq(1, 100, 1, 100, 1, 100,
    '2001-01-01', '2001-12-31', 10, 10, 10) AS seq
  FROM generate_series (1, 15) AS k )
SELECT DISTINCT srid(seq) FROM temp;

WITH temp AS (
  SELECT k, random_tgeometry3D_stepseq(1, 100, 1, 100, 1, 100,
    '2001-01-01', '2001-12-31', 10, 10, 10, 3812) AS seq
  FROM generate_series (1, 15) AS k )
SELECT DISTINCT srid(seq) FROM temp;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeography sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence
 * @param[in] srid SRID of the coordinates
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tgeography_stepseq;
CREATE FUNCTION random_tgeography_stepseq(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, maxminutes int, 
  mincard int, maxcard int, srid int DEFAULT 4326, 
  fixstart bool DEFAULT false)
  RETURNS tgeography AS $$
DECLARE
  pointarr geography[];
  tsarr timestamptz[];
  result tgeography[];
  card int;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_geography_array(lowx, highx, lowy, highy, mincard,
    maxcard, srid)
  INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card,
    fixstart) INTO tsarr;
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    result[i] = tgeography(pointarr[i], tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc THEN
    result[card] = tgeography(pointarr[card - 1], tsarr[card]);
  ELSE
    result[card] = tgeography(pointarr[card], tsarr[card]);
  END IF;
  RETURN tgeographySeq(result, 'Step', lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeography_stepseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 5, 10))
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeography_stepseq(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 5, 10, 7844))
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeography sequence
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence
 * @param[in] srid SRID of the coordinates
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tgeography3D_stepseq;
CREATE FUNCTION random_tgeography3D_stepseq(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxminutes int, mincard int, maxcard int, 
  srid int DEFAULT 4326, fixstart bool DEFAULT false)
  RETURNS tgeography AS $$
DECLARE
  pointarr geography[];
  tsarr timestamptz[];
  result tgeography[];
  card int;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_geography3D_array(lowx, highx, lowy, highy, lowz, highz,
    mincard, maxcard, srid) INTO pointarr;
  card = array_length(pointarr, 1);
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, card, card,
    fixstart) INTO tsarr;
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  FOR i IN 1..card - 1
  LOOP
    result[i] = tgeography(pointarr[i], tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc THEN
    result[card] = tgeography(pointarr[card - 1], tsarr[card]);
  ELSE
    result[card] = tgeography(pointarr[card], tsarr[card]);
  END IF;
  RETURN tgeographySeq(result, 'Step', lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeography3D_stepseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 5, 10))
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeography3D_stepseq(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 5, 10, 7844))
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------
-- Temporal Sequence Set
-------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal geo of sequence set subtype
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the number of instants 
 *   in a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the number of sequences
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_tgeometry_seqset;
CREATE FUNCTION random_tgeometry_seqset(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, maxminutes int, 
  mincardseq int, maxcardseq int, mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS tgeometry AS $$
DECLARE
  result tgeometry[];
  card int;
  seq tgeometry;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i > 1
    SELECT random_tgeometry_stepseq(lowx, highx, lowy, highy,
      t1, t2, maxminutes, mincardseq, maxcardseq, srid, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tgeometrySeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_tgeometry_seqset(1, 100, 1, 100,
  '2001-01-01', '2001-12-31', 10, 1, 10, 1, 10)) AS seqset
FROM generate_series (1, 15) AS k;

SELECT k, asEWKT(random_tgeometry_seqset(1, 100, 1, 100,
  '2001-01-01', '2001-12-31', 10, 1, 10, 1, 10, 3812)) AS seqset
FROM generate_series (1, 15) AS k;

WITH temp AS (
  SELECT k, random_tgeometry_seqset(1, 100, 1, 100,
    '2001-01-01', '2001-12-31', 10, 1, 10, 1, 10, 3812) AS seqset
  FROM generate_series (1, 15) AS k )
SELECT DISTINCT srid(seqset) FROM temp;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal geo of sequence set subtype
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the number of instants 
 *   in a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the number of sequences
 * @param[in] srid Optional SRID
 */
DROP FUNCTION IF EXISTS random_tgeometry3D_seqset;
CREATE FUNCTION random_tgeometry3D_seqset(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz, 
  hightime timestamptz, maxminutes int, mincardseq int, maxcardseq int, 
  mincard int, maxcard int, srid int DEFAULT 0)
  RETURNS tgeometry AS $$
DECLARE
  result tgeometry[];
  card int;
  seq tgeometry;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i > 1
    SELECT random_tgeometry3D_stepseq(lowx, highx, lowy, highy, lowz, highz,
      t1, t2, maxminutes, mincardseq, maxcardseq, srid, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tgeometrySeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asText(random_geometry3D_seqset(1, 100, 1, 100, 1, 100,
  '2001-01-01', '2001-12-31', 10, 1, 10, 1, 10)) AS seqset
FROM generate_series (1, 15) AS k;

SELECT k, asEWKT(random_geometry3D_seqset(1, 100, 1, 100, 1, 100,
  '2001-01-01', '2001-12-31', 10, 1, 10, 1, 10, 3812)) AS seqset
FROM generate_series (1, 15) AS k;

WITH temp AS (
  SELECT k, random_geometry3D_seqset(1, 100, 1, 100, 1, 100,
    '2001-01-01', '2001-12-31', 10, 1, 10, 1, 10, 3812) AS seqset
  FROM generate_series (1, 15) AS k )
SELECT DISTINCT srid(seqset) FROM temp;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 2D tgeography sequence set
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeography_seqset;
CREATE FUNCTION random_tgeography_seqset(lowx float, highx float, lowy float,
  highy float, lowtime timestamptz, hightime timestamptz, maxminutes int, 
  mincardseq int, maxcardseq int, mincard int, maxcard int, 
  srid int DEFAULT 4326)
  RETURNS tgeography AS $$
DECLARE
  result tgeography[];
  card int;
  seq tgeography;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i > 1
    SELECT random_tgeography_stepseq(lowx, highx, lowy, highy, t1, t2, 
      maxminutes, mincardseq, maxcardseq, srid, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tgeographySeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeography_seqset(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 5, 10, 5, 10)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeography_seqset(-180, 180, -90, 90, '2001-01-01', '2002-01-01', 10, 5, 10, 5, 10, 7844)) AS ts
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random 3D tgeography sequence set
 * @param[in] lowx, highx Inclusive bounds of the range for the x coordinates
 * @param[in] lowy, highy Inclusive bounds of the range for the y coordinates
 * @param[in] lowz, highz Inclusive bounds of the range for the z coordinates
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the cardinality of a sequence
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the sequence set
 * @param[in] srid SRID of the coordinates
 */
DROP FUNCTION IF EXISTS random_tgeography3D_seqset;
CREATE FUNCTION random_tgeography3D_seqset(lowx float, highx float, lowy float,
  highy float, lowz float, highz float, lowtime timestamptz,
  hightime timestamptz, maxminutes int, mincardseq int, maxcardseq int, 
  mincard int, maxcard int, srid int DEFAULT 4326)
  RETURNS tgeography AS $$
DECLARE
  result tgeography[];
  card int;
  seq tgeography;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i > 1
    SELECT random_tgeography3D_stepseq(lowx, highx, lowy, highy, lowz, highz,
      t1, t2, maxminutes, mincardseq, maxcardseq, srid, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tgeographySeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_tgeography3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 5, 10, 5, 10)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_tgeography3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 5, 10, 5, 10, 7844)) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, random_tgeography3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 5, 10, 5, 10) AS ts
FROM generate_series(1, 15) AS k;

SELECT k, numSequences(random_tgeography3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 5, 10, 5, 10))
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(endSequence(random_tgeography3D_seqset(-180, 180, -90, 90, 0, 100, '2001-01-01', '2002-01-01', 10, 5, 10, 5, 10))) AS ts
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------


-- Begin contents of npoint/random_tnpoint.sql

/*****************************************************************************
 *
 * This MobilityDB code is provided under The PostgreSQL License.
 * Copyright (c) 2016-2025, Université libre de Bruxelles and MobilityDB
 * contributors
 *
 * MobilityDB includes portions of PostGIS version 3 source code released
 * under the GNU General Public License (GPLv2 or later).
 * Copyright (c) 2001-2025, PostGIS contributors
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without a written
 * agreement is hereby granted, provided that the above copyright notice and
 * this paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL UNIVERSITE LIBRE DE BRUXELLES BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
 * EVEN IF UNIVERSITE LIBRE DE BRUXELLES HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * UNIVERSITE LIBRE DE BRUXELLES SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON
 * AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 *****************************************************************************/

/*
 * random_tnpoint.sql
 * Basic synthetic data generator functions FOR network point types
 * and temporal network point types.
 */

------------------------------------------------------------------------------
-- Ways table
------------------------------------------------------------------------------

DROP TABLE IF EXISTS ways;
CREATE TABLE ways(
  gid bigint PRIMARY KEY,
  the_geom geometry,
  length float);
INSERT INTO ways
SELECT k, random_geom_linestring(0, 100, 0, 100, 10, 2, 10, 0)
FROM generate_series(1, 100) AS k;
UPDATE ways
SET length = ST_Length(the_geom);

------------------------------------------------------------------------------
-- Static Network Types
------------------------------------------------------------------------------

/**
 * @brief Generate a random fraction between in the range [0,1]
 */
DROP FUNCTION IF EXISTS random_fraction;
CREATE FUNCTION random_fraction()
  RETURNS float AS $$
BEGIN
  RETURN random();
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_fraction() AS f
FROM generate_series(1,10) k;
*/

/**
 * @brief Generate a random network point
 * @param[in] lown, highn Inclusive bounds of the range for the identifier of
 * the network point
 */
DROP FUNCTION IF EXISTS random_npoint;
CREATE FUNCTION random_npoint(lown integer, highn integer)
  RETURNS npoint AS $$
BEGIN
  RETURN npoint(random_int(lown, highn), random_fraction());
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_npoint(1, 100) AS g
FROM generate_series(1,10) k;
*/

/**
 * @brief Generate a random network segment
 * @param[in] lown, highn Inclusive bounds of the range for the identifier of
 * the network point
 */
DROP FUNCTION IF EXISTS random_nsegment;
CREATE FUNCTION random_nsegment(lown integer, highn integer)
  RETURNS nsegment AS $$
DECLARE
  random1 float;
  random2 float;
  tmp float;
BEGIN
  random1 = random_fraction();
  random2 = random_fraction();
  IF random1 > random2 THEN
    tmp = random1;
    random1 = random2;
    random2 = tmp;
  END IF;
  RETURN nsegment(random_int(lown, highn), random1, random2);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_nsegment(1, 100) AS g
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random network points
 * @param[in] lown, highn Inclusive bounds of the range for the rid
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_npoint_array;
CREATE FUNCTION random_npoint_array(lown integer, highn integer, mincard int,
    maxcard int)
  RETURNS npoint[] AS $$
DECLARE
  result npoint[];
  card int;
BEGIN
  IF lown > highn THEN
    RAISE EXCEPTION 'lown must be less than or equal to highn: %, %',
      lowy, highy;
  END IF;
  IF mincard > maxcard THEN
    RAISE EXCEPTION 'mincard must be less than or equal to maxcard: %, %',
      mincard, maxcard;
  END IF;
  card = random_int(mincard, maxcard);
  FOR i IN 1..card
  LOOP
    result[i] = random_npoint(lown, highn);
  END LOOP;
  RETURN result;
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_npoint_array(1, 100, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate an array of random network points
 * @param[in] lown, highn Inclusive bounds of the range for the rid
 * @param[in] mincard, maxcard Inclusive bounds of the cardinality of the array
 */
DROP FUNCTION IF EXISTS random_npoint_set;
CREATE FUNCTION random_npoint_set(lown integer, highn integer, mincard int,
    maxcard int)
  RETURNS npointset AS $$
DECLARE
  nparr npoint[];
BEGIN
  RETURN set(random_npoint_array(lown, highn, mincard, maxcard));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, asEWKT(random_npoint_set(1, 100, 5, 10)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, asEWKT(random_npoint_set(1, 100, 5, 10, 3812)) AS g
FROM generate_series(1, 15) AS k;

SELECT k, random_npoint_set(1, 100, 5, 10) AS g
FROM generate_series(1, 15) AS k;
*/

------------------------------------------------------------------------------
-- Temporal Network Types
------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal network point of instant subtype
 * @param[in] lown, highn Inclusive bounds of the range for the identifier of
 * the network point
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 */
DROP FUNCTION IF EXISTS random_tnpoint_inst;
CREATE FUNCTION random_tnpoint_inst(lown integer, highn integer,
  lowtime timestamptz, hightime timestamptz)
  RETURNS tnpoint AS $$
BEGIN
  RETURN tnpoint(random_npoint(lown, highn),
    random_timestamptz(lowtime, hightime));
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tnpoint_inst(1, 100, '2001-01-01', '2001-12-31') AS inst
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal network point of discrete sequence subtype
 * @param[in] lown, highn Inclusive bounds of the range for the identifier of
 * the network point
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the number of instants
 */
DROP FUNCTION IF EXISTS random_tnpoint_discseq;
CREATE FUNCTION random_tnpoint_discseq(lown integer, highn integer,
  lowtime timestamptz, hightime timestamptz, maxminutes int, mincard int,
  maxcard int)
  RETURNS tnpoint AS $$
DECLARE
  result tnpoint[];
  card int;
  t timestamptz;
BEGIN
  card = random_int(1, maxcard);
  t = random_timestamptz(lowtime, hightime);
  FOR i IN 1..card
  LOOP
    result[i] = tnpoint(random_npoint(lown, highn), t);
    t = t + random_minutes(1, maxminutes);
  END LOOP;
  RETURN tnpointSeq(result, 'Discrete');
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tnpoint_discseq(1, 100, '2001-01-01', '2001-12-31', 10, 10, 10) AS ti
FROM generate_series(1,10) k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal network point of sequence subtype
 * @param[in] lown, highn Inclusive bounds of the range for the identifier of
 * the network point
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincard, maxcard Inclusive bounds of the number of instants
 * @param[in] linear True when the sequence has linear interpolation
 * @param[in] fixstart True when this function is called for generating a
 *    sequence set value and in this case the start timestamp is already fixed
 */
DROP FUNCTION IF EXISTS random_tnpoint_contseq;
CREATE FUNCTION random_tnpoint_contseq(lown integer, highn integer,
  lowtime timestamptz, hightime timestamptz, maxminutes int, mincard int,
  maxcard int, linear bool DEFAULT true, fixstart bool DEFAULT false)
  RETURNS tnpoint AS $$
DECLARE
  tsarr timestamptz[];
  result tnpoint[];
  card int;
  rid int;
  t1 timestamptz;
  interp text;
  lower_inc boolean;
  upper_inc boolean;
BEGIN
  SELECT random_timestamptz_array(lowtime, hightime, maxminutes, mincard,
    maxcard, fixstart) INTO tsarr;
  card = array_length(tsarr, 1);
  IF card = 1 THEN
    lower_inc = true;
    upper_inc = true;
  ELSE
    lower_inc = random() > 0.5;
    upper_inc = random() > 0.5;
  END IF;
  rid = random_int(lown, highn);
  FOR i IN 1..card - 1
  LOOP
    result[i] = tnpoint(npoint(rid, random()), tsarr[i]);
  END LOOP;
  -- Sequences with step interpolation and exclusive upper bound must have
  -- the same value in the last two instants
  IF card <> 1 AND NOT upper_inc AND NOT linear THEN
    result[card] = tnpoint(getValue(result[card - 1]), tsarr[card]);
  ELSE
    result[card] = tnpoint(npoint(rid, random()), tsarr[card]);
  END IF;
  IF linear THEN
    interp = 'Linear';
  ELSE
    interp = 'Step';
  END IF;
  RETURN tnpointSeq(result, interp, lower_inc, upper_inc);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tnpoint_contseq(1, 100, '2001-01-01', '2001-12-31', 10, 10, 10)
FROM generate_series (1, 15) AS k;

SELECT k, random_tnpoint_contseq(1, 100, '2001-01-01', '2001-12-31', 10, 10, 10, false)
FROM generate_series (1, 15) AS k;
*/

-------------------------------------------------------------------------------

/**
 * @brief Generate a random temporal network point of sequence set subtype
 * @param[in] lown, highn Inclusive bounds of the range for the identifier of
 * the network point
 * @param[in] lowtime, hightime Inclusive bounds of the tstzspan
 * @param[in] maxminutes Maximum number of minutes between consecutive instants
 * @param[in] mincardseq, maxcardseq Inclusive bounds of the number of instants in a
 * sequence
 * @param[in] mincard, maxcard Inclusive bounds of the number of sequences
 * @param[in] linear True when the sequence set has linear interpolation
 */
DROP FUNCTION IF EXISTS random_tnpoint_seqset;
CREATE FUNCTION random_tnpoint_seqset(lown integer, highn integer,
  lowtime timestamptz, hightime timestamptz, maxminutes int,
  mincardseq int, maxcardseq int, mincard int, maxcard int,
  linear bool DEFAULT true)
  RETURNS tnpoint AS $$
DECLARE
  result tnpoint[];
  card int;
  seq tnpoint;
  t1 timestamptz;
  t2 timestamptz;
BEGIN
  PERFORM tsequenceset_valid_duration(lowtime, hightime, maxminutes, mincardseq,
    maxcardseq, mincard, maxcard);
  card = random_int(mincard, maxcard);
  t1 = lowtime;
  t2 = hightime - interval '1 minute' *
    ( (maxminutes * (maxcardseq - mincardseq) * (maxcard - mincard)) +
    ((maxcard - mincard) * maxminutes) );
  FOR i IN 1..card
  LOOP
    -- the last parameter (fixstart) is set to true for all i except 1
    SELECT random_tnpoint_contseq(lown, highn, t1, t2, maxminutes, mincardseq,
      maxcardseq, linear, i > 1) INTO seq;
    result[i] = seq;
    t1 = endTimestamp(seq) + random_minutes(1, maxminutes);
    t2 = t2 + interval '1 minute' * maxminutes * (1 + maxcardseq - mincardseq);
  END LOOP;
  RETURN tnpointSeqSet(result);
END;
$$ LANGUAGE PLPGSQL STRICT;

/*
SELECT k, random_tnpoint_seqset(1, 100, '2001-01-01', '2001-12-31', 10, 10, 10, 10, 10) AS seqset
FROM generate_series (1, 15) AS k;

SELECT k, random_tnpoint_seqset(1, 100, '2001-01-01', '2001-12-31', 10, 10, 10, 10, 10, false) AS seqset
FROM generate_series (1, 15) AS k;
*/

-------------------------------------------------------------------------------

