I have a triangular mesh written down on a piece of paper with a bunch of nodes and their connectivity. I'd like to put in this mesh into CGAL, to, e.g., play around with Lloyd smoothing, computing the Voronoi diagram etc.
I'm looking at Mesh_2/mesh_optimization.cpp
which apparently allows the inserting of points
CDT cdt;
Vertex_handle va = cdt.insert(Point(-2,0));
Vertex_handle vb = cdt.insert(Point(0,-2));
Vertex_handle vc = cdt.insert(Point(2,0));
Vertex_handle vd = cdt.insert(Point(0,1));
but not cells (triangles).
Any hint on where to start?
I ran into a similar situation. Caveat: I didn't need to use the those specific algorithms, so I'm not sure how this solution works with those.
You can use the CGAL::Triangulation_data_structure_2 (wiki) to manually specify faces, neighbours, and vertices.
#include <CGAL/Triangulation_2.h>
#include <CGAL/Projection_traits_xy_3.h>
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Triangulation_ds_face_base_2.h>
#include <iostream>
int main()
{
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_3 Point_3;
typedef CGAL::Projection_traits_xy_3<K> Gt; //allows for using 2D algorithms on the 3D points (good for terrain)
typedef CGAL::Triangulation_vertex_base_2<Gt> Vb;
typedef CGAL::Constrained_triangulation_face_base_2<Gt> Fb;
typedef CGAL::Triangulation_data_structure_2<Vb, Fb> triangulation;
triangulation tri;
//read in x,y,z from a file here
//create a new vertex and insert into a 2D triangulation
//vertex 0
int x,y,z;
x = 0;
y = 0;
z=0;
Point_3 pt0( x,y,z);
auto Vh0 = tri.create_vertex(); // type of Vh0 is triangulation::Vertex_handle
Vh0->set_point(pt0);
//vertex 1
x = 1;
y = 0;
z=0;
Point_3 pt1( x,y,z);
auto Vh1 = tri.create_vertex();
Vh1->set_point(pt1);
//vertex 2
x = 0.5;
y = 0.5;
z=0;
Point_3 pt2( x,y,z);
auto Vh2= tri.create_vertex();
Vh2->set_point(pt2);
auto face = tri.create_face(Vh0,Vh1,Vh1); // type of face is triangulation::Face_handle
Vh0->set_face(face);
Vh1->set_face(face);
Vh2->set_face(face);
std::cout << "#veterx=" << tri.number_of_vertices() << std::endl;
std::cout << "#faces=" << tri.faces().size() << std::endl;
}
and if you have more than 1 face, you can set face neighbours
face->set_neighbors(face0,face1,face2);
CMakeLists.txt to build the example:
cmake_minimum_required (VERSION 3.12)
project (tri)
find_package(CGAL REQUIRED)
include(${CGAL_USE_FILE})
include_directories(
${CGAL_INCLUDE_DIRS}
${CGAL_3RD_PARTY_INCLUDE_DIRS})
add_executable(main
main.cpp)
target_link_libraries(main ${CGAL_3RD_PARTY_LIBRARIES})
Related
I'm trying to do a hyperbolic Delaunay triangulation with CGAL. Below is my code. Rcpp is a library to use C++ with R.
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Hyperbolic_Delaunay_triangulation_2.h>
#include <CGAL/Hyperbolic_Delaunay_triangulation_traits_2.h>
#include <CGAL/Triangulation_vertex_base_with_id_2.h>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Hyperbolic_Delaunay_triangulation_traits_2<K> HDtt;
typedef HDtt::Point_2 HPoint;
typedef CGAL::Triangulation_data_structure_2<
CGAL::Triangulation_vertex_base_with_id_2<HDtt>,
CGAL::Hyperbolic_triangulation_face_base_2<HDtt>>
HTds;
typedef CGAL::Hyperbolic_Delaunay_triangulation_2<HDtt, HTds> HDt;
Rcpp::IntegerMatrix htest(const Rcpp::NumericMatrix points) {
std::vector<HPoint> hpts;
const unsigned npoints = points.ncol();
hpts.reserve(npoints);
for(unsigned i = 0; i != npoints; i++) {
const Rcpp::NumericVector pt = points(Rcpp::_, i);
hpts.emplace_back(HPoint(pt(0), pt(1)));
}
HDt hdt;
hdt.insert(hpts.begin(), hpts.end());
const size_t nedges = hdt.number_of_hyperbolic_edges();
Rcpp::IntegerMatrix Edges(2, nedges);
size_t i = 0;
for(HDt::All_edges_iterator ed = hdt.all_edges_begin();
ed != hdt.all_edges_end(); ++ed) {
Rcpp::IntegerVector edge_i(2);
HDt::Vertex_handle sVertex = ed->first->vertex(HDt::cw(ed->second));
edge_i(0) = sVertex->id();
HDt::Vertex_handle tVertex = ed->first->vertex(HDt::ccw(ed->second));
edge_i(1) = tVertex->id();
Edges(Rcpp::_, i) = edge_i;
i++;
}
return Edges;
}
I feed this function with some points in the unit circle. Then it returns an integer matrix, which is supposed to be the edges, but all entries of the matrix I get are equal to a huge integer instead. I also tried std::cout << sVertex->id() and this prints this huge integer.
There are isolated edges because the authors choose to filter out triangles whose circumscribing circle is "not compact" (i.e., it is not included in the Poincaré disk).
See the user manual
https://doc.cgal.org/latest/Hyperbolic_triangulation_2/index.html#HT2_Euclidean_and_hyperbolic_Delaunay_triangulations
" A Euclidean Delaunay face is hyperbolic if its circumscribing circle is contained in ℍ2. "
For more details: https://jocg.org/index.php/jocg/article/view/2926
Ok, I found a way. One has to set the ids, they were unset and that's why I got a huge integer. Also one has to take the vertices of the triangulation, because while these are the same as the input vertices, they are in another order.
......
HDt hdt;
hdt.insert(hpts.begin(), hpts.end());
Rcpp::NumericMatrix Vertices(2, npoints);
int index = 0;
for(HDt::All_vertices_iterator vd = hdt.all_vertices_begin();
vd != hdt.all_vertices_end(); ++vd){
vd->id() = index;
HPoint pt = vd->point();
Vertices(0, index) = pt.x();
Vertices(1, index) = pt.y();
index++;
}
......
However I don't know why there is an isolated edge:
I would like to extract the medial axis of a MultiPolygon using CGAL.
Looking through the CGAL documentation I only see functions to make a straight skeleton.
How can I use CGAL to get the medial axis?
Overview
If we have a polygon (a planar domain with straight edges, possibly convex) then the edges which form the medial axis of a polygon are a subset of the multipolygon's Voronoi diagram.
Our strategy, then, is to find the polygon's Voronoi diagram and then remove edges from the diagram until we are left with the medial axis. If we have a MultiPolygon we just repeat the process for each of its constituent polygons.
I'll demonstrate the process visually below and then present code to accomplish it. The data for the test polygon I'll use is this WKT:
MULTIPOLYGON(((223.83073496659313 459.9703924976702,256.1247216035629 304.08821449578033,1.1135857461033538 187.63424160514955,468.8195991091309 189.21374898389445,310.69042316258424 318.7630937196408,432.07126948775 451.93407043677666,453.2293986636971 612.1086753947116,32.29398663697134 616.325100949687,223.83073496659313 459.9703924976702)))
The polygon looks like this:
The full Voronoi diagram of the polygon looks like this:
At this point we can consider three types of vertices in the Voronoi diagram: those that are properly within the polygon, those which lie on its border, and those which are properly outside the polygon. It's visually obvious that the medial axis does not contain any edge of the Voronoi diagram which is defined in part by a point which is properly outside the polygon. Therefore, we remove these exterior edges. Leaving us with this:
Not all of the Voronoi edges in the above figure are part of the medial axis. It turns out that those edges which are in part defined by the concave vertices (aka "reflex vertices") of the polygon are not part of the medial axis. The concave vertices of the polygon are those that "point inwards". Computationally, we can find them by using cross-products. (Incidentally, the area of a polygon can be calculated by summing the cross-products of each of its vertices.)
Removing the edges discussed above gives us the final medial axis:
The Code
The following C++ code calculates the medial axis of a multipolygon.
// Compile with: clang++ -DBOOST_ALL_NO_LIB -DCGAL_USE_GMPXX=1 -O3 -g -Wall -Wextra -pedantic -march=native -frounding-math main.cpp -lgmpxx -lmpfr -lgmp
#include <CGAL/Boolean_set_operations_2.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/IO/WKT.h>
#include <CGAL/Polygon_with_holes_2.h>
#include <CGAL/Segment_Delaunay_graph_2.h>
#include <CGAL/Segment_Delaunay_graph_adaptation_policies_2.h>
#include <CGAL/Segment_Delaunay_graph_adaptation_traits_2.h>
#include <CGAL/Segment_Delaunay_graph_traits_2.h>
#include <CGAL/squared_distance_2.h>
#include <CGAL/Voronoi_diagram_2.h>
#include <algorithm>
#include <deque>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <set>
#include <stdexcept>
#include <unordered_set>
typedef CGAL::Exact_predicates_exact_constructions_kernel K;
typedef CGAL::Segment_Delaunay_graph_traits_2<K> Gt;
typedef CGAL::Segment_Delaunay_graph_2<Gt> SDG2;
typedef CGAL::Segment_Delaunay_graph_adaptation_traits_2<SDG2> AT;
typedef CGAL::Segment_Delaunay_graph_degeneracy_removal_policy_2<SDG2> AP;
typedef CGAL::Voronoi_diagram_2<SDG2, AT, AP> VoronoiDiagram;
typedef AT::Site_2 Site_2;
typedef AT::Point_2 Point_2;
typedef VoronoiDiagram::Locate_result Locate_result;
typedef VoronoiDiagram::Vertex_handle Vertex_handle;
typedef VoronoiDiagram::Face_handle Face_handle;
typedef VoronoiDiagram::Halfedge_handle Halfedge_handle;
typedef VoronoiDiagram::Ccb_halfedge_circulator Ccb_halfedge_circulator;
typedef VoronoiDiagram::Bounded_halfedges_iterator BHE_Iter;
typedef VoronoiDiagram::Halfedge Halfedge;
typedef VoronoiDiagram::Vertex Vertex;
typedef CGAL::Polygon_with_holes_2<K> Polygon;
typedef std::deque<Polygon> MultiPolygon;
/// Creates a hash of a Point_2, used for making O(1) point lookups
// struct Point2Hash {
// size_t operator()(const Point_2 &pt) const {
// std::hash<double> hasher;
// auto seed = hasher(pt.x());
// // boost::hash_combine from https://stackoverflow.com/q/35985960/752843
// seed ^= hasher(pt.y()) + 0x9e3779b9 + (seed<<6) + (seed>>2);
// return seed;
// }
// };
typedef std::set<Point_2> Point2_Set;
typedef std::map<Vertex_handle, int> VH_Int_Map;
/// Holds a more accessible description of the Voronoi diagram
struct MedialData {
/// Map of vertices comprising the Voronoi diagram
VH_Int_Map vertex_handles;
/// List of edges in the diagram (pairs of the vertices above)
std::vector<std::pair<int, int>> edges;
/// Medial axis up governor. 1:1 correspondance with edges above.
std::vector<VoronoiDiagram::Delaunay_graph::Vertex_handle> ups;
/// Medial axis down governor. 1:1 correspondance with edges above.
std::vector<VoronoiDiagram::Delaunay_graph::Vertex_handle> downs;
};
/// Read well-known text from #p filename to obtain shape boundary
MultiPolygon get_wkt_from_file(const std::string& filename){
std::ifstream fin(filename);
MultiPolygon mp;
CGAL::read_multi_polygon_WKT(fin, mp);
if(mp.empty()){
throw std::runtime_error("WKT file '" + filename + "' was empty!");
}
for(const auto &poly: mp){
if(poly.outer_boundary().size()==0){
throw std::runtime_error("WKT file '" + filename + "' contained a polygon without an outer boundary!");
}
}
return mp;
}
/// Converts a MultiPolygon into its corresponding Voronoi diagram
VoronoiDiagram convert_mp_to_voronoi_diagram(const MultiPolygon &mp){
VoronoiDiagram vd;
const auto add_segments_to_vd = [&](const auto &poly){
for(std::size_t i=0;i<poly.size();i++){
std::cerr<<i<<" "<<std::fixed<<std::setprecision(10)<<poly[i]<<std::endl;
// Modulus to close the loop
vd.insert(
Site_2::construct_site_2(poly[i], poly[(i+1)%poly.size()])
);
}
};
for(const auto &poly: mp){ // For each polygon in MultiPolygon
std::cout<<poly<<std::endl; // Print polygon to screen for debugging
add_segments_to_vd(poly.outer_boundary()); // Add the outer boundary
for(const auto &hole : poly.holes()){ // And any holes
add_segments_to_vd(hole);
}
}
if(!vd.is_valid()){
throw std::runtime_error("Voronoi Diagram was not valid!");
}
return vd;
}
/// Find #p item in collection #p c or add it if not present.
/// Returns the index of `item`'s location
int find_or_add(VH_Int_Map &c, const Vertex_handle &item){
// Map means we can do this in log(N) time
if(c.count(item) == 0){
c.emplace(item, c.size());
return c.size() - 1;
}
return c.at(item);
}
/// Convert a map of <T, int> pairs to a vector of `T` ordered by increasing int
std::vector<Vertex_handle> map_to_ordered_vector(const VH_Int_Map &m){
std::vector<std::pair<Vertex_handle, int>> to_sort(m.begin(), m.end());
to_sort.reserve(m.size());
std::sort(to_sort.begin(), to_sort.end(), [](const auto &a, const auto &b){
return a.second < b.second;
});
std::vector<Vertex_handle> ret;
ret.reserve(to_sort.size());
std::transform(begin(to_sort), end(to_sort), std::back_inserter(ret),
[](auto const& pair){ return pair.first; }
);
return ret;
}
/// Find vertex handles which are in the interior of the MultiPolygon
std::set<Vertex_handle> identify_vertex_handles_inside_mp(
const VoronoiDiagram &vd,
const MultiPolygon &mp
){
// Used to accelerate interior lookups by avoiding Point-in-Polygon checks for
// vertices we've already considered
std::set<Vertex_handle> considered;
// The set of interior vertices we are building
std::set<Vertex_handle> interior;
for (
auto edge_iter = vd.bounded_halfedges_begin();
edge_iter != vd.bounded_halfedges_end();
edge_iter++
) {
// Determine if an orientation implies an interior vertex
const auto inside = [](const auto &orientation){
return orientation == CGAL::ON_ORIENTED_BOUNDARY || orientation == CGAL::POSITIVE;
};
// Determine if a vertex is in the interior of the multipolygon and, if so,
// add it to `interior`
const auto vertex_in_mp_interior = [&](const Vertex_handle& vh){
// Skip vertices which have already been considered, since a vertex may
// be connected to multiple halfedges
if(considered.count(vh)!=0){
return;
}
// Ensure we don't look at a vertex twice
considered.insert(vh);
// Determine if the vertex is inside of any polygon of the MultiPolygon
const auto inside_of_a_poly = std::any_of(
mp.begin(), mp.end(), [&](const auto &poly) {
return inside(CGAL::oriented_side(vh->point(), poly));
}
);
// If the vertex was inside the MultiPolygon make a note of it
if(inside_of_a_poly){
interior.insert(vh);
}
};
// Check both vertices of the current halfedge of the Voronoi diagram
vertex_in_mp_interior(edge_iter->source());
vertex_in_mp_interior(edge_iter->target());
}
return interior;
}
/// The medial axis is formed by building a Voronoi diagram and then removing
/// the edges of the diagram which connect to the concave points of the
/// MultiPolygon. Here, we identify those concave points
Point2_Set identify_concave_points_of_mp(const MultiPolygon &mp){
Point2_Set concave_points;
// Determine cross-product, given three points. The sign of the cross-product
// determines whether the point is concave or convex.
const auto z_cross_product = [](const Point_2 &pt1, const Point_2 &pt2, const Point_2 &pt3){
const auto dx1 = pt2.x() - pt1.x();
const auto dy1 = pt2.y() - pt1.y();
const auto dx2 = pt3.x() - pt2.x();
const auto dy2 = pt3.y() - pt2.y();
return dx1 * dy2 - dy1 * dx2;
};
// Loop through all the points in a polygon, get their cross products, and
// add any concave points to the set we're building.
// `sense` should be `1` for outer boundaries and `-1` for holes, since holes
// will have points facing outward.
const auto consider_polygon = [&](const auto &poly, const double sense){
for(size_t i=0;i<poly.size()+1;i++){
const auto zcp = z_cross_product(
poly[(i + 0) % poly.size()],
poly[(i + 1) % poly.size()],
poly[(i + 2) % poly.size()]
);
if(sense*zcp < 0){
concave_points.insert(poly[(i + 1) % poly.size()]);
}
}
};
// Loop over the polygons of the MultiPolygon, as well as their holes
for(const auto &poly: mp){
// Outer boundary has positive sense
consider_polygon(poly.outer_boundary(), 1);
for(const auto &hole: poly.holes()){
// Inner boundaries (holes) have negative (opposite) sense
consider_polygon(hole, -1);
}
}
return concave_points;
}
/// Print the points which collectively comprise the medial axis
void print_medial_axis_points(const MedialData &md, const std::string &filename){
std::ofstream fout(filename);
fout<<"x,y"<<std::endl;
for (const auto &vh : map_to_ordered_vector(md.vertex_handles)) {
fout << vh->point().x() << "," << vh->point().y() << std::endl;
}
}
/// Prints the edges of the medial diagram
void print_medial_axis_edges(const MedialData &md, const std::string &filename){
std::ofstream fout(filename);
fout<<"SourceIdx,TargetIdx,UpGovernorIsPoint,DownGovernorIsPoint"<<std::endl;
for (std::size_t i = 0; i < md.edges.size(); i++) {
fout << md.edges[i].first << ","
<< md.edges[i].second << ","
<< md.ups[i]->is_point() << "," // Is up-governor a point?
<< md.downs[i]->is_point() // Is down-governor a point?
<< std::endl;
}
}
MedialData filter_voronoi_diagram_to_medial_axis(
const VoronoiDiagram &vd,
const MultiPolygon &mp
){
MedialData ret;
const auto interior = identify_vertex_handles_inside_mp(vd, mp);
const auto concave_points = identify_concave_points_of_mp(mp);
// Returns true if a point is a concave point of the MultiPolygon
const auto pconcave = [&](const Point_2 &pt){
return concave_points.count(pt) != 0;
};
// The Voronoi diagram is comprised of a number of vertices connected by lines
// Here, we go through each edge of the Voronoi diagram and determine which
// vertices it's incident on. We add these vertices to `ret.vertex_handles`
// so that they will have unique ids.
// The `up` and `down` refer to the medial axis governors - that which
// constrains each edge of the Voronoi diagram
for (
auto edge_iter = vd.bounded_halfedges_begin();
edge_iter != vd.bounded_halfedges_end();
edge_iter++
) {
const Halfedge& halfedge = *edge_iter;
const Vertex_handle& v1p = halfedge.source();
const Vertex_handle& v2p = halfedge.target();
// Filter Voronoi diagram to only the part in the interior of the
// MultiPolygon
if(interior.count(v1p)==0 || interior.count(v2p)==0){
continue;
}
// Drop those edges of the diagram which are not part of the medial axis
if(pconcave(v1p->point()) || pconcave(v2p->point())){
continue;
}
// Get unique ids for edge vertex handle that's part of the medial axis
const auto id1 = find_or_add(ret.vertex_handles, v1p);
const auto id2 = find_or_add(ret.vertex_handles, v2p);
ret.edges.emplace_back(id1, id2);
// Keep track of the medial axis governors
ret.ups.push_back(halfedge.up());
ret.downs.push_back(halfedge.down());
}
return ret;
}
int main(int argc, char** argv) {
if(argc!=2){
std::cerr<<"Syntax: "<<argv[0]<<" <Shape Boundary WKT>"<<std::endl;
return -1;
}
CGAL::set_pretty_mode(std::cout);
const auto mp = get_wkt_from_file(argv[1]);
const auto voronoi = convert_mp_to_voronoi_diagram(mp);
const auto ma_data = filter_voronoi_diagram_to_medial_axis(voronoi, mp);
print_medial_axis_points(ma_data, "voronoi_points.csv");
print_medial_axis_edges(ma_data, "voronoi_edges.csv");
return 0;
}
You can plot the resulting medial axis with this Python script:
#!/usr/bin/env python3
import matplotlib.pyplot as plt
import numpy as np
from shapely import wkt
fig, ax = plt.subplots()
# Output file from C++ medial axis code
pts = np.loadtxt("build/voronoi_points.csv", skiprows=1, delimiter=',')
fig4 = wkt.loads(open("fig4_data.wkt").read())
for geom in fig4.geoms:
xs, ys = geom.exterior.xy
ax.plot(xs, ys, '-ok', lw=4)
# Output file from C++ medial axis code
ma = np.loadtxt("build/voronoi_edges.csv", dtype=int, skiprows=1, delimiter=',')
for mal in ma:
print(mal)
ax.plot((pts[mal[0]][0], pts[mal[1]][0]), (pts[mal[0]][1], pts[mal[1]][1]), '-o')
plt.show()
I have been using boost::geometry library in a program, mostly for handling polygon objects.
I am now trying to optimize my code to scale better with larger polygons. One my functions checks for a given polygon and a given point (usually inside the polygon) the minimum and maximum distance between the point and polygon outer ring.
I do it by looping on the polygon edges:
polygon pol;
point myPoint;
double min = 9999999, max = 0;
for(auto it1 = boost::begin(bg::exterior_ring(pol)); it1 != boost::end(bg::exterior_ring(pol)); ++it1){
double distance = bg::distance(*it1, myPoint);
if(max < distance)
max = distance;
if(min > distance)
min = distance;
}
I am hoping that there are algorithms faster than this one, linear in the polygon number of edges. Is there such a thing already inside the boost::geometry library?
I'd suggest you can use the builtin strategies for finding the minimum distance between the polygon and the point:
Live On Coliru
#include <boost/geometry.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/io/io.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/algorithms/distance.hpp>
namespace bg = boost::geometry;
using point = bg::model::d2::point_xy<double>;
using polygon = bg::model::polygon<point>;
int main() {
polygon pol;
boost::geometry::read_wkt(
"POLYGON((2 1.3,2.4 1.7,2.8 1.8,3.4 1.2,3.7 1.6,3.4 2,4.1 3,5.3 2.6,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3)"
"(4.0 2.0, 4.2 1.4, 4.8 1.9, 4.4 2.2, 4.0 2.0))", pol);
point myPoint(7, 7);
double min = 9999999, max = 0;
std::cout << "Minimal distance: " << bg::distance(pol, myPoint);
}
Prints
Minimal distance: 4.71699
Further hints:
You should consider ranking the distances first using comparable_distance. As you can see the sample there suggests looping over all the sampled distances... so I don't think the library has a better offering at this time.
More sophisticated algorithms are planned, of which a number may be related to this problem:
http://boost-geometry.203548.n3.nabble.com/distance-between-geometries-td4025549.html
mailing list thread http://lists.boost.org/geometry/2013/08/2446.php
another here http://lists.boost.org/geometry/2013/09/2494.php
Note also that Boost Geometry Index has a related predicate comparable_distance_far but it's not exposed as of yet.
Summary
You can improve at least a bit by using comparable_distance here for now.
Features have been planned and it looks like there is a good chance that requesting them on the mailing list/Boost Trac will help getting them there.
For best performances you should use an RTree with boost::geometry::index. Creating the RTree has a cost, but then computing the ditance of a point to any of the (multi)polygon ring will be much faster. Example code:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <iostream>
#include <vector>
int main()
{
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
typedef bg::model::point<double, 2, bg::cs::cartesian> point;
typedef bg::model::polygon<point> polygon;
point p{ 0, 0 };
// create some polygon and fill it with data
polygon poly;
double a = 0;
double as = bg::math::two_pi<double>() / 100;
for (int i = 0; i < 100; ++i, a += as)
{
double c = cos(a);
double s = sin(a);
poly.outer().push_back(point{10 * c, 10 * s});
poly.inners().resize(1);
poly.inners()[0].push_back(point{5 * c, 5 * s});
}
// make sure it is valid
bg::correct(poly);
// create rtree containing objects of type bg::model::pointing_segment
typedef bg::segment_iterator<polygon const> segment_iterator;
typedef std::iterator_traits<segment_iterator>::value_type segment_type;
bgi::rtree<segment_type, bgi::rstar<4> > rtree(bg::segments_begin(poly),
bg::segments_end(poly));
// get 1 nearest segment
std::vector<segment_type> result;
rtree.query(bgi::nearest(p, 1), std::back_inserter(result));
BOOST_ASSERT(!result.empty());
std::cout << bg::wkt(result[0]) << ", " << bg::distance(p, result[0]) << std::endl;
return 0;
}
I'm using CGAL with Qt to draw Voronoi diagram. I used CGAL::Voronoi_diagram_2<DT,AT,AP>since I need the faces. This is the example code:
for(Face_iterator f = VD.faces_begin(); f != VD.faces_end(); f++)
{
Ccb_halfedge_circulator ec_start = (f)->ccb();
Ccb_halfedge_circulator ec = ec_start;
do {
if (!ec->has_source())
{
}
else
QpolyF << QPointF(((Halfedge_handle)ec)->source()->point().x(), ((Halfedge_handle)ec)->source()->point().y());
} while ( ++ec != ec_start );
VectPolygon.push_back(QpolyF);
QpolyF.clear();}
I need to clip the rays that has source or target in infinity. If I use the Cropped_voronoi_from_delaunay to generate voronoi it only gives the segments not the faces. these are the typedefs:
typedef K::Line_2 Line_2;
typedef CGAL::Delaunay_triangulation_2<K> Delaunay_triangulation_2;
typedef Delaunay_triangulation_2::Face_iterator dt_Face_iterator;
typedef Delaunay_triangulation_2::Edge_circulator dt_Edge_circulator;
// typedefs for defining the adaptor
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Delaunay_triangulation_2<K> DT;
typedef CGAL::Delaunay_triangulation_adaptation_traits_2<DT> AT;
typedef CGAL::Delaunay_triangulation_caching_degeneracy_removal_policy_2<DT> AP;
typedef CGAL::Voronoi_diagram_2<DT,AT,AP> VD;
// typedef for the result type of the point location
typedef AT::Site_2 Site_2;
typedef AT::Point_2 Point_2;
typedef VD::Locate_result Locate_result;
typedef VD::Vertex_handle Vertex_handle;
typedef VD::Face_handle Face_handle;
typedef VD::Face_iterator Face_iterator;
typedef VD::Halfedge_handle Halfedge_handle;
typedef VD::Ccb_halfedge_circulator Ccb_halfedge_circulator;
There is some experimental code here: http://code.google.com/p/cgal-voronoi-cropping that crop a voronoi diagram to a rectangle, the result being a HDS. See main.cpp in the test directory
I know it's possible to do it with CGAL, but I found a workaround for now. in Qt, QPolygon class has the function to find intersected polygons. Qpolygon::intersected(yourPolygon).
this is the results:
The following will generate a random point cloud, find its Voronoi diagram, crop that diagram to the cloud's bounding box, and generate well-known text polygons.
I'm not sure how to integrate this with Qt, but, presumably, once you have the polygons this part will be easy(ish).
//Finds the cropped Voronoi diagram of a set of points and saves it as WKT
//Compile with: g++ main.cpp -Wall -lCGAL -lgmp
//Author: Richard Barnes (rbarnes.org)
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Regular_triangulation_filtered_traits_2.h>
#include <CGAL/Regular_triangulation_adaptation_traits_2.h>
#include <CGAL/Regular_triangulation_adaptation_policies_2.h>
#include <CGAL/Regular_triangulation_2.h>
#include <CGAL/Voronoi_diagram_2.h>
#include <CGAL/Boolean_set_operations_2.h>
#include <CGAL/bounding_box.h>
#include <CGAL/Polygon_2.h>
#include <iostream>
#include <cstdint>
//Used to convert otherwise infinite rays into looooong line segments
const int RAY_LENGTH = 1000;
typedef CGAL::Exact_predicates_exact_constructions_kernel K;
typedef CGAL::Regular_triangulation_filtered_traits_2<K> Traits;
typedef CGAL::Regular_triangulation_2<Traits> RT2;
typedef CGAL::Regular_triangulation_adaptation_traits_2<RT2> AT;
typedef CGAL::Regular_triangulation_degeneracy_removal_policy_2<RT2> DRP;
typedef CGAL::Voronoi_diagram_2<RT2, AT, DRP> VD;
int main(int argc, char **argv){
std::vector<RT2::Weighted_point> wpoints;
std::cout.precision(4);
std::cout.setf(std::ios::fixed);
//Generated random points
for(int i=0;i<100;i++)
//Weight of 0 gives a Voronoi diagram. Non-zero weight gives a power diagram
wpoints.push_back(RT2::Weighted_point(K::Point_2(rand()%100,rand()%100), 0));
//Find the bounding box of the points. This will be used to crop the Voronoi
//diagram later.
const K::Iso_rectangle_2 bbox = CGAL::bounding_box(wpoints.begin(), wpoints.end());
//Create a Regular Triangulation from the points
RT2 rt(wpoints.begin(), wpoints.end());
rt.is_valid();
//Wrap the triangulation with a Voronoi diagram adaptor. This is necessary to
//get the Voronoi faces.
VD vd(rt);
//CGAL often returns objects that are either segments or rays. This converts
//these objects into segments. If the object would have resolved into a ray,
//that ray is intersected with the bounding box defined above and returned as
//a segment.
const auto ConvertToSeg = [&](const CGAL::Object seg_obj, bool outgoing) -> K::Segment_2 {
//One of these will succeed and one will have a NULL pointer
const K::Segment_2 *dseg = CGAL::object_cast<K::Segment_2>(&seg_obj);
const K::Ray_2 *dray = CGAL::object_cast<K::Ray_2>(&seg_obj);
if (dseg) { //Okay, we have a segment
return *dseg;
} else { //Must be a ray
const auto &source = dray->source();
const auto dsx = source.x();
const auto dsy = source.y();
const auto &dir = dray->direction();
const auto tpoint = K::Point_2(dsx+RAY_LENGTH*dir.dx(),dsy+RAY_LENGTH*dir.dy());
if(outgoing)
return K::Segment_2(
dray->source(),
tpoint
);
else
return K::Segment_2(
tpoint,
dray->source()
);
}
};
//First line of WKT CSV output
std::cout<<"\"id\",\"geom\"\n";
int fnum = 0;
//Loop over the faces of the Voronoi diagram in some arbitrary order
for(VD::Face_iterator fit = vd.faces_begin(); fit!=vd.faces_end();++fit,fnum++){
CGAL::Polygon_2<K> pgon;
//Edge circulators traverse endlessly around a face. Make a note of the
//starting point so we know when to quit.
VD::Face::Ccb_halfedge_circulator ec_start = fit->ccb();
//Current location of the edge circulator
VD::Face::Ccb_halfedge_circulator ec = ec_start;
do {
//A half edge circulator representing a ray doesn't carry direction
//information. To get it, we take the dual of the dual of the half-edge.
//The dual of a half-edge circulator is the edge of a Delaunay triangle.
//The dual of the edge of Delaunay triangle is either a segment or a ray.
// const CGAL::Object seg_dual = rt.dual(ec->dual());
const CGAL::Object seg_dual = vd.dual().dual(ec->dual());
//Convert the segment/ray into a segment
const auto this_seg = ConvertToSeg(seg_dual, ec->has_target());
pgon.push_back(this_seg.source());
//If the segment has no target, it's a ray. This means that the next
//segment will also be a ray. We need to connect those two rays with a
//segment. The following accomplishes this.
if(!ec->has_target()){
const CGAL::Object nseg_dual = vd.dual().dual(ec->next()->dual());
const auto next_seg = ConvertToSeg(nseg_dual, ec->next()->has_target());
pgon.push_back(next_seg.target());
}
} while ( ++ec != ec_start ); //Loop until we get back to the beginning
//In order to crop the Voronoi diagram, we need to convert the bounding box
//into a polygon. You'd think there'd be an easy way to do this. But there
//isn't (or I haven't found it).
CGAL::Polygon_2<K> bpoly;
bpoly.push_back(K::Point_2(bbox.xmin(),bbox.ymin()));
bpoly.push_back(K::Point_2(bbox.xmax(),bbox.ymin()));
bpoly.push_back(K::Point_2(bbox.xmax(),bbox.ymax()));
bpoly.push_back(K::Point_2(bbox.xmin(),bbox.ymax()));
//Perform the intersection. Since CGAL is very general, it believes the
//result might be multiple polygons with holes.
std::list<CGAL::Polygon_with_holes_2<K>> isect;
CGAL::intersection(pgon, bpoly, std::back_inserter(isect));
//But we know better. The intersection of a convex polygon and a box is
//always a single polygon without holes. Let's assert this.
assert(isect.size()==1);
//And recover the polygon of interest
auto &poly_w_holes = isect.front();
auto &poly_outer = poly_w_holes.outer_boundary();
//Print the polygon as a WKT polygon
std::cout<<fnum<<", "
"\"POLYGON ((";
for(auto v=poly_outer.vertices_begin();v!=poly_outer.vertices_end();v++)
std::cout<<v->x()<<" "<<v->y()<<", ";
std::cout<<poly_outer.vertices_begin()->x()<<" "<<poly_outer.vertices_begin()->y()<<"))\"\n";
}
return 0;
}
I have read many articles on drawing planar graphs on the plane, I tried a lot of libraries.
In the end, I need to specify the input graph, the output to obtain new coordinates of its vertices, so that the edges do not intersect.
The choice fell on the function chrobak_payne_straight_line_drawing from Boost Graph Library.
I tested it on the following graph: http://cboard.cprogramming.com/attachments/cplusplus-programming/11153d1322430048-graph-planarization-boost-library-1grb.jpg
But it did not work and calls System.AccessViolationException at the 125 line of file chrobak_payne_drawing.hpp:
delta_x[v3] = 1;
But on this graph it works: http://cboard.cprogramming.com/attachments/cplusplus-programming/11154d1322430090-graph-planarization-boost-library-2gr.jpg
Please help, I need to function to work with all planar graphs and does not cause critical errors. Below I present the code, which I tested the first graph and got a critical error, by the way, I'm using visual studio 2010.
#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <vector>
#include <stack>
#include <boost/graph/planar_canonical_ordering.hpp>
#include <boost/graph/is_straight_line_drawing.hpp>
#include <boost/graph/chrobak_payne_drawing.hpp>
#include <boost/graph/boyer_myrvold_planar_test.hpp>
using namespace boost;
//a class to hold the coordinates of the straight line embedding
struct coord_t
{
std::size_t x;
std::size_t y;
};
int main(int argc, char** argv)
{
typedef adjacency_list
< vecS,
vecS,
undirectedS,
property<vertex_index_t, int>
> graph;
//Define the storage type for the planar embedding
typedef std::vector< std::vector< graph_traits<graph>::edge_descriptor > >
embedding_storage_t;
typedef boost::iterator_property_map
< embedding_storage_t::iterator,
property_map<graph, vertex_index_t>::type
>
embedding_t;
// Create the graph - a maximal planar graph on 7 vertices. The functions
// planar_canonical_ordering and chrobak_payne_straight_line_drawing both
// require a maximal planar graph. If you start with a graph that isn't
// maximal planar (or you're not sure), you can use the functions
// make_connected, make_biconnected_planar, and make_maximal planar in
// sequence to add a set of edges to any undirected planar graph to make
// it maximal planar.
graph g(5);
add_edge(0,3, g);
add_edge(0,4, g);
add_edge(1,3, g);
add_edge(1,4, g);
add_edge(2,3, g);
add_edge(2,4, g);
// Create the planar embedding
embedding_storage_t embedding_storage(num_vertices(g));
embedding_t embedding(embedding_storage.begin(), get(vertex_index,g));
boyer_myrvold_planarity_test(boyer_myrvold_params::graph = g,
boyer_myrvold_params::embedding = embedding
);
// Find a canonical ordering
std::vector<graph_traits<graph>::vertex_descriptor> ordering;
planar_canonical_ordering(g, embedding, std::back_inserter(ordering));
//Set up a property map to hold the mapping from vertices to coord_t's
typedef std::vector< coord_t > straight_line_drawing_storage_t;
typedef boost::iterator_property_map
< straight_line_drawing_storage_t::iterator,
property_map<graph, vertex_index_t>::type
>
straight_line_drawing_t;
straight_line_drawing_storage_t straight_line_drawing_storage
(num_vertices(g));
straight_line_drawing_t straight_line_drawing
(straight_line_drawing_storage.begin(),
get(vertex_index,g)
);
// Compute the straight line drawing
chrobak_payne_straight_line_drawing(g,
embedding,
ordering.begin(),
ordering.end(),
straight_line_drawing
);
std::cout << "The straight line drawing is: " << std::endl;
graph_traits<graph>::vertex_iterator vi, vi_end;
for(tie(vi,vi_end) = vertices(g); vi != vi_end; ++vi)
{
coord_t coord(get(straight_line_drawing,*vi));
std::cout << *vi << " -> (" << coord.x << ", " << coord.y << ")"
<< std::endl;
}
// Verify that the drawing is actually a plane drawing
if (is_straight_line_drawing(g, straight_line_drawing))
std::cout << "Is a plane drawing." << std::endl;
else
std::cout << "Is not a plane drawing." << std::endl;
return 0;
}
Mayber i must first make graph maximal planar? But I did not succeed .. Please help me add to my code the necessary functions for this, I'm not quite sure what step to convert the graph.
Thanks in advance, you - my last hope!
This looks like a bug in boost::graph. I have a different access violation there, perhaps because of a different boost version, but there's definitely a bug.
left[v] = right[leftmost];
vertex_t next_to_rightmost;
for(vertex_t temp = leftmost; temp != rightmost;
temp = right[temp]
)
{
next_to_rightmost = temp;
}
right[next_to_rightmost] = graph_traits<Graph>::null_vertex();
Here when leftmost==rightmost, the for loop is not executed and next_to_rightmost remains uninitialized. Crash! That's what I'm seeing.
The graph you were using was not maximal planar. You need to use the functions make_connected, make_biconnected_planar and make_maximal_planar before you get to a canonical ordering or an embedding. Though the comment in the source explains this, their use is not exactly intuitive and I could not find a source using them and the function chrobak_payne_straight_line_drawing in combination. Here is an example I adapted from the original source and other examples.