{*
 *******************************************************************************
 *
 *  Copyright 2025 RIEGL Laser Measurement Systems
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  SPDX-License-Identifier: Apache-2.0
 *
 *******************************************************************************
 *}
{*!
 *******************************************************************************
 *
 * \file    riegl.rdb.pointcloud.pointAttributes.pas
 * \author  RIEGL LMS GmbH, Austria
 * \brief   Manage point attributes (Pascal wrapper code)
 * \version 2015-10-14/AW: Initial version
 * \version 2016-01-04/AW: Added function to duplicate point attribute data
 * \version 2016-01-11/AW: Added function to discard point attribute data
 * \version 2016-11-29/AW: Added function to define a RIEGL point attribute
 * \version 2017-03-23/AW: Added function to modify point attribute details
 * \version 2017-11-09/AW: Added function to merge point attribute details
 * \version 2019-02-15/AW: Fix C++ API wrapper of PointAttribute class
 * \version 2019-04-15/AW: Added function to query point attribute group (#3342)
 * \version 2020-12-02/AW: put() accepts name, title, description & unit (#3750)
 * \version 2022-09-20/AW: Added function to query attributes of filter (#4349)
 *
 *******************************************************************************
 *}

unit riegl.rdb.pointcloud.pointAttributes;

{******************************************************************************}
{***} INTERFACE {**************************************************************}
{******************************************************************************}

//---< INCLUDES >---------------------------------------------------------------

uses
  Classes,
  Contnrs,
  riegl.rdb,
  riegl.rdb.context,
  riegl.rdb.pointcloud.dataTypes,
  riegl.rdb.pointcloud.pointAttribute;

//---< TYPE DEFINITIONS >-------------------------------------------------------

type
  TRDBPointcloudPointID = TRDBUInt64;

//---< RDB LIBRARY IMPORTS >----------------------------------------------------

//______________________________________________________________________________
{*!
 * \brief Query attribute names
 *
 * This function returns the names of the attributes defined in the
 * database in the order defined by the point attribute group table
 * (details see rdb_pointcloud_point_attributes_group() function).
 *
 * \see rdb_pointcloud_point_attributes_group()
 *}
function rdb_pointcloud_point_attributes_list(
  Context    : TRDBContextHandle;    //!< [in] library context
  Pointcloud : TRDBPointcloudHandle; //!< [in] point cloud handle
  var Count  : TRDBUInt32;           //!< [out] number of names
  var List   : TRDBString            //!< [out] list of names, separated by \0
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Query default attribute names
 * \returns a list of built-in RIEGL default attribute names
 * \note the order of attribute names is undefined
 *}
function rdb_pointcloud_point_attributes_list_default(
  Context   : TRDBContextHandle; //!< [in] library context
  var Count : TRDBUInt32;        //!< [out] number of names
  var List  : TRDBString         //!< [out] list of names, separated by \0
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Query attribute names of filter
 * \returns a list of attribute names used in the filter string
 *
 * __Examples:__
 *
 *     filter: (riegl.reflectance > 0)
 *     result: [riegl.reflectance]
 *
 *     filter: (riegl.id < 100) && (riegl.class == 2)
 *     result: [riegl.id, riegl.class]
 *
 *     filter: (riegl.target_index == riegl.target_count)
 *     result: [riegl.target_index, riegl.target_count]
 *
 *     filter: (riegl.xyz[0] > 0.0) && (riegl.xyz[1] > 0.0)
 *     result: [riegl.xyz]
 *}
function rdb_pointcloud_point_attributes_list_filtered(
  Context   : TRDBContextHandle; //!< [in] library context
  Filter    : TRDBString;        //!< [in] filter string
  var Count : TRDBUInt32;        //!< [out] number of names
  var List  : TRDBString         //!< [out] list of names, separated by \0
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Check if attribute exists
 * \returns true if an attribute with given name exists
 *}
function rdb_pointcloud_point_attributes_exists(
  Context    : TRDBContextHandle;    //!< [in] library context
  Pointcloud : TRDBPointcloudHandle; //!< [in] point cloud handle
  Name       : TRDBString;           //!< [in] point attribute name
  var Exists : TRDBUInt32            //!< [out] 0 if attribute does not exist
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Add new attribute
 *
 * \note All attribute name related functions are case sensitive
 *       (i.e. "xyz" and "XYZ" are different attributes).
 * \note Attribute names are unique. If an attribute with the same
 *       name already exists in the database, this function fails.
 *}
function rdb_pointcloud_point_attributes_add(
  Context    : TRDBContextHandle;                 //!< [in] library context
  Pointcloud : TRDBPointcloudHandle;              //!< [in] point cloud handle
  Attribute  : TRDBPointcloudPointAttributeHandle //!< [in] attribute information to be added
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Add RIEGL attribute
 *
 * If the given name refers to an built-in RIEGL default point attribute,
 * this function adds the point attribute to the database. Otherwise this
 * function fails.
 *
 * \note All attribute name related functions are case sensitive
 *       (i.e. "xyz" and "XYZ" are different attributes).
 * \note Attribute names are unique. If an attribute with the same
 *       name already exists in the database, this function fails.
 *}
function rdb_pointcloud_point_attributes_add_default(
  Context    : TRDBContextHandle;    //!< [in] library context
  Pointcloud : TRDBPointcloudHandle; //!< [in] point cloud handle
  Name       : TRDBString            //!< [in] name of a RIEGL point attribute
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Query attribute details
 *
 * If the given attribute name could not be found, the function fails.
 *}
function rdb_pointcloud_point_attributes_get(
  Context    : TRDBContextHandle;                 //!< [in] library context
  Pointcloud : TRDBPointcloudHandle;              //!< [in] point cloud handle
  Name       : TRDBString;                        //!< [in] point attribute name
  Attribute  : TRDBPointcloudPointAttributeHandle //!< [out] attribute information (copy)
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Query attribute group and index
 *
 * Since RDB version 2.2 the point attributes are grouped and sorted for
 * a better overview. The grouping and order is defined by a table which
 * is stored in the meta data entry "riegl.point_attribute_groups". Each
 * database is filled with a default table with RIEGL point attributes.
 *
 * The list() function returns the names of the attributes actually used
 * in the order defined by the table. Function group() returns the group
 * name as well as the table index of a point attribute (index starts at
 * 1 for the first attribute in the first group and goes up to N for the
 * last attribute in the last group).
 *
 * So unless you want to change the order of the point attributes or add
 * your own, it is not necessary to access or change the attribute table.
 *
 * \see rdb_pointcloud_point_attributes_list()
 *}
function rdb_pointcloud_point_attributes_group(
  Context    : TRDBContextHandle;    //!< [in] library context
  Pointcloud : TRDBPointcloudHandle; //!< [in] point cloud handle
  Name       : TRDBString;           //!< [in] attribute name
  var Group  : TRDBString;           //!< [out] attribute group name
  var Index  : TRDBUInt32            //!< [out] attribute index
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Modify attribute details
 *
 * This function allows to modify certain point attribute properties.
 * Thereto the function looks for a point attribute of the given name,
 * compares the given properties with those stored in the database and
 * finally updates the modified properties in the database.
 *
 * \note Only the modification of `name`, `title`, `description`, `tags`,
 *       `unitSymbol`, `scaleFactor` and `lodSettings` is supported.
 *       If a point attribute shall be renamed, the `name` property
 *       must contain the old and the new name separated by " -> ".
 *       Example: To rename `riegl.xyz` to `riegl.xyz_socs`, set
 *       `attribute.name` to `riegl.xyz -> riegl.xyz_socs`.
 *}
function rdb_pointcloud_point_attributes_put(
  Context    : TRDBContextHandle;                 //!< [in] library context
  Pointcloud : TRDBPointcloudHandle;              //!< [in] point cloud handle
  Attribute  : TRDBPointcloudPointAttributeHandle //!< [in] attribute information to be modified
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Query default attribute details
 *
 * This function is similar to get() but instead of returning the attribute
 * details defined in the database, it returns the details of the built-in
 * RIEGL default point attribute.
 *
 * If the given attribute name could not be found, the function fails.
 *}
function rdb_pointcloud_point_attributes_get_default(
  Context   : TRDBContextHandle;                 //!< [in] library context
  Name      : TRDBString;                        //!< [in] point attribute name
  Attribute : TRDBPointcloudPointAttributeHandle //!< [out] attribute information (copy)
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Query default attribute group and index
 *
 * This function works like rdb_pointcloud_point_attributes_group() but returns
 * the group and index from the built-in RIEGL default attribute table.
 *}
function rdb_pointcloud_point_attributes_group_default(
  Context   : TRDBContextHandle; //!< [in] library context
  Name      : TRDBString;        //!< [in] attribute name
  var Group : TRDBString;        //!< [out] attribute group name
  var Index : TRDBUInt32         //!< [out] attribute index
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Merge attribute details
 *
 * This function returns the details (description) of a point attribute by
 * analyzing two or more point clouds. If the attribute details are not the
 * same in all point clouds (e.g. a typo was fixed or the minimum, maximum,
 * resolution or default value has changed), merged details that cover all
 * variants are returned. However, some changes can not be merged, e.g.
 * changes to the vector length, the unit symbol or changes to the minimum,
 * maximum and resolution values that would require unsupported data types
 * (see riegl::rdb::pointcloud::DataType). In this case an exception with
 * error code riegl::rdb::Error::PointAttributeNotMergeable is thrown.
 * It is not required that the point attribute exists in all point clouds.
 *
 * This function might be helpful when merging point clouds, i.e. when
 * reading points from multiple databases and storing them in a single
 * database. In this case one needs to define the point attributes in the
 * target database. If the source databases were generated by different
 * software applications (or different versions of the same application),
 * the details of the same attribute may slightly differ (see above), which
 * makes the definition of the attribute in the target database quite hard.
 * So instead of defining the attributes by using a hard-coded table, one
 * can use this function to generate the attribute description at runtime.
 *
 * __Examples (simplified, fake attributes):__
 *
 *   | Example 1      | Attribute   | Description      | Resolution | Minimum | Maximum |
 *   | -------------- | ----------- | ---------------- |----------: | ------: | ------: |
 *   | Point cloud A  | amplitude   | signal amplitude |       0.01 |    0.00 |  655.35 |
 *   | Point cloud B  | amplitude   | signal amplitude |       0.01 | -327.68 |  327.67 |
 *   | Merged details | amplitude   | signal amplitude |       0.01 | -327.68 |  655.35 |
 *
 *   | Example 2      | Attribute   | Description         | Resolution | Minimum | Maximum |
 *   | -------------- | ----------- | ------------------- | ---------: | ------: | ------: |
 *   | Point cloud A  | temperature | coarse temperature  |       0.50 | -100.00 |  500.00 |
 *   | Point cloud B  | temperature | precise temperature |       0.01 | -100.00 | 1000.00 |
 *   | Merged details | temperature | precise temperature |       0.01 | -100.00 | 1000.00 |
 *
 * In both examples the value range (minimum/maximum) of the attribute has
 * changed, so the result range is the combination (disjunction) of both
 * ranges.
 *
 * In the second example the resolution is different too. The example
 * shows that always the higher resolution (lower value) is returned.
 *
 * If the description text changes, then the result is the text that is
 * stored in the younger database (the database that was created last).
 * In example 2, "Point cloud B" is the younger database.
 *}
function rdb_pointcloud_point_attributes_get_merged(
  Context     : TRDBContextHandle;                 //!< [in] library context
  Pointclouds : PRDBPointcloudHandle;              //!< [in] point cloud handles
  Count       : TRDBUInt32;                        //!< [in] number of point clouds
  Name        : TRDBString;                        //!< [in] point attribute name
  Attribute   : TRDBPointcloudPointAttributeHandle //!< [out] attribute information (copy)
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Merge attribute details
 *
 * Same as rdb_pointcloud_point_attributes_get_merged(), but instead of
 * merging one point attribute, this function merges all point attributes
 * of all given point clouds.
 *
 * To query the number of point attributes, call with 'attribute_list'
 * and 'attribute_size' set to 0 and read 'attribute_size' afterwards.
 *}
function rdb_pointcloud_point_attributes_get_merged_all(
  Context           : TRDBContextHandle;                  //!< [in] library context
  PointcloudList    : PRDBPointcloudHandle;               //!< [in] point cloud handles
  PointcloudSize    : TRDBUInt32;                         //!< [in] number of point clouds
  AttributeList     : PRDBPointcloudPointAttributeHandle; //!< [out] attribute information array
  var AttributeSize : TRDBUInt32                          //!< [out] number of attributes
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Delete attribute
 *
 * If the given attribute name could not be found, this function does not fail.
 *
 * \note The primary point attribute (see CreateSettings::primaryAttribute)
 *       and the unique point identifier (ID) cannot be deleted.
 *}
function rdb_pointcloud_point_attributes_remove(
  Context    : TRDBContextHandle;    //!< [in] library context
  Pointcloud : TRDBPointcloudHandle; //!< [in] point cloud handle
  Name       : TRDBString            //!< [in] point attribute name
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Duplicate attribute data
 *
 * Use this function to duplicate the current point attribute *data* (but
 * not the point attribute description). This can e.g. be used to make a
 * backup of point attribute data.
 *
 * \note The source and target attributes must exist and be compatible (i.e.
 *       attribute minimum, maximum, resolution and length are identical).
 *
 * \note The primary point attribute and the point ID attribute cannot be
 *       used as target attribute.
 *}
function rdb_pointcloud_point_attributes_duplicate(
  Context    : TRDBContextHandle;    //!< [in] library context
  Pointcloud : TRDBPointcloudHandle; //!< [in] point cloud handle
  Source     : TRDBString;           //!< [in] source attribute name
  Target     : TRDBString            //!< [in] target attribute name
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Discard attribute data
 *
 * Use this function to delete the current point attribute *data* (but
 * not the point attribute description). This is equivalent to a newly
 * created point attribute, i.e. all points will have the default value.
 *
 * \note The primary point attribute (see CreateSettings::primaryAttribute)
 *       and the unique point identifier (ID) data cannot be discarded.
 *}
function rdb_pointcloud_point_attributes_discard(
  Context    : TRDBContextHandle;    //!< [in] library context
  Pointcloud : TRDBPointcloudHandle; //!< [in] point cloud handle
  Name       : TRDBString            //!< [in] attribute name
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Point identifier attribute name
 *}
function rdb_pointcloud_point_attributes_point_id_name(
  var Value : TRDBString //!< [out] point attribute name
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Point identifier attribute unit
 *}
function rdb_pointcloud_point_attributes_point_id_unit(
  var Value : TRDBString //!< [out] point attribute unit
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Point identifier data type enum
 *}
function rdb_pointcloud_point_attributes_point_id_type(
  var Value : TRDBUInt32 //!< [out] point attribute type enum
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Name of primary point attribute
 *}
function rdb_pointcloud_point_attributes_primary_attribute_name(
  Context    : TRDBContextHandle;    //!< [in] library context
  Pointcloud : TRDBPointcloudHandle; //!< [in] point cloud handle
  var Value  : TRDBString            //!< [out] point attribute name
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//---< CLASS TRDBPointcloudPointAttributes >------------------------------------
{*!
 * \brief Manage point attributes
 *
 * This class allows to manage point attributes (dimensions).
 *
 * When creating a new database, each point has at least two attributes:
 *
 *  - an unique point identifier (ID)
 *  - the primary attribute specified in CreateSettings::primaryAttribute
 *
 * Both attributes cannot be deleted or modified in any way.
 *
 * The point identifier attribute is always named "id" and is an unsigned 64
 * bit integer number (see DataTypes::UINT64, pointIDType() and pointIDName()).
 *
 * \see riegl::rdb::Pointcloud::pointAttribute()
 *}
type
  TRDBPointcloudPointAttributes = class(System.TObject)
  public
    {*!
     * \brief Constructor
     * \note  You cannot create new TRDBPointcloudPointAttributes objects directly,
     *        use riegl::rdb::Pointcloud::pointAttribute() instead.
     *}
    constructor Create(Parent : System.TObject); reintroduce;
    destructor  Destroy; override;

    {*!
     * \brief Query attribute names
     *
     * This function returns the names of the attributes defined in the
     * database in the order defined by the point attribute group table
     * (details see Group() function).
     *
     * \see Group()
     *}
    function List() : Classes.TStringList;

    {*!
     * \brief Query default attribute names
     * \returns a list of built-in RIEGL default attribute names
     * \note the order of attribute names is undefined
     *}
    class function ListDefault(Context : TRDBContext) : Classes.TStringList;

    {*!
     * \brief Query attribute names of filter
     * \returns a list of attribute names used in the filter string
     *
     * __Examples:__
     *
     *     filter: (riegl.reflectance > 0)
     *     result: [riegl.reflectance]
     *
     *     filter: (riegl.id < 100) && (riegl.class == 2)
     *     result: [riegl.id, riegl.class]
     *
     *     filter: (riegl.target_index == riegl.target_count)
     *     result: [riegl.target_index, riegl.target_count]
     *
     *     filter: (riegl.xyz[0] > 0.0) && (riegl.xyz[1] > 0.0)
     *     result: [riegl.xyz]
     *}
    class function ListFiltered(
      const Filter : System.String; //!< [in] filter string
      Context      : TRDBContext
    ) : Classes.TStringList;

    {*!
     * \brief Check if attribute exists
     * \returns true if an attribute with given name exists
     *}
    function Exists(
      const Name : System.String //!< [in] attribute name
    ) : System.Boolean;

    {*!
     * \brief Add new attribute
     *
     * \note All attribute name related functions are case sensitive
     *       (i.e. "xyz" and "XYZ" are different attributes).
     * \note Attribute names are unique. If an attribute with the same
     *       name already exists in the database, this function fails.
     *}
    procedure Add(
      const Attribute : TRDBPointcloudPointAttribute //!< [in] attribute information to be added
    ); overload;

    {*!
     * \brief Add RIEGL attribute
     *
     * If the given name refers to an built-in RIEGL default point attribute,
     * this function adds the point attribute to the database. Otherwise this
     * function fails.
     *
     * \note All attribute name related functions are case sensitive
     *       (i.e. "xyz" and "XYZ" are different attributes).
     * \note Attribute names are unique. If an attribute with the same
     *       name already exists in the database, this function fails.
     *}
    procedure Add(
      const Name : System.String //!< [in] name of a RIEGL point attribute
    ); overload;

    {*!
     * \brief Query attribute details
     *
     * If the given attribute name could not be found, the function fails.
     *
     * \note Don't forget to destroy the result point attribute object!
     *}
    function Get(
      const Name : System.String //!< [in] attribute name
    ) : TRDBPointcloudPointAttribute;

    {*!
     * \brief Query attribute group and index
     *
     * Since RDB version 2.2 the point attributes are grouped and sorted for
     * a better overview. The grouping and order is defined by a table which
     * is stored in the meta data entry "riegl.point_attribute_groups". Each
     * database is filled with a default table with RIEGL point attributes.
     *
     * The List() function returns the names of the attributes actually used
     * in the order defined by the table. Function Group() returns the group
     * name as well as the table index of a point attribute (index starts at
     * 1 for the first attribute in the first group and goes up to N for the
     * last attribute in the last group).
     *
     * So unless you want to change the order of the point attributes or add
     * your own, it is not necessary to access or change the attribute table.
     *
     * \see List()
     *}
    procedure Group(
      const Name  : System.String; //!< [in] attribute name
      out   Group : System.String; //!< [out] attribute group name
      out   Index : TRDBUInt32     //!< [out] attribute index
    );

    {*!
     * \brief Modify attribute details
     *
     * This function allows to modify certain point attribute properties.
     * Thereto the function looks for a point attribute of the given name,
     * compares the given properties with those stored in the database and
     * finally updates the modified properties in the database.
     *
     * \note Only the modification of `name`, `title`, `description`, `tags`,
     *       `unitSymbol`, `scaleFactor` and `lodSettings` is supported.
     *       If a point attribute shall be renamed, the `name` property
     *       must contain the old and the new name separated by " -> ".
     *       Example: To rename `riegl.xyz` to `riegl.xyz_socs`, set
     *       `attribute.name` to `riegl.xyz -> riegl.xyz_socs`.
     *}
    procedure Put(
      const Attribute : TRDBPointcloudPointAttribute //!< [in] attribute information to be modified
    ); overload;

    {*!
     * \brief Query default attribute details
     *
     * This function is similar to get() but instead of returning the attribute
     * details defined in the database, it returns the details of the built-in
     * RIEGL default point attribute.
     *
     * If the given attribute name could not be found, the function fails.
     *
     * \note Don't forget to destroy the result point attribute object!
     *}
    class function GetDefault(
      Context    : TRDBContext;  //!< [in] library context
      const Name : System.String //!< [in] attribute name
    ) : TRDBPointcloudPointAttribute;

    {*!
     * \brief Query default attribute group and index
     *
     * This function works like Group() but returns the group and index from
     * the built-in RIEGL default attribute table.
     *}
    class procedure GroupDefault(
      Context     : TRDBContext;   //!< [in] library context
      const Name  : System.String; //!< [in] attribute name
      out   Group : System.String; //!< [out] attribute group name
      out   Index : TRDBUInt32     //!< [out] attribute index
    );

    {*!
     * \brief Merge attribute details
     *
     * This function returns the details (description) of a point attribute by
     * analyzing two or more point clouds. If the attribute details are not the
     * same in all point clouds (e.g. a typo was fixed or the minimum, maximum,
     * resolution or default value has changed), merged details that cover all
     * variants are returned. However, some changes can not be merged, e.g.
     * changes to the vector length, the unit symbol or changes to the minimum,
     * maximum and resolution values that would require unsupported data types
     * (see riegl::rdb::pointcloud::DataType). In this case an exception with
     * error code riegl::rdb::Error::PointAttributeNotMergeable is thrown.
     * It is not required that the point attribute exists in all point clouds.
     *
     * This function might be helpful when merging point clouds, i.e. when
     * reading points from multiple databases and storing them in a single
     * database. In this case one needs to define the point attributes in the
     * target database. If the source databases were generated by different
     * software applications (or different versions of the same application),
     * the details of the same attribute may slightly differ (see above), which
     * makes the definition of the attribute in the target database quite hard.
     * So instead of defining the attributes by using a hard-coded table, one
     * can use this function to generate the attribute description at runtime.
     *
     * __Examples (simplified, fake attributes):__
     *
     *   | Example 1      | Attribute   | Description      | Resolution | Minimum | Maximum |
     *   | -------------- | ----------- | ---------------- |----------: | ------: | ------: |
     *   | Point cloud A  | amplitude   | signal amplitude |       0.01 |    0.00 |  655.35 |
     *   | Point cloud B  | amplitude   | signal amplitude |       0.01 | -327.68 |  327.67 |
     *   | Merged details | amplitude   | signal amplitude |       0.01 | -327.68 |  655.35 |
     *
     *   | Example 2      | Attribute   | Description         | Resolution | Minimum | Maximum |
     *   | -------------- | ----------- | ------------------- | ---------: | ------: | ------: |
     *   | Point cloud A  | temperature | coarse temperature  |       0.50 | -100.00 |  500.00 |
     *   | Point cloud B  | temperature | precise temperature |       0.01 | -100.00 | 1000.00 |
     *   | Merged details | temperature | precise temperature |       0.01 | -100.00 | 1000.00 |
     *
     * In both examples the value range (minimum/maximum) of the attribute has
     * changed, so the result range is the combination (disjunction) of both
     * ranges.
     *
     * In the second example the resolution is different too. The example
     * shows that always the higher resolution (lower value) is returned.
     *
     * If the description text changes, then the result is the text that is
     * stored in the younger database (the database that was created last).
     * In example 2, "Point cloud B" is the younger database.
     *
     * \note Don't forget to destroy the result point attribute object!
     *}
    class function GetMerged(
      Context     : TRDBContext;                 //!< [in] library context
      Pointclouds : TObjectList{TRDBPointcloud}; //!< [in] list of point clouds
      const Name  : System.String                //!< [in] attribute name
    ) : TRDBPointcloudPointAttribute; overload;

    {*!
     * \brief Merge attribute details
     *
     * Same as the overloaded version above, but instead of merging one point
     * attribute, this function merges all point attributes of all given point
     * clouds.
     *
     * \note Don't forget to destroy the result point attribute list!
     *}
    class function GetMerged(
      Context     : TRDBContext;                //!< [in] library context
      Pointclouds : TObjectList{TRDBPointcloud} //!< [in] list of point clouds
    ) : TObjectList{TRDBPointcloudPointAttribute}; overload;

    {*!
     * \brief Delete attribute
     *
     * If the given attribute name could not be found, this function does not fail.
     *
     * \note The primary point attribute (see CreateSettings::primaryAttribute)
     *       and the unique point identifier (ID) cannot be deleted.
     *}
    procedure Remove(
      const Name : System.String //!< [in] attribute name
    );

    {*!
     * \brief Duplicate attribute data
     *
     * Use this function to duplicate the current point attribute *data* (but
     * not the point attribute description). This can e.g. be used to make a
     * backup of point attribute data.
     *
     * \note The source and target attributes must exist and be compatible (i.e.
     *       attribute minimum, maximum, resolution and length are identical).
     *
     * \note The primary point attribute and the point ID attribute cannot be
     *       used as target attribute.
     *}
    procedure Duplicate(
      const Source : System.String; //!< [in] source attribute name
      const Target : System.String  //!< [in] target attribute name
    );

    {*!
     * \brief Discard attribute data
     *
     * Use this function to delete the current point attribute *data* (but
     * not the point attribute description). This is equivalent to a newly
     * created point attribute, i.e. all points will have the default value.
     *
     * \note The primary point attribute (see CreateSettings::primaryAttribute)
     *       and the unique point identifier (ID) data cannot be discarded.
     *}
    procedure Discard(
      const Name : System.String //!< [in] attribute name
    );

  public
    class function PointIDName() : System.String;          //!< Point identifier attribute name
    class function PointIDUnit() : System.String;          //!< Point identifier attribute unit
    class function PointIDType() : TRDBPointcloudDataType; //!< Point identifier data type enum

    function PrimaryAttributeName() : System.String; //!< Name of primary point attribute

  private
    FContext    : TRDBContext;
    FPointcloud : TRDBPointcloudHandle;
  end;

{******************************************************************************}
{***} IMPLEMENTATION {*********************************************************}
{******************************************************************************}

//---< INCLUDES >---------------------------------------------------------------

uses
  SysUtils,
  riegl.rdb.pointcloud;

//---< TRDBPointcloudPointAttributes::PUBLIC >----------------------------------

constructor TRDBPointcloudPointAttributes.Create(Parent : System.TObject);
begin
  inherited Create;
  FContext    := (Parent as TRDBPointcloud).Context;
  FPointcloud := (Parent as TRDBPointcloud).Pointcloud;
end; // Create()

destructor TRDBPointcloudPointAttributes.Destroy;
begin
  inherited;
end; // Destroy()

function TRDBPointcloudPointAttributes.List() : Classes.TStringList;
var
  i     : Integer;
  Count : TRDBUInt32;
  List  : TRDBString;
  Value : String;
begin
  Count := 0; List := nil;
  FContext.Check(rdb_pointcloud_point_attributes_list(
    FContext.Handle, FPointcloud, Count, List
  ));
  Result := Classes.TStringList.Create;
  if (Count > 0) then for i := 0 to Count-1 do
  begin
    Value := AsSTDString(List);
    Result.Add(Value);
    Inc(List, Length(Value)+1);
  end;
end; // List()

class function TRDBPointcloudPointAttributes.ListDefault(Context : TRDBContext) : Classes.TStringList;
var
  i     : Integer;
  Count : TRDBUInt32;
  List  : TRDBString;
  Value : String;
begin
  Count := 0; List := nil;
  Context.Check(rdb_pointcloud_point_attributes_list_default(
    Context.Handle, Count, List
  ));
  Result := Classes.TStringList.Create;
  if (Count > 0) then for i := 0 to Count-1 do
  begin
    Value := AsSTDString(List);
    Result.Add(Value);
    Inc(List, Length(Value)+1);
  end;
end; // ListDefault()

class function TRDBPointcloudPointAttributes.ListFiltered(
  const Filter : System.String;
  Context      : TRDBContext
) : Classes.TStringList;
var
  i     : Integer;
  Count : TRDBUInt32;
  List  : TRDBString;
  Value : String;
begin
  Count := 0; List := nil;
  Context.Check(rdb_pointcloud_point_attributes_list_filtered(
    Context.Handle, TRDBString(AnsiToUtf8(Filter)), Count, List
  ));
  Result := Classes.TStringList.Create;
  if (Count > 0) then for i := 0 to Count-1 do
  begin
    Value := AsSTDString(List);
    Result.Add(Value);
    Inc(List, Length(Value)+1);
  end;
end; // ListFiltered()

function TRDBPointcloudPointAttributes.Exists(const Name : System.String) : System.Boolean;
var
  Value : TRDBUInt32;
begin
  FContext.Check(rdb_pointcloud_point_attributes_exists(
    FContext.Handle, FPointcloud, TRDBString(AnsiToUtf8(Name)), Value
  ));
  Result := (Value <> 0);
end; // Exists()

procedure TRDBPointcloudPointAttributes.Add(const Attribute : TRDBPointcloudPointAttribute);
begin
  PointcloudPointAttributePost(Attribute);
  FContext.Check(rdb_pointcloud_point_attributes_add(
    FContext.Handle, FPointcloud, Attribute.Handle
  ));
end; // Add()

procedure TRDBPointcloudPointAttributes.Add(const Name : System.String);
begin
  FContext.Check(rdb_pointcloud_point_attributes_add_default(
    FContext.Handle, FPointcloud, TRDBString(AnsiToUtf8(Name))
  ));
end; // Add()

function TRDBPointcloudPointAttributes.Get(const Name : System.String) : TRDBPointcloudPointAttribute;
var
  Return : TRDBPointcloudPointAttribute;
begin
  Return := TRDBPointcloudPointAttribute.Create(FContext);
  try
    FContext.Check(rdb_pointcloud_point_attributes_get(
      FContext.Handle, FPointcloud, TRDBString(AnsiToUtf8(Name)), Return.Handle
    ));
    PointcloudPointAttributeRead(Return);
    Result := Return; Return := nil;
  finally
    FreeAndNil(Return); // in case of troubles...
  end; // try..finally
end; // Get()

procedure TRDBPointcloudPointAttributes.Group(
  const Name  : System.String;
  out   Group : System.String;
  out   Index : TRDBUInt32
);
var
  Return : record
    Group : TRDBString;
    Index : TRDBUInt32;
  end;
begin
  FillChar(Return, SizeOf(Return), 0);
  FContext.Check(rdb_pointcloud_point_attributes_group(
    FContext.Handle, FPointcloud,
    TRDBString(AnsiToUtf8(Name)),
    Return.Group, Return.Index
  ));
  Group := AsSTDString(Return.Group);
  Index := Return.Index;
end;

procedure TRDBPointcloudPointAttributes.Put(const Attribute : TRDBPointcloudPointAttribute);
begin
  PointcloudPointAttributePost(Attribute);
  FContext.Check(rdb_pointcloud_point_attributes_put(
    FContext.Handle, FPointcloud, Attribute.Handle
  ));
end; // Put()

class function TRDBPointcloudPointAttributes.GetDefault(
  Context    : TRDBContext;
  const Name : System.String
) : TRDBPointcloudPointAttribute;
var
  Return : TRDBPointcloudPointAttribute;
begin
  Return := TRDBPointcloudPointAttribute.Create(Context);
  try
    Context.Check(rdb_pointcloud_point_attributes_get_default(
      Context.Handle, TRDBString(AnsiToUtf8(Name)), Return.Handle
    ));
    PointcloudPointAttributeRead(Return);
    Result := Return; Return := nil;
  finally
    FreeAndNil(Return); // in case of troubles...
  end; // try..finally
end; // GetDefault()

class procedure TRDBPointcloudPointAttributes.GroupDefault(
  Context     : TRDBContext;
  const Name  : System.String;
  out   Group : System.String;
  out   Index : TRDBUInt32
);
var
  Return : record
    Group : TRDBString;
    Index : TRDBUInt32;
  end;
begin
  FillChar(Return, SizeOf(Return), 0);
  Context.Check(rdb_pointcloud_point_attributes_group_default(
    Context.Handle,
    TRDBString(AnsiToUtf8(Name)),
    Return.Group, Return.Index
  ));
  Group := AsSTDString(Return.Group);
  Index := Return.Index;
end; // GroupDefault()

class function TRDBPointcloudPointAttributes.GetMerged(
  Context     : TRDBContext;
  Pointclouds : TObjectList{TRDBPointcloud};
  const Name  : System.String
) : TRDBPointcloudPointAttribute;
var
  i      : Integer;
  n      : TRDBUInt32;
  List   : array of TRDBPointcloudHandle;
  Return : TRDBPointcloudPointAttribute;
begin
  Result := nil; // no result yet
  Return := TRDBPointcloudPointAttribute.Create(Context);
  try
    if (Pointclouds       = nil) then Exit;
    if (Pointclouds.Count = 000) then Exit;
    SetLength(List, Pointclouds.Count); n := 0;
    for i := 0 to Pointclouds.Count-1 do if (Pointclouds[i] is TRDBPointcloud) then
    begin
      List[n] := TRDBPointcloud(Pointclouds[i]).Pointcloud;
      Inc(n);
    end;
    Context.Check(rdb_pointcloud_point_attributes_get_merged(
      Context.Handle, @List[0], n, TRDBString(AnsiToUtf8(Name)), Return.Handle
    ));
    PointcloudPointAttributeRead(Return);
    Result := Return; Return := nil;
  finally
    FreeAndNil(Return); // in case of troubles...
  end; // try..finally
end; // GetMerged()

class function TRDBPointcloudPointAttributes.GetMerged(
  Context     : TRDBContext;
  Pointclouds : TObjectList{TRDBPointcloud}
) : TObjectList{TRDBPointcloudPointAttribute};
var
  i              : Integer;
  Return         : TObjectList{TRDBPointcloudPointAttribute};
  PointcloudList : array of TRDBPointcloudHandle;
  PointcloudSize : TRDBUInt32;
  AttributeList  : array of TRDBPointcloudPointAttributeHandle;
  AttributeSize  : TRDBUInt32;
begin
  Result := nil; // no result yet
  Return := TObjectList.Create(TRUE);
  try
    // prepare point cloud handle array
    if (Pointclouds = nil) or (Pointclouds.Count = 0) then Exit;
    SetLength(PointcloudList, Pointclouds.Count); PointcloudSize := 0;
    for i := 0 to Pointclouds.Count-1 do if (Pointclouds[i] is TRDBPointcloud) then
    begin
      PointcloudList[PointcloudSize] := TRDBPointcloud(Pointclouds[i]).Pointcloud;
      Inc(PointcloudSize);
    end;

    // query number of attributes
    AttributeSize := 0;
    Context.Check(rdb_pointcloud_point_attributes_get_merged_all(
      Context.Handle,
      @PointcloudList[0], PointcloudSize,
      nil{doesn't exist}, AttributeSize
    ));

    // prepare point attribute data and handle arrays
    if (AttributeSize = 0) then Exit;
    SetLength(AttributeList, AttributeSize);
    for i := 0 to AttributeSize-1 do
    begin
      Return.Add(TRDBPointcloudPointAttribute.Create(Context));
      AttributeList[i] := TRDBPointcloudPointAttribute(Return[i]).Handle;
    end;

    // query merged point attributes
    Context.Check(rdb_pointcloud_point_attributes_get_merged_all(
      Context.Handle,
      @PointcloudList[0], PointcloudSize,
      @AttributeList [0], AttributeSize
    ));

    // read back results
    if (AttributeSize = 0) then Exit;
    for i := 0 to AttributeSize-1 do
    begin
      PointcloudPointAttributeRead(TRDBPointcloudPointAttribute(Return[i]));
    end;

    // success
    Result := Return;
    Return := nil;
  finally
    FreeAndNil(Return);
  end; // try..finally
end; // GetMerged()

procedure TRDBPointcloudPointAttributes.Remove(const Name : System.String);
begin
  FContext.Check(rdb_pointcloud_point_attributes_remove(
    FContext.Handle, FPointcloud, TRDBString(AnsiToUtf8(Name))
  ));
end; // Remove()

procedure TRDBPointcloudPointAttributes.Duplicate(const Source : System.String; const Target : System.String);
begin
  FContext.Check(rdb_pointcloud_point_attributes_duplicate(
    FContext.Handle, FPointcloud, TRDBString(AnsiToUtf8(Source)), TRDBString(AnsiToUtf8(Target))
  ));
end; // Duplicate()

procedure TRDBPointcloudPointAttributes.Discard(const Name : System.String);
begin
  FContext.Check(rdb_pointcloud_point_attributes_discard(
    FContext.Handle, FPointcloud, TRDBString(AnsiToUtf8(Name))
  ));
end; // Discard()

class function TRDBPointcloudPointAttributes.PointIDName() : System.String;
var
  Value : TRDBString;
begin
  Value := nil;
  rdb_pointcloud_point_attributes_point_id_name(Value);
  Result := AsSTDString(Value);
end; // PointIDName()

class function TRDBPointcloudPointAttributes.PointIDUnit() : System.String;
var
  Value : TRDBString;
begin
  Value := nil;
  rdb_pointcloud_point_attributes_point_id_unit(Value);
  Result := AsSTDString(Value);
end; // PointIDUnit()

class function TRDBPointcloudPointAttributes.PointIDType() : TRDBPointcloudDataType;
var
  Value : TRDBUInt32;
begin
  Value := 0;
  rdb_pointcloud_point_attributes_point_id_type(Value);
  Result := ConvertDataType(Value);
end; // PointIDType()

function TRDBPointcloudPointAttributes.PrimaryAttributeName() : String;
var
  Value : TRDBString;
begin
  Value := nil;
  FContext.Check(rdb_pointcloud_point_attributes_primary_attribute_name(
    FContext.Handle, FPointcloud, Value
  ));
  Result := AsSTDString(Value);
end; // PrimaryAttributeName()

end.
