Working with Object Primitives

As discussed in Data and Objects, spatial objects are composed of fundamental geometrical structures called primitives. The five primitive types are points, edges, facets, cells, and blocks. The primitives that make up a spatial object can be accessed through various properties on the object. In addition, other properties exist that correspond to each primitive, such as colours or visibilities.

Points

The points of any object are stored in its points property. Because most point-based objects contain multiple points, the points property is represented as a two-dimensional NumPy array. A single point is stored in an array of three elements [X, Y, Z], representing the X, Y and Z coordinates, respectively. A points array is then an array of point arrays.

Use the following properties and methods to manipulate the points of an object:

Name Description
point_z Use this property to change the z-coordinate of a point.
point_colours

Use this property to change the colour of a point. The colour of each point is represented by an array containing four values [R, G, B, A] where:

  • R, G and B are each values between 0 and 255.

  • A is a value between 0 and 255 where 0 represents full transparency and 255 represents no transparency.

point_visibility Use this boolean property to make a point visible or invisible.
point_selection Use this property to select a group of points.
point_count Use this property to count the number of points in an object.
append_points() Use this method to append points to an object.
remove_points(point_indices) Use this method to remove a set of points from an object. This method takes an array of point indices.
point_attributes Use this method to save or return custom point attributes in an object. To set a particular point attribute, you can use the notation point_attributes[attribute_name] = data.
save_point_attribute(attribute_name, data) Use this method to save a point attribute and allocate data to it.

Refer to the mapteksdk.data.primitives.point_properties module in the API reference for more detailed information.

Creating a point set

A PointSet is the simplest kind of point-based spatial object. In the example below, we create a PointSet containing four points in a square shape in the XY plane.

from mapteksdk.project import Project
from mapteksdk.data import PointSet

project = Project()

with project.new("cad/points", PointSet) as new_points:
    new_points.points = [[-1, -1, 0], [1, -1, 0], [-1, 1, 0], [1, 1, 0]]

While facet-based and cell-based objects are also point-based, only PointSet is purely based on point primitive types. To learn more about point sets, see the extended topic Point Sets.

Accessing points

As the Maptek Python SDK leverages the NumPy library, we can use NumPy indexing to access points as illustrated below.

from mapteksdk.project import Project
from mapteksdk.data import PointSet

project = Project()

with project.new("cad/points", PointSet) as point_set:
    point_set.points = [[-1, -1, 0], [1, -1, 0], [-1, 1, 0], [1, 1, 0]]

    print("First point: ", point_set.points[0])
    print("Last point: ", point_set.points[-1])
    print("X of point 1: ", point_set.points[1, 0])
    print("Y of point 2: ", point_set.points[2, 1])
    print("Z of point 0: ", point_set.points[0, 2])
    print("X coordinates: ", point_set.points[:, 0])
    print("Y coordinates: ", point_set.points[:, 1])
    print("Z coordinates: ", point_set.point_z)
    # point_set.points[:, 2] will also give the z coordinates.

There is one method that can be applied to any point-based object — the dataframe method. It returns the following values:

  • The X, Y and Z coordinates of each point

  • The R, G, B and A values of each point

  • The point visibility of each point

  • The point selection of each point

  • Point primitive attributes

Here is a code example:

from mapteksdk.project import Project
from mapteksdk.project import PointSet
project = Project() # Connect to default project


point_path = "scrapbook/my_points"


#1. Create points
with project.new(point_path, PointSet, overwrite=True) as points:
    points.points = [[0, 1, 0], [0.5, 1, 0.5], [1, 1, 0],
                     [0, 0.5, 0.5], [0.5, 0.5, 1], [1, 0.5, 0.5],
                     [0, 0, 0], [0.5, 0, 0.5], [1, 0, 0]]
    points:PointSet
    #2. Print frame values
    with points.dataframe(save_changes=False) as frame:
        print(frame)

If you run this code, you should be able to print the following information:

Appending points

Appending points to an object using the append_points() function is the easiest and safest way to add new points to an existing object.

The example below appends a single point to an object.

new_point_mask = point_object.append_points([0.0, 0.0, 1.0])

By passing an arbitrary amount of points, the function can efficiently append an arbitrary amount of points at once:

new_point_mask = point_object.append_points(
    [-1.0, -1.0, 0.0], [1.0, -1.0, 0.0],
    [1.0, 1.0, 0.0], [-1.0, 1.0, 0.0],
    # You can pass as many points as you want.
    )

Alternatively the function can also accept an iterable of points:

points_to_add = [
    [-1.0, -1.0, 0.0], [1.0, -1.0, 0.0],
    [1.0, 1.0, 0.0], [-1.0, 1.0, 0.0]
]
new_point_mask = point_object.append_points(points_to_add)

You may notice that in each of the above snippets, the return value of the append_points() function is captured in a variable. This allows for applying point properties only to the newly appended points. For example, the following snippet demonstrates appending four new points to an object and then colouring those four points to be blue.

new_point_mask= point_object.append_points(
    [-1.0, -1.0, 0.0], [1.0, -1.0, 0.0],
    [1.0, 1.0, 0.0], [-1.0, 1.0, 0.0],
)
point_object.point_colours[new_point_mask] = [0, 0, 200, 255]

The following script puts most of the above snippets together:

from mapteksdk.project import Project
from mapteksdk.data import PointSet


def main(project: Project):
    with project.new("cad/append_points_example", PointSet) as point_object:
        new_point_mask = point_object.append_points(
            [0.0, 0.0, 1.0]
        )
        point_object.point_colours[new_point_mask] = [200, 0, 0, 255]


        new_point_mask = point_object.append_points(
            [-1.0, -1.0, 0.0], [1.0, -1.0, 0.0],
            [1.0, 1.0, 0.0], [-1.0, 1.0, 0.0],
        )
        point_object.point_colours[new_point_mask] = [0, 0, 200, 255]
    return point_object.id


if __name__ == "__main__":
    with Project() as main_project:
        main(main_project)

This creates the following object:

By using the point mask, the second assignment to point colours only coloured the four newly appended points.

Note
  • Duplicate points are silently removed during save (except for FilledPolygon objects).

  • Scripts should avoid calling append_points() inside a loop because it will be computationally expensive (the run time of the script will increase exponentially with the number of points to add). Instead, gather all of the points to append into a list and pass that to a single call of append_points().

The following object types support append_points():

Edges

An edge is a line segment between two points. As a result, all objects that contain edges also contain points. The Maptek Python SDK represents the edge as an array containing two items, with each item representing a point.

The edges of any object are stored in its edges property. Because most edge-based objects contain multiple edges, the edges property is represented as a NumPy array. Each element of the array represents a single edge, which in turn is an array of the two point indices defining the edge.

Use the following properties and methods to manipulate the edges of an object:

Name Description
edge_colours Use this property to change the colour of an edge. The colour of each edge is represented by an array containing four values [R, G, B, A] where:
  • R, G and B are each values between 0 and 255.

  • A is a value between 0 and 255 where 0 represents full transparency and 255 represents no transparency.

edge_count Use this property to get the total number of edges for an edge-based object. For example, you may want to preallocate NumPy arrays to the correct length. In this case, using edge_count is advised for performance reasons with very large objects.
edge_attributes Use this method to save or return custom edge attributes in an object. To set a particular edge attribute, you can use the notation edge_attributes[attribute_name] = data.
save_edge_attribute(attribute_name, data) Use this method to save an edge attribute and allocate data to it.
append_edges() Append edges to an object.
delete_edge_attribute(attribute_name) Use this method to delete an edge attribute.

Creating an edge network

An EdgeNetwork is the simplest edge-based spatial object. We can use it to illustrate how to store its edges with the example below.

from mapteksdk.project import Project
from mapteksdk.data import EdgeNetwork
import numpy

project = Project()

with project.new("cad/edge_network", EdgeNetwork) as network:
  network.points = [[-1, -1, 0], [1, -1, 0], [-1, 1, 0], [1, 1, 0],
                    [-1, 1.2, 0], [1, 1.2, 0]]
  network.edges = [[0, 1], [1, 2], [2, 3], [0, 3], [4, 5]]
  for i, edge in enumerate(network.edges):
    start = network.points[edge[0]]
    end = network.points[edge[1]]
    length = numpy.linalg.norm(end - start)
    print(f"Edge {i} goes from {start} to {end} and is {length} "
          "meters long")

In the example above, we create an EdgeNetwork with six points and five edges, and then print each edge out by accessing each point from the start of an edge to the end of an edge.

Appending edges

The EdgeNetwork.append_edges() function can be used to append an edge to an edge network and apply edge properties to those edges without any risk of changing any of the edge properties of the existing edges. To append an edge between the point with index_a and the point with index_b you would use the following code:

edge_network.append_edges((index_a, index_b))

If you wish to append multiple edges to an edge network, it is more efficient to gather each edge up into a sequence and then pass the entire sequence to the append_edges() function at once. For example, to append an edge between the point with index_a and the point with index_b, and to append an edge between the point with the index_a and the point with index_c, then you should use the following code with a single call to append_edges():

edge_network.append_edges([(index_a, index_b), (index_a, index_c)])

The append_edges() function returns a boolean array which can be used to set edge properties for the newly added edges without risking changing any of the properties of existing edges. For example, to colour the newly added edges:

new_edges = edge_network.append_edges(
                [(index_a, index_b), (index_a, index_c)]
            )
edge_network.edge_colours[new_edges] = [[200, 0, 0, 255], [0, 0, 200, 255]]

In the above example, one colour is provided for each new edge. To colour all of the new edges the same, provide a single colour for all of the edges.

Example: appending an edge to an existing edge network

The following script allows the user to pick a point in an edge network, and then another point. It then adds a point to the edge network between these two points and colours that edge red.

from __future__ import annotations


import typing


from mapteksdk.project import Project
from mapteksdk.data import EdgeNetwork
from mapteksdk.operations import (
    primitive_pick,
    coordinate_pick,
    show_message,
    SelectablePrimitiveType,
    PickCancelledError,
)


if typing.TYPE_CHECKING:
    from mapteksdk.data import ObjectID
    from mapteksdk.operations import Primitive




def main(project: Project):
    """Allow the user to add a new edge to an edge network.


    The user is allowed to pick a point on an edge network and then another
    point. These two points are used to define the new edge.


    Parameters
    ----------
    project
      Project to use to perform the pick operations and edit the edge network.
    """
    while True:
        primitive, oid = pick_point_on_edge_network(project)


        new_point = coordinate_pick(label="Pick a new point for the existing edge")
        with project.undo(), project.edit(oid, EdgeNetwork) as edge_network:
            edge_network.append_points(new_point)
            new_edges = edge_network.append_edges(
                [primitive.index, edge_network.point_count - 1]
            )
            edge_network.edge_colours[new_edges] = [200, 0, 0, 255]




def pick_point_on_edge_network(
    project: Project,
) -> typing.Tuple[Primitive, ObjectID[EdgeNetwork]]:
    """Get the user to pick a point which is on an edge network.


    Parameters
    ----------
    project
      The project to use to perform the pick operation.


    Returns
    -------
    tuple
      Tuple containing the primitive returned by the pick operation and the
      object ID of the object the picked primitive is a part of.
    """
    primitive = primitive_pick(
        SelectablePrimitiveType.POINT, label="Pick a point on an Edge Network."
    )
    oid = project.find_object(primitive.path)
    if not oid:
        raise RuntimeError("The picked object no longer exists.")
    if not oid.is_a(EdgeNetwork):
        raise RuntimeError("This script only supports edge networks.")
    return primitive, oid




if __name__ == "__main__":
    with Project() as project:
        try:
            main(project)
        except PickCancelledError:
            # The pick was cancelled.
            pass
        except RuntimeError as error:
            show_message("Append edge", str(error))
            raise
        except Exception as error:
            show_message("Append edge", f"Unexpected error: {str(error)}")
            raise

The following animation shows this script in action:

Note
  • The script above relies on duplicate points being removed when the edge network is closed to allow for connecting edges between two existing points.

  • Duplicate edges are removed when the object is closed.

  • You cannot append edges to polylines or polygons. Appending points to such objects will also append an edge.

Facets

A facet is a primitive defined by three points. A facet in the SDK does not store the actual points, but instead references the three points by their index in the points property array. Thus a single facet is represented by an array of three point indices.

The facets of any object are stored in its facets property. Because most facet-based objects contain multiple facets, the facets property is represented as a NumPy array. Each element of the array represents a facet, which in turn is an array of the three point indices defining the facet.

Use the following properties and methods to manipulate the facets of an object:

Name Description
facet_colours Use this property to change the colour of a facet. The colour of each facet is represented by an array containing four values [R, G, B, A] where:
  • R, G and B are each values between 0 and 255.

  • A is a value between 0 and 255 where 0 represents full transparency and 255 represents no transparency.

facet_count Use this property to count the total number of facets within an object. For example, you may want to preallocate NumPy arrays to the correct length. In this case, using facet_count is advised for performance reasons with very large objects.
remove_facets(facet_indices) Use this method to remove facets from an object. Specify the facets you want to remove by using the argument facet_indices, which can be in the form of an integer or an array of integers.
facet_attributes Use this method to save or return custom facet attributes in an object. To set a particular edge attribute, you can use the notation facet_attributes[attribute_name] = data.
save_facet_attribute(attribute_name, data) Use this method to save a facet attribute and allocate data to it.
delete_facet_attribute(attribute_name) Use this method to delete a facet attribute.

Creating a surface

A Surface is the simplest facet-based spatial object. We can use it to illustrate how to store facets with the example below.

from mapteksdk.project import Project
from mapteksdk.data import Surface

project = Project()

with project.new("surfaces/cube", Surface) as new_surface:
  new_surface.points = [[-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1],
                        [-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1]]
  new_surface.facets = [[0, 1, 2], [2, 0, 3], # The bottom face.
                        [4, 5, 6], [6, 4, 7], # The top face.
                        [0, 1, 5], [5, 4, 0], # The front face.
                        [1, 2, 5], [5, 6, 2], # The right face.
                        [2, 3, 7], [7, 6, 2], # The back face.
                        [0, 3, 7], [4, 7, 0]] # The left face.

Appending facets

The Surface.append_facets() function allows you to append facets to an existing surface without any risk of accidentally editing any of the existing facets or their properties. To append a single facet to a surface, simply give the function a sequence containing the indices of the three points which define the facet:

surface.append_facets((index_a, index_b, index_c))

To efficiently append multiple facets at once, collect the facets to append into a sequence and then pass the entire sequence into a single call to the append_facets() function:

surface.append_facets(
    [(index_a, index_b, index_c), (index_a, index_b, index_d)])

The append_facets() function returns a boolean array which can be used to assign the properties for the newly appended facets. For example, to colour the newly appended facets without potentially changing the colours of any pre-existing facets:

surface.append_facets((index_a, index_b, index_c))
new_facets = surface.append_facets(
                 [(index_a, index_b, index_c), (index_a, index_b, index_d)]
             )
surface.facet_colours[new_facets] = [[200, 0, 0, 255], [0, 200, 0, 255]]

Note that one colour was provided for each facet. To instead colour all the new facets the same colour, provide a single colour.

Example: appending facets to an existing surface

The following script requires the user to pick a point on a surface, and then two other points. It then adds a new facet to the surface which is defined by the three picked points. The new facet is coloured red.

from __future__ import annotations


import typing


from mapteksdk.project import Project
from mapteksdk.data import Surface
from mapteksdk.operations import (
    primitive_pick,
    coordinate_pick,
    show_message,
    SelectablePrimitiveType,
    PickCancelledError,
)


if typing.TYPE_CHECKING:
    from mapteksdk.data import ObjectID
    from mapteksdk.operations import Primitive




def main(project: Project):
    """Allow the user to add a new facet to a surface.


    The user is allowed to pick a point on an surface and then another
    two points. These three points are used to define the new facet.


    Parameters
    ----------
    project
      Project to use to perform the pick operations and edit the surface.
    """
    while True:
        primitive, oid = pick_point_on_surface(project)


        second_point = coordinate_pick(label="Pick a second point for the new facet")
        third_point = coordinate_pick(label="Pick a third point for the new facet")
        with project.undo(), project.edit(oid, Surface) as surface:
            surface.append_points(second_point, third_point)
            new_facets = surface.append_facets(
                [primitive.index, surface.point_count - 2, surface.point_count - 1]
            )
            surface.facet_colours[new_facets] = [200, 0, 0, 255]




def pick_point_on_surface(
    project: Project,
) -> typing.Tuple[Primitive, ObjectID[Surface]]:
    """Get the user to pick a point which is on a surface.


    Parameters
    ----------
    project
      The project to use to perform the pick operation.


    Returns
    -------
    tuple
      Tuple containing the primitive returned by the pick operation and the
      object ID of the object the picked primitive is a part of.
    """
    primitive = primitive_pick(
        SelectablePrimitiveType.POINT, label="Pick a point on a Surface."
    )
    oid = project.find_object(primitive.path)
    if not oid:
        raise RuntimeError("The picked object no longer exists.")
    if not oid.is_a(Surface):
        raise RuntimeError("This script only supports surfaces.")
    return primitive, oid




if __name__ == "__main__":
    with Project() as project:
        try:
            main(project)
        except PickCancelledError:
            # The pick was cancelled.
            pass
        except RuntimeError as error:
            show_message("Append facet", str(error))
            raise
        except Exception as error:
            show_message("Append facet", f"Unexpected error: {str(error)}")
            raise

The animation below demonstrates the script in action:

Note
  • The script above relies on duplicate points being removed when the surface is closed to allow for connecting facets between existing points.

  • Duplicate facets are removed when the object is closed.

Cells

A cell is a primitive containing four points. The Maptek Python SDK represents a cell as an array containing the indices of four points.

Cells are point-based objects. Therefore, the cells of any object are set and accessed using the points property. We define points for a cell to be an array of n*4 points, where n is the number of cells. Below is an example of points for a cell:

You can only access the cells of an object via the cells property. The cells property is a read-only property. Because most cell-based objects contain multiple cells, the cells property is represented as a NumPy array. Each element of the array represents a cell, which in turn is an array of the four point indices defining the cell.

A GridSurface is the simplest cell-based spatial object. We can use it to illustrate how to create cells with the example below.

from mapteksdk.project import Project
from mapteksdk.data import GridSurface
project = Project()
points = [[1.1, 1.15, 1.11], [1.98, 1.17, 1.08], [3.02, 1.13, 1.07],
          [1.08, 1.99, 1.07], [2.01, 2.03, 1.37], [3.00, 2.11, 1.33],
          [1.13, 3.08, 1.08], [2.00, 3.01, 0.99], [3.18, 3.07, 1.34]]
with project.new("surfaces/grid_surface", GridSurface(
        major_dimension_count=3, minor_dimension_count=3
        )) as new_grid:
    new_grid: GridSurface # Added to enable IntelliSense
    new_grid.points = points
    # Accessing cells requires PointStudio 2021.1 or higher,
    # SDK version 1.2 (from Extend Plugins 2021.1).
    print(new_grid.cells)

Use the following properties and methods to manipulate the cells of an object:

Name Description
major_dimension_count Use this property to define the number of rows of cells in a cell-based object.
minor_dimension_count Use this property to define the number of columns of cells in a cell-based object.
cell_count Use this property to return the total number of cells in an object.
cell_point_count Use this property to return the number of cell points in a cell-based object. This is usually equal to the major_dimension_count multiplied by the minor_dimension count properties, but may be less if the object contains invalid points.
cell_visibility Use this property to set the visibility of each cell. This is represented by an array of boolean values, where a False value indicates that the cell is invisible and a True value indicates that the cell is visible.
cell_attributes Use this method to save or return custom cell attributes in an object. To set a particular cell attribute, you can use the notation cell_attributes[attribute_name] = data.
save_cell_attribute(attribute_name, data) Use this method to save a cell attribute and allocate data to it.
delete_cell_attribute(attribute_name) Use this method to delete a cell attribute.

Blocks

A block is a cuboid with a size in each of the three directions and with a location defined by its centroid. The Maptek Python SDK only supports rectangular, prism-shaped blocks. Blocks are used to make more complex objects called block models. How blocks are created depends on the type of block model being created. There are three kinds of block models supported by the Maptek Python SDK, as follows:

  • Dense block models

    A kind of block model where each block has the same size. The model is dense in that a block is defined at every location within the 3D grid structure over the model’s extent. Use DenseBlockModel to represent dense blocks. See Dense Block Models for more information.

  • Subblocked block models

    A kind of block model that allows blocks to be subdivided into subblocks, allowing for the efficient representation of finer-grainer detail within the model. Use SubblockedBlockModel to represent a block model with subblocks. See Subblocked Block Models for more information.

  • Sparse blocks models
    Similar to dense block models in that each block has the same size, but the model is sparse in that blocks are only defined at given locations within the 3D grid structure. Sparse block models allow for the more efficient storage of models that contain many holes or voids. Use SparseBlockModel to build a sparse block model. See Sparse Block Models for more information.

The following is a list of properties and methods that can be used to query and configure all block models, irrespective of type:

block_count Use this read-only property to return the number of blocks in a model. This could be useful for when you want to perform an operation on a number of blocks. You can use this property to iterate over each block in a model.
block_resolution Use this read-only property to return the resolution of blocks in a block model.
block_centroids Use this read-only property to return the centroid of each block in a model.
block_sizes Use this read-only property to return the block sizes of each block in a model.
block_colours Use this property to set the colours of each block in a model. The colour of each block is represented by an array containing four values [R, G, B, A] where:
  • R, G and B are each a value between 0 and 255.

  • A is a value between 0 and 255 where 0 represents full transparency and 255 represents no transparency (fully opaque). This value is optional; you can use the format [R, G, B] for full opacity.

slice_count Use this read-only property to return the number of slices in a block model. If the block model hasn’t rotated, the number of slices in a block model will equal the number of blocks in the z-direction.
row_count Use this read-only property to return the number of rows in a block model. If the block model hasn’t been rotated, the number of rows in a block model will equal the number of blocks in the y-direction.
column_count Use this read-only property to return the number of columns in a block model. If the block model hasn’t been rotated, the number of columns in a block model will equal the number of blocks in the x-direction.
block_selection Use this property to select blocks in a block model. The selection of each block is represented by a boolean value in an array. True means that the block is selected while False means that the block is not selected. By default, block_selection is set to False.
block_visibility Use this property to set the visibility of each block in a block model. The visibility of each block is represented by a boolean value in an array. True means that the block is visible while False means that the block is not visible. By default, block_visibility is set to True.
block_attributes Use this property to set or return the custom block attributes of a model. To set a particular block attribute, you can use the notation block_attributes[attribute_name] = data.
origin Use this property to set the origin of a block model.
block_to_grid_index Use this property to retrieve the row, column and slice that contains the centroid for each block. Each item in the array is an array in the form [row, column, slice].
orientation Use this read-only property to retrieve the orientation of a block model. Returns a tuple of the form (dip, plunge, bearing).
set_orientation(dip, plunge, bearing) Use this method to set the orientation of a block model. This method takes three parameters: dip, plunge and bearing.
grid_index(start, end) Use this method to return all of the subblocks within a parent block. The parent block is defined by its row, count and slice. The start and end parameters are arrays of three items: [row, count, slice].
rotate(angle, axis) Use this method to rotate a block model by the specified angle around the specified axis.

Primitive attributes

Primitive attributes are arbitrary values associated with each primitive in an object.

Attributes vs tags

Prior to mapteksdk 1.6, the Python SDK made no distinction between primitive attributes and primitive tags.

Primitive tags:

  • Have one value per primitive of the given type

  • Contain no metadata

Primitive attributes:

  • Have one value per primitive of the given type

  • Contain metadata, such as the unit or type

The following script creates a point set with a point tag and a point attribute:

from mapteksdk.project import Project
from mapteksdk.data import PointSet, AttributeKey, DistanceUnit


def main(project: Project):
    """Main function.


    Creates a point set with a point tag and a point attribute.
    """
    key = AttributeKey.create_with_metadata(
        name="attribute",
        data_type=float,
        unit=DistanceUnit.METRE
    )
    with project.new("cad/point_attribute_demo", PointSet) as point_set:
        point_set.points = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]]


        # A point tag:
        point_set.point_attributes["tag"] = [1, 2, 3, 4]
        point_set.point_attributes[key] = [1, 2, 3, 4]


if __name__ == "__main__":
    with Project() as main_project, main_project.undo():
        main(main_project)

The point tag is created using only a name, whereas the point attribute is created using a more sophisticated attribute key object. If you right click on the created object and select “Properties of Selection” twice it places the following report in the report window:

Note
  • The tag’s values are from 1.000 to 4.000. The application doesn’t have any awareness of whether the values represent metres, feet, radians or some other unit.

  • The property’s values are from 1.000 m to 4.000 m. The application is aware that the values are in metres. This is because the AttributeKey was constructed with DistanceUnit.metre as the unit.

  • As tags do not contain metadata, they cannot have information such as the unit of the measurement.

Creating attribute keys: tags vs. properties

An AttributeKey object represents a specific point tag or property. There are two ways to construct a new attribute key.

The first way is without metadata. This creates an attribute key that represents a primitive tag:

key = AttributeKey("tag")

The second way is with metadata. This creates an attribute key that represents a primitive attribute:

key = AttributeKey.create_with_metadata(
    name="attribute",
    data_type=float
)
		

Note that when creating a new primitive tag, the following code:

point_set.point_attributes["tag"] = [1, 2, 3, 4]

is equivalent to:

point_set.point_attributes[AttributeKey("tag")] = [1, 2, 3, 4]

Metadata for primitive attributes

When creating an AttributeKey for a primitive attribute, the minimum metadata to provide is the data_type:

key = AttributeKey.create_with_metadata(
    name="attribute",
    data_type=float
)

The following data types are supported:

  • int (numerical values without decimal points)

  • float (numerical values with decimal points)

  • bool (True or False)

  • str (text)

The unit can also be specified using the DistanceUnit or AngleUnit enumerations:

distance_key = AttributeKey.create_with_metadata(
    name="distance",
    data_type=float,
    unit=DistanceUnit.METRE
)
angle_key = AttributeKey.create_with_metadata(
    name="angle",
    data_type=float,
    unit=AngleUnit.RADIANS
)

The Python SDK does not support creating primitive attributes with other units.

The other type of metadata supported is null values. This allows for indicating that specific values are used to indicate the absence of a value. For example:

# -99 represents the absence of a value.
integer_key = AttributeKey.create_with_metadata(
    name="integer",
    data_type=int,
    null_values=(-99,)
)


# NaN (Not a Number) represents the absence of a value.
float_key = AttributeKey.create_with_metadata(
    name="float",
    data_type=float,
    null_values=(math.nan,)
)


# An empty string and null represent the absence of a value.
string_key = AttributeKey.create_with_metadata(
    name="string",
    data_type=str,
    null_values=("", "null")
)

Reading and editing primitive attributes

Once a primitive attribute has been created it can be read either via its key or via its name.

For example, given the following attribute key:

key = AttributeKey.create_with_metadata(
    name="attribute",
    data_type=float,
    unit=DistanceUnit.METRE
)

Then the attribute can be read using either using the original attribute key:

values = point_set.point_attributes[key]

or by using the name of the attribute:

values = point_set.point_attributes["attribute"]

This means that once a primitive attribute has been created, it can be edited either via its name or key. Thus the following code:

point_set.point_attributes[key] = new_values

is equivalent to:

point_set.point_attributes["attribute"] = new_values

However it is can be preferable to search for the key via name before reading the value, because this will raise an error if there are no attributes with a given name rather than making a new attribute:

# This will raise a KeyError if there is no tag / property with the name
# "attribute". This ensures you do not accidentally create a tag when
# intended to edit an existing one.
key = point_set.point_attributes.get_key_by_name("attribute")
point_set.point_attributes[key] = new_values