Skip to content

Commit

Permalink
Merge pull request #412 from jeremy-murphy/388_is_straight_line_drawing
Browse files Browse the repository at this point in the history
Fix geometry bug in is_straight_line_drawing
  • Loading branch information
jeremy-murphy authored Jan 26, 2025
2 parents 80e00c3 + 9176040 commit 84c36ca
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 80 deletions.
112 changes: 32 additions & 80 deletions include/boost/graph/is_straight_line_drawing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,74 +16,44 @@
#include <boost/graph/properties.hpp>
#include <boost/graph/planar_detail/bucket_sort.hpp>

#include <boost/geometry/algorithms/crosses.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/core/coordinate_type.hpp>

#include <boost/numeric/conversion/cast.hpp>

#include <algorithm>
#include <vector>
#include <set>
#include <map>

namespace boost
{

// Return true exactly when the line segments s1 = ((x1,y1), (x2,y2)) and
// s2 = ((a1,b1), (a2,b2)) intersect in a point other than the endpoints of
// the line segments. The one exception to this rule is when s1 = s2, in
// which case false is returned - this is to accomodate multiple edges
// between the same pair of vertices, which shouldn't invalidate the straight
// line embedding. A tolerance variable epsilon can also be used, which
// defines how far away from the endpoints of s1 and s2 we want to consider
// an intersection.

inline bool intersects(double x1, double y1, double x2, double y2, double a1,
double b1, double a2, double b2, double epsilon = 0.000001)
// Overload of make from Boost.Geometry.
template<typename Geometry, typename Graph, typename GridPositionMap>
Geometry make(typename graph_traits<Graph>::edge_descriptor e,
Graph const &g,
GridPositionMap const &drawing)
{
auto e_source(source(e, g));
auto e_target(target(e, g));
using Float = typename geometry::coordinate_type<Geometry>::type;
return {{numeric_cast<Float>(drawing[e_source].x), numeric_cast<Float>(drawing[e_source].y)},
{numeric_cast<Float>(drawing[e_target].x), numeric_cast<Float>(drawing[e_target].y)}};
}

if (x1 - x2 == 0)
{
std::swap(x1, a1);
std::swap(y1, b1);
std::swap(x2, a2);
std::swap(y2, b2);
}

if (x1 - x2 == 0)
{
BOOST_USING_STD_MAX();
BOOST_USING_STD_MIN();

// two vertical line segments
double min_y = min BOOST_PREVENT_MACRO_SUBSTITUTION(y1, y2);
double max_y = max BOOST_PREVENT_MACRO_SUBSTITUTION(y1, y2);
double min_b = min BOOST_PREVENT_MACRO_SUBSTITUTION(b1, b2);
double max_b = max BOOST_PREVENT_MACRO_SUBSTITUTION(b1, b2);
if ((max_y > max_b && max_b > min_y)
|| (max_b > max_y && max_y > min_b))
return true;
else
return false;
}

double x_diff = x1 - x2;
double y_diff = y1 - y2;
double a_diff = a2 - a1;
double b_diff = b2 - b1;

double beta_denominator = b_diff - (y_diff / ((double)x_diff)) * a_diff;

if (beta_denominator == 0)
{
// parallel lines
return false;
}

double beta = (b2 - y2 - (y_diff / ((double)x_diff)) * (a2 - x2))
/ beta_denominator;
double alpha = (a2 - x2 - beta * (a_diff)) / x_diff;

double upper_bound = 1 - epsilon;
double lower_bound = 0 + epsilon;

return (beta < upper_bound && beta > lower_bound && alpha < upper_bound
&& alpha > lower_bound);
// Overload of crosses from Boost.Geometry.
template<typename Graph, typename GridPositionMap>
bool crosses(typename graph_traits<Graph>::edge_descriptor e,
typename graph_traits<Graph>::edge_descriptor f,
Graph const &g,
GridPositionMap const &drawing)
{
using geometry::crosses;
using geometry::model::linestring;
using geometry::model::d2::point_xy;
using linestring2d = geometry::model::linestring<geometry::model::d2::point_xy<double>>;
return crosses(make<linestring2d>(e, g, drawing),
make<linestring2d>(f, g, drawing));
}

template < typename Graph, typename GridPositionMap, typename VertexIndexMap >
Expand Down Expand Up @@ -161,33 +131,15 @@ bool is_straight_line_drawing(

if (before != active_edges.end())
{

edge_t f = before->second;
vertex_t e_source(source(e, g));
vertex_t e_target(target(e, g));
vertex_t f_source(source(f, g));
vertex_t f_target(target(f, g));

if (intersects(drawing[e_source].x, drawing[e_source].y,
drawing[e_target].x, drawing[e_target].y,
drawing[f_source].x, drawing[f_source].y,
drawing[f_target].x, drawing[f_target].y))
if (crosses(e, f, g, drawing))
return false;
}

if (after != active_edges.end())
{

edge_t f = after->second;
vertex_t e_source(source(e, g));
vertex_t e_target(target(e, g));
vertex_t f_source(source(f, g));
vertex_t f_target(target(f, g));

if (intersects(drawing[e_source].x, drawing[e_source].y,
drawing[e_target].x, drawing[e_target].y,
drawing[f_source].x, drawing[f_source].y,
drawing[f_target].x, drawing[f_target].y))
if (crosses(e, f, g, drawing))
return false;
}

Expand Down
27 changes: 27 additions & 0 deletions test/is_straight_line_draw_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,32 @@ int main(int, char*[])

BOOST_TEST(!is_straight_line_drawing(g, drawing));


// issue #388
g.clear();
add_edge(0, 1, g);
add_edge(2, 0, g);
add_edge(1, 2, g);

struct coord_t { size_t x, y; };
std::vector<coord_t> coordinates{
{4143438, 86426},
{4064945, 7932},
{4064944, 7931}
};
/*
There is a very small angle between edge 0--1 and edge 2--0 at vertex 0,
with slope of edges 78494/78493 != 78495/78494, which cannot be
correctly handled by double type function "intersects()" called
by "is_straight_line_drawing()":
4143438-4064945 = 78493
86426-7932 = 78494
4143438-4064944 = 78494
86426-7931 = 78495
*/
BOOST_TEST(is_straight_line_drawing(g, coordinates.data()));

return boost::report_errors();
}

0 comments on commit 84c36ca

Please sign in to comment.