(writing-filter)=

# Writing a filter

PDAL can be extended through the development of filter functions.

```{seealso}
For more on filters and their role in PDAL, and their lifecycle
please refer to {ref}`overview`.
```

Every filter stage in PDAL is implemented as a plugin (sometimes referred to as
a "driver"). Filters native to PDAL, such as {ref}`filters.ferry`, are
implemented as *static* filters and are statically linked into the PDAL
library. Filters that require extra/optional dependencies, or are external to
the core PDAL codebase altogether, such as {ref}`filters.python`, are
implemented as *shared* filters, and are built as individual shared libraries,
discoverable by PDAL at runtime through DLL/.so's named by a convention and found
in the path(s) defined by `PDAL_DRIVER_PATH` environment variable.

In this tutorial, we will give a brief example of a filter, with notes on how
to make it static or shared.

```{warning}
If no points are passed to a filter, it will not be executed. For example, if a
``where`` clause were to remove all of the points, or a reader returns an empty
PointView, the filter will not be executed.
```


## The header

First, we provide a full listing of the filter header.

```{literalinclude} ../../examples/writing-filter/MyFilter.hpp
:language: cpp
:linenos: true
```

This header should be relatively straightforward, but we will point out one
method that must be declared for the plugin interface to be satisfied.

```{literalinclude} ../../examples/writing-filter/MyFilter.hpp
:language: cpp
:lines: 16
```

In many instances, you should be able to copy this header template verbatim,
changing only the filter class name, includes, and member functions/variables
as required by your implementation.

## The source

Again, we start with a full listing of the filter source.

```{literalinclude} ../../examples/writing-filter/MyFilter.cpp
:language: cpp
:linenos: true
```

For your filter to be available to PDAL at runtime, it must adhere to the PDAL
plugin interface. As a convenience, we provide macros to do just this.

We begin by creating a `PluginInfo` struct containing three identifying
elements - the filter name, description, and a link to documentation.

```{literalinclude} ../../examples/writing-filter/MyFilter.cpp
:language: cpp
:linenos: true
:lines: 10-15
```

PDAL requires that filter names always begin with `filters.`, and end with a
string that uniquely identifies the filter. The description will be displayed
to users of the PDAL CLI (`pdal --drivers`).  When making a shared plugin,
the name of the shared library must correspond with the name of the filter
provided here.  The name of the generated shared object must be

```
libpdal_plugin_filter_<filter name>.<shared library extension>
```

Next, we pass the following to the `CREATE_SHARED_STAGE` macro, passing in
the name of the stage and the `PluginInfo` struct.

```{literalinclude} ../../examples/writing-filter/MyFilter.cpp
:language: cpp
:lines: 17
```

To create a static stage, we simply change `CREATE_SHARED_STAGE` to
`CREATE_STATC_STAGE`.

Finally, we implement a method to get the plugin name, which is primarily used
by the PDAL CLI when using the `--drivers` or `--options` arguments.

```{literalinclude} ../../examples/writing-filter/MyFilter.cpp
:language: cpp
:linenos: true
:lines: 19
```

Now that the filter has implemented the proper plugin interface, we will begin
to implement some methods that actually implement the filter. The `addArgs()`
method is used to register and bind any provided options to the stage. Here, we
get the value of `param`, if provided, else we populate `m_value` with the
default value of `1.0`. Option names, descriptions, and default values
specified in `addArgs()` will be displayed via the PDAL CLI with the
`--options` argument.

```{literalinclude} ../../examples/writing-filter/MyFilter.cpp
:language: cpp
:linenos: true
:lines: 21-24
```

In `addDimensions()` we make sure that the known `Intensity` dimension is
registered. We can also add a custom dimension, `MyDimension`, which will be
populated within `run()`.

```{literalinclude} ../../examples/writing-filter/MyFilter.cpp
:language: cpp
:linenos: true
:lines: 26-31
```

Finally, we define `run()`, which takes as input a `PointViewPtr` and
returns a `PointViewSet`. It is here that we can transform existing
dimensions, add data to new dimensions, or selectively add/remove individual
points.

We suggest you take a closer look at our existing filters to get an idea of the
power of the `Filter` stage and inspiration for your own filters!

## Compilation

Set up a `CMakeLists.txt` file to compile your filter against PDAL:

```{literalinclude} ../../examples/writing-filter/CMakeLists.txt
:language: cmake
:linenos: true
```

```{note}
CMakeLists.txt contents may vary slightly depending on your project
requirements, operating system, and compilter.
```

## Stand-alone program

An example of a standalone program that will read a point cloud from disk, apply
a filter, and write it back to disk to a new file is given in
`examples/filter-streamer`. This will also show how to adjust the offset and
scale of points in a way that is consistent with the filtering method.
