implement hit_test for new_geometry

This commit is contained in:
Dane Springmeyer 2015-03-22 11:41:44 -07:00
parent 537a392a09
commit 1a57aef9c6
2 changed files with 231 additions and 13 deletions

View file

@ -25,11 +25,129 @@
// mapnik
#include <mapnik/feature.hpp>
#include <mapnik/geometry_impl.hpp>
#include <mapnik/geom_util.hpp>
// boost
namespace mapnik {
namespace detail {
inline bool pip(double x0,
double y0,
double x1,
double y1,
double x,
double y)
{
return ((((y1 <= y) && (y < y0)) || ((y0 <= y) && (y < y1))) && (x < (x0 - x1) * (y - y1) / (y0 - y1) + x1));
}
struct hit_test_visitor
{
hit_test_visitor(double x, double y, double tol)
: x_(x),
y_(y),
tol_(tol) {}
bool operator() (new_geometry::point const& geom) const
{
return distance(geom.x, geom.y, x_, y_) <= tol_;
}
bool operator() (new_geometry::multi_point const& geom) const
{
for (auto const& pt : geom)
{
if (distance(pt.x, pt.y, x_, y_) <= tol_) return true;
}
return false;
}
bool operator() (new_geometry::line_string const& geom) const
{
std::size_t num_points = geom.num_points();
if (num_points > 1)
{
for (std::size_t i = 1; i < num_points; ++i)
{
auto const& pt0 = geom[i-1];
auto const& pt1 = geom[i];
double distance = point_to_segment_distance(x_,y_,pt0.x,pt0.y,pt1.x,pt1.y);
if (distance < tol_) return true;
}
}
return false;
}
bool operator() (new_geometry::multi_line_string const& geom) const
{
for (auto const& line: geom)
{
if (operator()(line)) return true;
}
return false;
}
bool operator() (new_geometry::polygon const& geom) const
{
auto const& exterior = geom.exterior_ring;
std::size_t num_points = exterior.num_points();
if (num_points < 2) return false;
bool inside = false;
for (std::size_t i = 1; i < num_points; ++i)
{
auto const& pt0 = exterior[i-1];
auto const& pt1 = exterior[i];
// todo - account for tolerance
if (pip(pt0.x,pt0.y,pt1.x,pt1.y,x_,y_))
{
inside = true;
break;
}
}
if (!inside) return false;
for (auto const& ring : geom.interior_rings)
{
std::size_t num_interior_points = ring.size();
for (std::size_t j = 1; j < num_interior_points; ++j)
{
auto const& pt0 = ring[j-1];
auto const& pt1 = ring[j];
if (pip(pt0.x,pt0.y,pt1.x,pt1.y,x_,y_))
{
// TODO - account for tolerance
inside=!inside;
break;
}
}
}
return inside;
}
bool operator() (new_geometry::multi_polygon const& geom) const
{
for (auto const& poly: geom)
{
if (operator()(poly)) return true;
}
return false;
}
bool operator() (new_geometry::geometry_collection const& collection) const
{
for (auto const& geom: collection)
{
if (mapnik::util::apply_visitor((*this),geom)) return true;
}
return false;
}
double x_;
double y_;
double tol_;
};
}
inline bool hit_test(mapnik::new_geometry::geometry const& geom, double x, double y, double tol)
{
return mapnik::util::apply_visitor(detail::hit_test_visitor(x,y,tol), geom);
}
class hit_test_filter
{
public:
@ -38,18 +156,9 @@ public:
y_(y),
tol_(tol) {}
bool pass(feature_impl & feature)
bool pass(feature_impl const& feature)
{
// FIXME
/*
for (geometry_type const& geom : feature.paths())
{
vertex_adapter va(geom);
if (label::hit_test(va, x_,y_,tol_))
return true;
}
*/
return false;
return hit_test(feature.get_geometry(),x_,y_,tol_);
}
private:

View file

@ -0,0 +1,109 @@
#include "catch.hpp"
#include <mapnik/geometry_impl.hpp>
#include <mapnik/hit_test_filter.hpp>
#include <mapnik/geometry_correct.hpp>
TEST_CASE("geometry ops") {
SECTION("hit_test_filter") {
using namespace mapnik::new_geometry;
{
geometry geom(point(0,0));
REQUIRE( mapnik::hit_test(geom,0,0,0) );
}
{
geometry geom(point(0,0));
REQUIRE( mapnik::hit_test(geom,1,0,1) );
}
{
geometry geom(point(0,0));
REQUIRE( mapnik::hit_test(geom,0,1,1) );
}
{
geometry geom(point(0,0));
REQUIRE( mapnik::hit_test(geom,1,1,1.5) );
}
{
line_string line;
line.add_coord(0,0);
line.add_coord(1,1);
line.add_coord(2,2);
geometry geom(line);
REQUIRE( mapnik::hit_test(geom,0,0,1.5) );
}
{
line_string line;
line.add_coord(0,0);
line.add_coord(1,1);
line.add_coord(2,2);
multi_line_string multi_line;
multi_line.emplace_back(std::move(line));
geometry geom(multi_line);
REQUIRE( mapnik::hit_test(geom,0,0,1.5) );
}
{
polygon poly;
linear_ring ring;
ring.add_coord(0,0);
ring.add_coord(-10,0);
ring.add_coord(-10,10);
ring.add_coord(0,10);
ring.add_coord(0,0);
poly.set_exterior_ring(std::move(ring));
geometry geom(poly);
REQUIRE( mapnik::hit_test(geom,-5,5,0) );
multi_polygon mp;
mp.push_back(poly);
geometry geom_mp(mp);
REQUIRE( mapnik::hit_test(geom_mp,-5,5,0) );
correct(geom);
REQUIRE( mapnik::hit_test(geom,-5,5,0) );
correct(geom_mp);
REQUIRE( mapnik::hit_test(geom_mp,-5,5,0) );
geometry_collection gc;
REQUIRE( !mapnik::hit_test(geometry(gc),-5,5,0) );
gc.push_back(geom_mp);
REQUIRE( mapnik::hit_test(geometry(gc),-5,5,0) );
REQUIRE( !mapnik::hit_test(geometry(gc),-50,-50,0) );
gc.emplace_back(point(-50,-50));
REQUIRE( mapnik::hit_test(geometry(gc),-50,-50,0) );
}
{
// polygon with hole
polygon poly;
linear_ring ring;
ring.add_coord(0,0);
ring.add_coord(-10,0);
ring.add_coord(-10,10);
ring.add_coord(0,10);
ring.add_coord(0,0);
poly.set_exterior_ring(std::move(ring));
linear_ring hole;
hole.add_coord(-7,7);
hole.add_coord(-7,3);
hole.add_coord(-3,3);
hole.add_coord(-3,7);
hole.add_coord(-7,7);
poly.add_hole(std::move(hole));
geometry geom(poly);
REQUIRE( !mapnik::hit_test(geom,-5,5,0) );
// add another hole inside the first hole
// which should be considered a hit
linear_ring fill;
fill.add_coord(-6,4);
fill.add_coord(-6,6);
fill.add_coord(-4,6);
fill.add_coord(-4,4);
fill.add_coord(-6,4);
poly.add_hole(std::move(fill));
REQUIRE( mapnik::hit_test(geometry(poly),-5,5,0) );
}
}
}