\( \newcommand{\E}{\mathrm{E}} \) \( \newcommand{\A}{\mathrm{A}} \) \( \newcommand{\R}{\mathrm{R}} \) \( \newcommand{\N}{\mathrm{N}} \) \( \newcommand{\Q}{\mathrm{Q}} \) \( \newcommand{\Z}{\mathrm{Z}} \) \( \def\ccSum #1#2#3{ \sum_{#1}^{#2}{#3} } \def\ccProd #1#2#3{ \sum_{#1}^{#2}{#3} }\)

CGAL 4.8 - 2D Alpha Shapes

All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Groups Pages
User Manual

Author
  Tran Kai Frank Da

Assume we are given a set \( S\) of points in 2D or 3D and we'd like to have something like "the shape formed by these points." This is quite a vague notion and there are probably many possible interpretations, the \( \alpha\)-shape being one of them. Alpha shapes can be used for shape reconstruction from a dense unorganized set of data points. Indeed, an \( \alpha\)-shape is demarcated by a frontier, which is a linear approximation of the original shape [1].

As mentioned in Edelsbrunner's and Mücke's paper [2], one can intuitively think of an \( \alpha\)-shape as the following. Imagine a huge mass of ice-cream making up the space \( \mathbb{R}^3\) and containing the points as "hard" chocolate pieces. Using one of these sphere-formed ice-cream spoons we carve out all parts of the ice-cream block we can reach without bumping into chocolate pieces, thereby even carving out holes in the inside (e.g. parts not reachable by simply moving the spoon from the outside). We will eventually end up with a (not necessarily convex) object bounded by caps, arcs and points. If we now straighten all "round" faces to triangles and line segments, we have an intuitive description of what is called the \( \alpha\)-shape of \( S\). Here's an example for this process in 2D (where our ice-cream spoon is simply a circle):

And what is \( \alpha\) in the game? \( \alpha\) is the squared radius of the carving spoon. A very small value will allow us to eat up all of the ice-cream except the chocolate points themselves. Thus we already see that the \( \alpha\)-shape degenerates to the point-set \( S\) for \( \alpha \rightarrow 0\). On the other hand, a huge value of \( \alpha\) will prevent us even from moving the spoon between two points since it's way too large. So we will never spoon up ice-cream lying in the inside of the convex hull of \( S\), and hence the \( \alpha\)-shape for \( \alpha \rightarrow \infty\) is the convex hull of \( S\).

# Definitions

We distinguish two versions of alpha shapes. Basic alpha shapes are based on the Delaunay triangulation. Weighted alpha shapes are based on its generalization, the regular triangulation, replacing the euclidean distance by the power to weighted points.

There is a close connection between alpha shapes and the underlying triangulations. More precisely, the \( \alpha\)-complex of \( S\) is a subcomplex of this triangulation of \( S\), containing the \( \alpha\)-exposed \( k\)-simplices, \( 0 \leq k \leq d\). A simplex is \( \alpha\)-exposed, if there is an open disk (resp. ball) of radius \( \sqrt{\alpha}\) through the vertices of the simplex that does not contain any other point of \( S\), for the metric used in the computation of the underlying triangulation. The corresponding \( \alpha\)-shape is defined as the underlying interior space of the \( \alpha\)-complex (see [2]).

In general, an \( \alpha\)-complex is a non-connected and non-pure polytope, it means, that one \( k\)-simplex, \( 0 \leq k \leq d-1\) is not necessary adjacent to a \( (k+1)\)-simplex.

The \( \alpha\)-shapes of \( S\) form a discrete family, even though they are defined for all real numbers \( \alpha\) with \( 0 \leq \alpha \leq \infty\). Thus, we can represent the entire family of \( \alpha\)-shapes of \( S\) by the underlying triangulation of \( S\). In this representation each \( k\)-simplex of the underlying triangulation is associated with an interval that specifies for which values of \( \alpha\) the \( k\)-simplex belongs to the \( \alpha\)-shape. Relying on this fact, the family of \( \alpha\)-shapes can be computed efficiently and relatively easily. Furthermore, we can select an appropriate \( \alpha\)-shape from a finite number of different \( \alpha\)-shapes and corresponding \( \alpha\)-values.

# Functionality

The class `Alpha_shape_2<Dt>` represents the family of \( \alpha\)-shapes of points in a plane for all positive \( \alpha\). It maintains the underlying triangulation `Dt` which represents connectivity and order among squared radius of its faces. Each \( k\)-dimensional face of the `Dt` is associated with an interval that specifies for which values of \( \alpha\) the face belongs to the \( \alpha\)-shape. There are links between the intervals and the \( k\)-dimensional faces of the triangulation.

The class `Alpha_shape_2<Dt>` provides functions to set and get the current \( \alpha\)-value, as well as an iterator that enumerates the \( \alpha\)-values where the \( \alpha\)-shape changes.

It provides iterators to enumerate the vertices and edges that are in the \( \alpha\)-shape, and functions that allow to classify vertices, edges and faces with respect to the \( \alpha\)-shape. They can be in the interior of a face that belongs or does not belong to the \( \alpha\)-shape. They can be singular/regular, that is be on the boundary of the \( \alpha\)-shape, but not incident/incident to a triangle of the \( \alpha\)-complex.

Finally, it provides a function to determine the \( \alpha\)-value such that the \( \alpha\)-shape satisfies the following two properties, or at least the second one if there is no such \( \alpha\) that both are satisfied:

(i) The number of components equals a number of your choice and

(ii) all data points are either on the boundary or in the interior of the regularized version of the \( \alpha\)-shape (no singular edges).

The current implementation is static, that is after its construction points cannot be inserted or removed.

# Concepts and Models

We currently do not specify concepts for the underlying triangulation type. Models that work for a basic alpha-shape are the classes `Delaunay_triangulation_2` and `Triangulation_hierarchy_2` templated with a Delaunay triangulation. A model that works for a weighted alpha-shape is the class `Regular_triangulation_2`.

The triangulation needs a geometric traits class as argument. The requirements of this class are described in the concept `AlphaShapeTraits_2` for which the CGAL kernels and `Weighted_alpha_shape_euclidean_traits_2` are models.

There are no requirements on the triangulation data structure. However it must be parameterized with vertex and face classes, which are model of the concepts `AlphaShapeVertex_2` and `AlphaShapeFace_2`, by default the classes `Alpha_shape_vertex_base_2<Gt>` and `Alpha_shape_face_base_2<Gt>`.

# Examples

## Example for Basic Alpha-Shapes

The basic alpha shape needs a Delaunay triangulation as underlying triangulation `Dt`. The Delaunay triangulation class is parameterized with a geometric and a triangulation data structure traits.

For the geometric traits class we can use a CGAL kernel.

For the triangulation data structure traits, we have to choose the vertex and face classes needed for alpha shapes, namely `Alpha_shape_vertex_base_2<Gt, Vb>` and `Alpha_shape_face_base_2<Gt,Fb>`. As default vertex and face type they use `Triangulation_vertex_base_2<Gt>` and `Triangulation_face_base_2<Gt>` respectively.

File Alpha_shapes_2/ex_alpha_shapes_2.cpp

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/algorithm.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Alpha_shape_2.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::FT FT;
typedef K::Point_2 Point;
typedef K::Segment_2 Segment;
typedef CGAL::Alpha_shape_vertex_base_2<K> Vb;
typedef CGAL::Alpha_shape_face_base_2<K> Fb;
typedef CGAL::Triangulation_data_structure_2<Vb,Fb> Tds;
typedef CGAL::Delaunay_triangulation_2<K,Tds> Triangulation_2;
typedef CGAL::Alpha_shape_2<Triangulation_2> Alpha_shape_2;
typedef Alpha_shape_2::Alpha_shape_edges_iterator Alpha_shape_edges_iterator;
template <class OutputIterator>
void
alpha_edges( const Alpha_shape_2& A,
OutputIterator out)
{
for(Alpha_shape_edges_iterator it = A.alpha_shape_edges_begin();
it != A.alpha_shape_edges_end();
++it){
*out++ = A.segment(*it);
}
}
template <class OutputIterator>
bool
file_input(OutputIterator out)
{
std::ifstream is("./data/fin", std::ios::in);
if(is.fail()){
std::cerr << "unable to open file for input" << std::endl;
return false;
}
int n;
is >> n;
std::cout << "Reading " << n << " points from file" << std::endl;
CGAL::cpp11::copy_n(std::istream_iterator<Point>(is), n, out);
return true;
}
// Reads a list of points and returns a list of segments
// corresponding to the Alpha shape.
int main()
{
std::list<Point> points;
if(! file_input(std::back_inserter(points))){
return -1;
}
Alpha_shape_2 A(points.begin(), points.end(),
FT(10000),
Alpha_shape_2::GENERAL);
std::vector<Segment> segments;
alpha_edges( A, std::back_inserter(segments));
std::cout << "Alpha Shape computed" << std::endl;
std::cout << segments.size() << " alpha shape edges" << std::endl;
std::cout << "Optimal alpha: " << *A.find_optimal_alpha(1)<<std::endl;
return 0;
}

## Example for Weighted Alpha-Shapes

A weighted alpha shape, needs a regular triangulation as underlying triangulation `Dt`, and it needs a particular face class, namely `Regular_triangulation_face_base_2<Gt>`. Note that there is no special weighted alpha shape class.

File Alpha_shapes_2/ex_weighted_alpha_shapes_2.cpp

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Weighted_point.h>
#include <CGAL/Weighted_alpha_shape_euclidean_traits_2.h>
#include <CGAL/Regular_triangulation_2.h>
#include <CGAL/Alpha_shape_2.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::FT FT;
typedef K::Point_2 Point_base;
typedef CGAL::Weighted_point<Point_base,FT> Point;
typedef CGAL::Weighted_alpha_shape_euclidean_traits_2<K> Gt;
typedef CGAL::Regular_triangulation_vertex_base_2<Gt> Rvb;
typedef CGAL::Alpha_shape_vertex_base_2<Gt,Rvb> Vb;
typedef CGAL::Regular_triangulation_face_base_2<Gt> Rf;
typedef CGAL::Alpha_shape_face_base_2<Gt, Rf> Fb;
typedef CGAL::Triangulation_data_structure_2<Vb,Fb> Tds;
typedef CGAL::Regular_triangulation_2<Gt,Tds> Triangulation_2;
typedef CGAL::Alpha_shape_2<Triangulation_2> Alpha_shape_2;
typedef Alpha_shape_2::Alpha_shape_edges_iterator Alpha_shape_edges_iterator;
template <class InputIterator, class OutputIterator>
void
alpha_edges(InputIterator begin, InputIterator end,
const FT &Alpha,
bool mode,
OutputIterator out)
// Generate Alpha Shape
{
std::vector<Gt::Segment_2> V_seg;
Alpha_shape_2 A(begin,end);
if (mode)
{ A.set_mode(Alpha_shape_2::GENERAL); }
else
{ A.set_mode(Alpha_shape_2::REGULARIZED); };
A.set_alpha(Alpha);
for(Alpha_shape_edges_iterator it = A.alpha_shape_edges_begin();
it != A.alpha_shape_edges_end();
++it){
*out++ = A.segment(*it);
}
}
bool
file_input(std::list<Point>& L)
{
std::ifstream is("./data/fin", std::ios::in);
if(is.fail())
{
std::cerr << "unable to open file for input" << std::endl;
return false;
}
CGAL::set_ascii_mode(is);
int n;
is >> n;
std::cout << "Reading " << n << " points" << std::endl;
for( ; n>0 ; n--)
{
Point_base p(0., 0.);
is >> p;
if(is) {
L.push_back(Point (p, FT(10)));
} else {
return false;
}
}
std::cout << "Points inserted" << std::endl;
return true;
}
// Reads a list of points and returns a list of segments corresponding to
// the weighted Alpha Shape.
int main()
{
std::list<Point> points;
file_input(points);
std::vector<Gt::Segment_2> segments;
alpha_edges(points.begin(), points.end(),
FT(10000),Alpha_shape_2::GENERAL,
std::back_inserter(segments));
std::cout << segments.size() << " alpha shape edges." << std::endl;
return 0;
}