Skip to content

Positions

positions

Positions

Positions(
    xyz: ndarray | list,
    epsg: int = 0,
    local_transformer: Transformer | None = None,
    init_local_transformer: bool = True,
    epsg_local_cart: int = 4936,
    epsg_local_geod: int = 4937,
)

Class representing (georeferenced) positions.

The Positions class can hold one or multiple 3d positions with a corresponding EPSG code for datum information. When creating a Positions instance, a transformation pipeline is created based on the passed points, which can transform the points into a local system tangent to the ellipsoid (grs80). Such a local datum is represented within this class with an EPSG code of 0 and is mainly suitable for local calculations within the Positions.

All transformations, including the local transformation, are carried out using pyproj, a python toolbox for projections and transformations.

Initialize Positions and create local transformer.

If a Positions instance is initialized directly with an EPSG code of 0, such a local transformer cannot be constructed, and a transformation into other EPSG codes is therefore not possible. Use this setting if you don't have any information about the geodetic datum of the passed points. However, if the local transformer is already known, it can be provided during the initialization of the Positions object using the local_transformer variable.

Parameters:

  • xyz (ndarray | list) –

    1- / 2- dimensional numpy array containing the coordinates of the input positions.

  • epsg (int, default: 0 ) –

    EPSG code of the datum of the input positions. Defaults to 0.

  • local_transformer (Transformer, default: None ) –

    pyproj transformer that describes the transformation to a local coordinate system. Defaults to None.

  • init_local_transformer (bool, default: True ) –

    Specifies if a local transformer should be initialized. Defaults to True.

  • epsg_local_cart (int, default: 4936 ) –

    EPSG code of the earth-centered datum that is used to construct the local transformation pipeline. In a first step, the coordinates are transformed to this coordinate frame. In this coordinate frame they are reduced by their mean position. Defaults to 4936.

  • epsg_local_geod (int, default: 4937 ) –

    In the final step of the local transformation pipeline, the positions reduced by their mean are rotated into a local system tangent to the ellipsoid. The ellipsoid is defined by this parameter using an EPSG code. Both EPSG codes, epsg_local_cart and epsg_local_geod should refer to the same datum (here ETRS89). Defaults to 4937.

Raises:

  • PointSetError –

    Gets raised if input xyz is not a numpy array.

Source code in trajectopy\core\positions.py
def __init__(
    self,
    xyz: np.ndarray | list,
    epsg: int = 0,
    local_transformer: Transformer | None = None,
    init_local_transformer: bool = True,
    epsg_local_cart: int = 4936,
    epsg_local_geod: int = 4937,
) -> None:
    """Initialize Positions and create local transformer.

    If a Positions instance is initialized directly with an EPSG code of 0, such a local transformer
    cannot be constructed, and a transformation into other EPSG codes is therefore not possible.
    Use this setting if you don't have any information about the geodetic datum of the passed points.
    However, if the local transformer is already known, it can be provided during the
    initialization of the Positions object using the local_transformer variable.

    Args:
        xyz (np.ndarray | list): 1- / 2- dimensional numpy array containing the coordinates
            of the input positions.
        epsg (int, optional): EPSG code of the datum of the input positions. Defaults to 0.
        local_transformer (Transformer, optional): pyproj transformer that describes the
            transformation to a local coordinate system. Defaults to None.
        init_local_transformer (bool, optional): Specifies if a local transformer should be
            initialized. Defaults to True.
        epsg_local_cart (int, optional): EPSG code of the earth-centered datum that is used
            to construct the local transformation pipeline. In a first step, the coordinates
            are transformed to this coordinate frame. In this coordinate frame they are reduced
            by their mean position. Defaults to 4936.
        epsg_local_geod (int, optional): In the final step of the local transformation pipeline,
            the positions reduced by their mean are rotated into a local system tangent to the
            ellipsoid. The ellipsoid is defined by this parameter using an EPSG code. Both EPSG
            codes, epsg_local_cart and epsg_local_geod should refer to the same datum (here ETRS89).
            Defaults to 4937.

    Raises:
        PointSetError: Gets raised if input xyz is not a numpy array.
    """

    if not isinstance(xyz, np.ndarray):
        xyz = np.array(xyz)

    if (len(xyz.shape) == 1 and len(xyz) != 3) or (len(xyz.shape) == 2 and xyz.shape[1] != 3):
        raise PointSetError("Input must be nx3 or 3xNone array!")

    self.xyz = xyz
    self.epsg = epsg
    self.local_transformer = local_transformer
    self.epsg_local_geod = epsg_local_geod
    self.epsg_local_cart = epsg_local_cart

    if self.epsg == 0:
        return

    # Create CRS and local transformer (none for no positions: its origin is their mean)
    if local_transformer or not init_local_transformer or len(self.xyz) == 0:
        return

    self.build_local_transformer()

pipeline_str property

pipeline_str: str

Definition of the local transformer (empty if there is none). Derived from the transformer, so that it also matches a transformer that was assigned, e.g. one taken over from other positions.

xyz property writable

xyz: ndarray

Returns the points within the Positions object.

Returns:

  • ndarray –

    np.ndarray: 2-dimensional numpy array.

x property writable

x: int | float | ndarray

Returns x coordinate(s).

The x/y/z properties will either return a one-dimensional numpy array or a single float / int depending on whether there is more than one point in the Positions object.

Returns:

  • int | float | ndarray –

    Union[int, float, np.ndarray]: X coordinate(s).

y property writable

y: int | float | ndarray

Returns y coordinate(s).

The x/y/z properties will either return a one-dimensional numpy array or a single float / int depending on whether there is more than one point in the Positions object.

Returns:

  • int | float | ndarray –

    Union[int, float, np.ndarray]: Y coordinate(s).

z property writable

z: int | float | ndarray

Returns z coordinate(s).

The x/y/z properties will either return a one-dimensional numpy array or a single float / int depending on whether there is more than one point in the Positions object.

Returns:

  • int | float | ndarray –

    Union[int, float, np.ndarray]: Z coordinate(s).

crs property

crs: CRS | None

Returns the Coordinate Reference System.

Returns:

  • CRS | None –

    CRS | None: pyproj CRS object that represents the current coordinate system (None if local).

export_local_transformer

export_local_transformer(filename: str) -> None

Exports the local transformer to a file.

Parameters:

  • filename (str) –

    Path to the output file.

Source code in trajectopy\core\positions.py
def export_local_transformer(self, filename: str) -> None:
    """Exports the local transformer to a file.

    Args:
        filename (str): Path to the output file.
    """
    with open(filename, "w", encoding="utf-8") as f:
        f.write(self.pipeline_str)

import_local_transformer

import_local_transformer(filename: str) -> None

Imports the local transformer from a file.

Parameters:

  • filename (str) –

    Path to the input file.

Source code in trajectopy\core\positions.py
def import_local_transformer(self, filename: str) -> None:
    """Imports the local transformer from a file.

    Args:
        filename (str): Path to the input file.
    """
    with open(filename, encoding="utf-8") as f:
        pipeline_str = f.read().strip()
    # empty for positions whose transformer was not known when they were saved
    self.local_transformer = Transformer.from_pipeline(proj_pipeline=pipeline_str) if pipeline_str else None

__len__

__len__() -> int

Returns number of points

Source code in trajectopy\core\positions.py
def __len__(self) -> int:
    """Returns number of points"""
    return len(self.xyz)

copy

copy() -> Positions

Deep copy

Source code in trajectopy\core\positions.py
def copy(self) -> "Positions":
    """Deep copy"""
    return copy.deepcopy(self)

__get_column

__get_column(idx: int) -> int | float | ndarray

Internal method to extract a column from xyz

This method will return one column specified by idx from the xyz array. It distinguishes between the number of points and returns either a single float or a 1-d numpy array.

Parameters:

  • idx (int) –

    index of the desired column

Returns:

  • int | float | ndarray –

    Union[int, float, np.ndarray]: Value / array of that column

Source code in trajectopy\core\positions.py
def __get_column(self, idx: int) -> int | float | np.ndarray:
    """Internal method to extract a column from xyz

    This method will return one column specified by idx from the
    xyz array.
    It distinguishes between the number of points and returns
    either a single float or a 1-d numpy array.

    Args:
        idx (int): index of the desired column

    Returns:
        Union[int, float, np.ndarray]: Value / array of that column
    """
    return self.xyz[:, idx] if len(self.xyz) > 1 else self.xyz[0][idx]

__set_column

__set_column(v: int | float | ndarray, idx: int) -> None

Internal method to change a column of xyz

This method will set a column of xyz to v.

Parameters:

  • v (Union[int, float, ndarray]) –

    Value(s) with which the column is to be filled. Either a single value or an array of exactly the same length as xyz.

  • idx (int) –

    column index in xyz

Source code in trajectopy\core\positions.py
def __set_column(self, v: int | float | np.ndarray, idx: int) -> None:
    """Internal method to change a column of xyz

    This method will set a column of xyz to v.

    Args:
        v (Union[int, float, np.ndarray]): Value(s) with which the
                                           column is to be filled.
                                           Either a single value or
                                           an array of exactly the
                                           same length as xyz.
        idx (int): column index in xyz
    """
    self.xyz[:, idx] = v

to_epsg

to_epsg(
    target_epsg: int, inplace: bool = True
) -> Positions

Performs a coordinate transformation using a target CRS.

This method will construct the required pyproj transformer and applies it in order to transform the positions to the target EPSG code.

Parameters:

  • target_epsg (int) –

    EPSG code of target CRS.

  • inplace (bool, default: True ) –

    Perform transformation in place. Defaults to True.

Raises:

  • PointSetError –

    Gets raised if it is not possible to recover from a local datum since local transformer is unknown.

Returns:

  • Positions ( Positions ) –

    Transformed positions.

Source code in trajectopy\core\positions.py
def to_epsg(self, target_epsg: int, inplace: bool = True) -> "Positions":
    """Performs a coordinate transformation using a target CRS.

    This method will construct the required pyproj transformer and applies it in order to
    transform the positions to the target EPSG code.

    Args:
        target_epsg (int): EPSG code of target CRS.
        inplace (bool, optional): Perform transformation in place. Defaults to True.

    Raises:
        PointSetError: Gets raised if it is not possible to recover from a local datum
            since local transformer is unknown.

    Returns:
        Positions: Transformed positions.
    """
    pointset = self if inplace else self.copy()

    # 0) already there
    if pointset.epsg == target_epsg:
        return pointset

    # currently in unknown frame
    local_transformer = pointset.local_transformer
    if pointset.epsg == 0 and local_transformer is None:
        raise PointSetError("Unable to recover from local frame since definition is unknown!")

    # 1) from non-local to local
    if target_epsg == 0:
        # geocentric cartesian (ITRF2014)
        if local_transformer is None:
            raise PointSetError("Unable to transform to a local frame since no local transformer is defined!")
        pointset.to_epsg(target_epsg=pointset.epsg_local_cart)

        # apply local transformer
        x, y, z = local_transformer.transform(xx=pointset.x, yy=pointset.y, zz=pointset.z)

        pointset.xyz = np.c_[x, y, z]
        pointset.epsg = target_epsg

        return pointset

    # 2) from local to non-local (intermediate step)
    if pointset.epsg == 0 and local_transformer is not None:
        # undo local transformation
        x, y, z = local_transformer.transform(
            xx=pointset.x,
            yy=pointset.y,
            zz=pointset.z,
            direction=TransformDirection.INVERSE,
        )
        xyz = np.c_[x, y, z]
        crs = _crs(pointset.epsg_local_cart)
    else:
        xyz = pointset.xyz
        crs = _crs(pointset.epsg)

    # 3) from non-local to non-local
    transformer = Transformer.from_crs(crs, _crs(target_epsg))

    transformed = transform_coordinates(transformer, xyz)
    # pyproj returns inf for positions outside the area of the coordinate system (e.g. a wrong EPSG code)
    if np.any(np.isfinite(xyz) & ~np.isfinite(transformed)):
        raise PointSetError(
            f"The positions cannot be transformed from EPSG {crs.to_epsg()} to EPSG {target_epsg}: they are "
            "outside of the valid area. Check the EPSG code of the positions."
        )

    pointset.xyz = transformed
    pointset.epsg = target_epsg

    return pointset

to_local

to_local(inplace: bool = True) -> Positions

Transform positions to a local frame tangential to the (grs80) ellipsoid.

This is equivalent to a transformation to an EPSG of 0.

Parameters:

  • inplace (bool, default: True ) –

    Perform transformation in place. Defaults to True.

Returns:

  • Positions ( Positions ) –

    2-dimensional object containing xyz of the transformed points.

Source code in trajectopy\core\positions.py
def to_local(self, inplace: bool = True) -> "Positions":
    """Transform positions to a local frame tangential to the (grs80) ellipsoid.

    This is equivalent to a transformation to an EPSG of 0.

    Args:
        inplace (bool, optional): Perform transformation in place. Defaults to True.

    Returns:
        Positions: 2-dimensional object containing xyz of the transformed points.
    """
    return self.to_epsg(target_epsg=0, inplace=inplace)

mean

mean(inplace: bool = False) -> Positions

Computes the mean of all points within the Positions object.

Parameters:

  • inplace (bool, default: False ) –

    If true, the positions object gets replaced by a single mean position. Defaults to False.

Returns:

  • Positions ( Positions ) –

    Contains the mean position.

Source code in trajectopy\core\positions.py
def mean(self, inplace: bool = False) -> "Positions":
    """Computes the mean of all points within the Positions object.

    Args:
        inplace (bool, optional): If true, the positions object gets replaced by a single mean
            position. Defaults to False.

    Returns:
        Positions: Contains the mean position.
    """
    mean_xyz = np.mean(self.xyz, axis=0)

    if inplace:
        self.xyz = mean_xyz
    return Positions(xyz=mean_xyz, epsg=self.epsg, local_transformer=self.local_transformer)

round_to

round_to(prec: float) -> Positions

Rounds all points to a given precision.

Parameters:

  • prec (float) –

    Desired rounding precision.

Returns:

  • Positions ( Positions ) –

    Contains the rounded positions.

Source code in trajectopy\core\positions.py
def round_to(self, prec: float) -> "Positions":
    """Rounds all points to a given precision.

    Args:
        prec (float): Desired rounding precision.

    Returns:
        Positions: Contains the rounded positions.
    """
    rounded = self.copy()
    rounded.xyz = np.round(rounded.xyz / prec) * prec
    return rounded

__add__

__add__(other: Positions) -> Positions

Plus operator.

Parameters:

Returns:

  • Positions ( Positions ) –

    Sum of positions.

Source code in trajectopy\core\positions.py
def __add__(self, other: "Positions") -> "Positions":
    """Plus operator.

    Args:
        other (Positions): Positions to add.

    Returns:
        Positions: Sum of positions.
    """
    xyz_add = self.xyz + other.xyz
    return Positions(xyz=xyz_add, epsg=self.epsg, local_transformer=self.local_transformer)

__sub__

__sub__(other: Positions) -> Positions

Minus operator.

Parameters:

  • other (Positions) –

    Positions to subtract.

Returns:

  • Positions ( Positions ) –

    Difference of positions.

Source code in trajectopy\core\positions.py
def __sub__(self, other: "Positions") -> "Positions":
    """Minus operator.

    Args:
        other (Positions): Positions to subtract.

    Returns:
        Positions: Difference of positions.
    """
    xyz_sub = self.xyz - other.xyz
    return Positions(xyz=xyz_sub, epsg=self.epsg, local_transformer=self.local_transformer)

__key

__key() -> tuple

Key generation used for hash generation.

Returns:

  • tuple ( tuple ) –

    Key tuple for hashing.

Source code in trajectopy\core\positions.py
def __key(self) -> tuple:
    """Key generation used for hash generation.

    Returns:
        tuple: Key tuple for hashing.
    """
    if len(self.xyz) == 0:
        return (self.epsg, 0.0, 0.0, 0.0, self.pipeline_str)

    return (
        self.epsg,
        float(self.xyz[:, 0] @ self.xyz[:, 0]),
        float(self.xyz[:, 1] @ self.xyz[:, 1]),
        float(self.xyz[:, 2] @ self.xyz[:, 2]),
        self.pipeline_str,
    )

__eq__

__eq__(other: object) -> bool

Equality comparison.

Parameters:

  • other (object) –

    Object to compare with.

Returns:

  • bool ( bool ) –

    True if equal, False otherwise.

Source code in trajectopy\core\positions.py
def __eq__(self, other: object) -> bool:
    """Equality comparison.

    Args:
        other: Object to compare with.

    Returns:
        bool: True if equal, False otherwise.
    """
    if isinstance(other, Positions):
        return self.__key() == other.__key()
    return NotImplemented

transform_coordinates

transform_coordinates(
    transformer: Transformer, xyz: ndarray, **options: Any
) -> ndarray

Transforms n x 3 coordinates with a pyproj transformer.

A single point is passed as numbers: older pyproj versions treat one-element arrays as a point and convert them to numbers in a way that NumPy deprecated (and will refuse).

Parameters:

  • transformer (Transformer) –

    pyproj transformer.

  • xyz (ndarray) –

    Coordinates, n x 3.

  • **options (Any, default: {} ) –

    Further arguments of Transformer.transform, e.g. direction.

Returns:

  • ndarray –

    np.ndarray: Transformed coordinates, n x 3.

Source code in trajectopy\core\positions.py
def transform_coordinates(transformer: Transformer, xyz: np.ndarray, **options: Any) -> np.ndarray:
    """Transforms n x 3 coordinates with a pyproj transformer.

    A single point is passed as numbers: older pyproj versions treat one-element arrays as a point and convert
    them to numbers in a way that NumPy deprecated (and will refuse).

    Args:
        transformer (Transformer): pyproj transformer.
        xyz (np.ndarray): Coordinates, n x 3.
        **options: Further arguments of Transformer.transform, e.g. direction.

    Returns:
        np.ndarray: Transformed coordinates, n x 3.
    """
    if len(xyz) == 1:
        x, y, z = (float(value) for value in xyz[0])
        return np.array([transformer.transform(x, y, z, **options)], dtype=float)
    return np.column_stack(transformer.transform(xyz[:, 0], xyz[:, 1], xyz[:, 2], **options)).astype(float)