Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/uniform point distribution #618

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/example/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ project boost-geometry-examples-extensions
;

build-project gis ;
build-project random ;
16 changes: 16 additions & 0 deletions extensions/example/random/Jamfile.v2
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Boost.Geometry (aka GGL, Generic Geometry Library)
#
# Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
# Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
# Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

# Use, modification and distribution is subject to the Boost Software License,
# Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)


project boost-geometry-example-extensions-random
: # requirements
;

exe random_example : random_example.cpp ;
82 changes: 82 additions & 0 deletions extensions/example/random/random_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2019 Tinko Bartels, Berlin, Germany.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// Random Example - showing random sampling in various geometries

#include <fstream>
#include <random>
#include <cmath>

#include <boost/geometry.hpp>
#include <boost/geometry/extensions/random/uniform_point_distribution.hpp>

const int samples = 100;

template<typename Distribution, typename Generator, typename Mapper>
void draw_samples(Distribution& d, Generator& g, Mapper& m) {
for (int i = 0 ; i < samples ; ++i)
{
m.map(d(g), "fill-opacity:1;fill:rgb(255,0,0);stroke:rgb(255,0,0);stroke-width:1", 1);
}
}

int main()
{
namespace bg = boost::geometry;
typedef bg::model::point<double, 2, bg::cs::cartesian> point;
typedef bg::model::segment<point> segment;
typedef bg::model::box<point> box;
typedef bg::model::multi_point<point> multi_point;
typedef bg::model::polygon<point> polygon;

std::default_random_engine generator(1);
polygon poly;
boost::geometry::read_wkt("POLYGON((16 21,17.1226 17.5451,20.7553 17.5451,17.8164 15.4098,18.9389 11.9549,16 14.0902,13.0611 11.9549,14.1836 15.4098,11.2447 17.5451,14.8774 17.5451,16 21))", poly);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please reduce line's length

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g.:

boost::geometry::read_wkt("POLYGON((16 21,17.1226 17.5451,20.7553 17.5451,17.8164 15.4098,"
                                   "18.9389 11.9549,16 14.0902,13.0611 11.9549,14.1836 15.4098,"
                                   "11.2447 17.5451,14.8774 17.5451,16 21))",
                          poly);

box b(point(0, 0), point(10, 10));
segment s(point(11, 0), point(21, 10));
multi_point mp;
for (double y = 11.0 ; y <= 21.0 ; y += 0.5)
{
for(double x = 0.0 ; x <= 10.0 ; x += 0.5)
{
bg::append(mp, point(x, y));
}
}
bg::random::uniform_point_distribution<box> box_dist(b);
bg::random::uniform_point_distribution<multi_point> mp_dist(mp);
bg::random::uniform_point_distribution<segment> seg_dist(s);
bg::random::uniform_point_distribution<polygon> poly_dist(poly);
std::ofstream svg("random.svg");
bg::svg_mapper<point> mapper(svg, 720, 720);
mapper.add(poly);
mapper.add(b);
mapper.add(mp);
mapper.add(s);
mapper.add(box(point(0, 22), point(22, 35)));
mapper.map(b, "fill-opacity:0.3;fill:rgb(51,51,153);stroke:rgb(51,51,153);stroke-width:2");
mapper.map(s, "fill-opacity:0.3;fill:rgb(51,51,153);stroke:rgb(51,51,153);stroke-width:2");
mapper.map(mp, "fill-opacity:0.3;fill:rgb(51,51,153);stroke:rgb(51,51,153);stroke-width:2", 2);
mapper.map(poly, "fill-opacity:0.3;fill:rgb(51,51,153);stroke:rgb(51,51,153);stroke-width:2");
draw_samples(box_dist, generator, mapper);
draw_samples(mp_dist, generator, mapper);
draw_samples(seg_dist, generator, mapper);
draw_samples(poly_dist, generator, mapper);

typedef bg::model::point<double, 2, bg::cs::spherical_equatorial<bg::degree>> point_spherical;
typedef bg::model::box<point_spherical> box_spherical;
box_spherical sb(point_spherical(0, 0), point_spherical(90, 90));
bg::random::uniform_point_distribution<box_spherical> sb_dist(sb);
for (int i = 0 ; i < 10*samples ; ++i)
{
point_spherical p = sb_dist(generator);
double x = bg::get<0>(p) / 90 * 22;
double y = 32 - bg::get<1>(p) / 90 * 10;
mapper.map(point(x,y), "fill-opacity:1;fill:rgb(255,0,0);stroke:rgb(255,0,0);stroke-width:1", 1);
}
return 0;
}
2 changes: 1 addition & 1 deletion extensions/test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ build-project algorithms ;
build-project gis ;
build-project iterators ;
build-project nsphere ;

build-project random ;
11 changes: 11 additions & 0 deletions extensions/test/random/Jamfile.v2
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Boost.Geometry (aka GGL, Generic Geometry Library)
#
# Use, modification and distribution is subject to the Boost Software License,
# Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)

test-suite boost-geometry-extensions-random
:
[ run random.cpp ]
;

144 changes: 144 additions & 0 deletions extensions/test/random/random.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2019 Tinko Bartels, Berlin, Germany.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can ack gsoc (see other PR)


// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <random>
#include <vector>
#include <sstream>

#include <geometry_test_common.hpp>

#include <boost/geometry.hpp>
#include <boost/geometry/extensions/random/uniform_point_distribution.hpp>

typedef bg::model::point<double, 2, bg::cs::cartesian> point2d_cart;
typedef bg::model::point<double, 3, bg::cs::cartesian> point3d_cart;
typedef bg::model::point<double, 2, bg::cs::geographic<bg::degree>> point2d_geog;

void test_geographic()
{
//We check whether the generated points lie roughly along
//the great circle segment (<2 km distance on a sphere with
//the radius of earth) and are distributed uniformly with respect
//to great circle arc length.
typedef bg::model::linestring<point2d_geog> linestring;
linestring ls {{ 0.0, 0.0 }, { 45.0, 45.0 }, { 60.0, 60.0 }};
bg::random::uniform_point_distribution<linestring> l_dist(ls);
std::mt19937 generator(0);
int sample_count = 2000;
int count_below_45 = 0;
for (int i = 0 ; i < sample_count ; ++i)
{
point2d_geog sample = l_dist(generator);
BOOST_CHECK( bg::distance(sample, ls) < 2000 );
if(bg::get<0>(sample) < 45.0) count_below_45++;
}
double length_ratio = bg::distance(ls[0], ls[1]) /
( bg::distance(ls[0], ls[1]) + bg::distance(ls[1], ls[2]) );
double sample_ratio = ((double) count_below_45) / sample_count;
bool in_range = sample_ratio * 0.95 < length_ratio
&& sample_ratio * 1.05 > length_ratio;
BOOST_CHECK( in_range );

//We check whether the generated points lie in the spherical box
//(which is actually a triangle in this case) and whether the latitude
//is distributed as expected for uniform spherical distribution, using
//known area ratios of spherical caps.
typedef bg::model::box<point2d_geog> box;
box b {{ 0.0, 0.0 }, { 90.0, 90.0 }};
bg::random::uniform_point_distribution<box> b_dist(b);
int under_60 = 0;
for (int i = 0 ; i < sample_count ; ++i)
{
point2d_geog sample = b_dist(generator);
BOOST_CHECK( bg::within(sample, b) );
if(bg::get<1>(sample) < 60.0) ++under_60;
}
BOOST_CHECK_GT(under_60, 0.5 * 0.95 * sample_count);
BOOST_CHECK_LT(under_60, 0.5 * 1.05 * sample_count);
}

void test_polygon()
{
//This test will test uniform sampling in polygon, which also checks
//uniform sampling in boxes. We check whether two equal distributions
//(copied using operator<< and operator>>) generate the same sequence
//of points and whether those points are uniformly distributed with
//respect to cartesian area.
typedef bg::model::polygon<point2d_cart> polygon;
polygon poly;
bg::read_wkt(
"POLYGON((16 21,17.1226 17.5451,20.7553 17.5451, 17.8164 15.4098,18.9389 11.9549,16 14.0902,13.0611 11.9549, 14.1836 15.4098,11.2447 17.5451,14.8774 17.5451,16 21))",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please reduce length

poly);
bg::random::uniform_point_distribution<polygon> poly_dist(poly);
bg::random::uniform_point_distribution<polygon> poly_dist2;
BOOST_CHECK( !(poly_dist == poly_dist2) );
std::stringstream ss;
ss << poly_dist;
ss >> poly_dist2;
BOOST_CHECK( poly_dist == poly_dist2 );
std::mt19937 generator(0), generator2(0);
for (int i = 0 ; i < 100 ; ++i)
{
point2d_cart sample1 = poly_dist(generator);
BOOST_CHECK( bg::equals(sample1, poly_dist2(generator2)) );
BOOST_CHECK( bg::within(sample1, poly) );
}
std::vector<point2d_cart> randoms;
const int uniformity_test_samples = 2000;
for (int i = 0 ; i < uniformity_test_samples ; ++i)
{
randoms.push_back(poly_dist(generator));
}
typedef bg::model::box<point2d_cart> box;
box env, lhalf;
bg::envelope(poly, env);
bg::set<bg::min_corner, 0>(lhalf, bg::get<bg::min_corner, 0>(env));
bg::set<bg::min_corner, 1>(lhalf, bg::get<bg::min_corner, 1>(env));
bg::set<bg::max_corner, 0>(lhalf, bg::get<bg::max_corner, 0>(env));
bg::set<bg::max_corner, 1>(lhalf,
(bg::get<bg::max_corner, 1>(env) + bg::get<bg::min_corner, 1>(env)) / 2);
std::vector<polygon> lower;
bg::intersection(lhalf, poly, lower);
double area_ratio = bg::area(lower[0])/bg::area(poly);
int in_lower = 0;
for (int i = 0 ; i < uniformity_test_samples ; ++i)
{
if(bg::within(randoms[i], lhalf))
++in_lower;
}
double sample_ratio = ((double) in_lower ) / uniformity_test_samples;
BOOST_CHECK_GT( sample_ratio * 1.05, area_ratio );
BOOST_CHECK_LT( sample_ratio * 0.95, area_ratio );
}

void test_multipoint()
{
typedef bg::model::multi_point<point3d_cart> multipoint;
multipoint mp {{0.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}};
int first = 0;
bg::random::uniform_point_distribution<multipoint> mp_dist(mp);
std::mt19937 generator(0);
int sample_count = 1000;
for (int i = 0 ; i < sample_count ; ++i)
{
point3d_cart sample = mp_dist(generator);
BOOST_CHECK( bg::within(sample, mp) );
if(bg::equals(sample, mp[0])) ++first;
}
BOOST_CHECK_GT(first * 1.05, sample_count / 3);
BOOST_CHECK_LT(first * 0.95, sample_count / 3);
}

int test_main(int, char* [])
{
test_polygon();
test_geographic();
test_multipoint();
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2019 Tinko Bartels, Berlin, Germany.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_EXTENSIONS_RANDOM_STRATEGIES_AGNOSTIC_UNIFORM_CONVEX_FAN_HPP
#define BOOST_GEOMETRY_EXTENSIONS_RANDOM_STRATEGIES_AGNOSTIC_UNIFORM_CONVEX_FAN_HPP

#include <random>
#include <vector>
#include <cstdlib>
#include <cmath>

#include <boost/geometry/algorithms/equals.hpp>
#include <boost/geometry/algorithms/transform.hpp>
#include <boost/geometry/algorithms/within.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace uniform_point_distribution {

// The following strategy is suitable for convex rings and polygons with
// non-empty interior.
template
<
typename Point,
typename DomainGeometry,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is either a convex ring or convex polygon (they are actually the same but the can have different types)

typename TriangleStrategy,
typename SideStrategy //Actually, we need a triangle area strategy here.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to use already existing AreaStrategy here? Can this agnostic strategy (or algorithm) be used in noncartesian CS? Side strategy would not allow to calculate areas in general case as you noticed.

>
struct uniform_convex_fan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this really a strategy (i.e. coordinate type specific) or an algorithm? To my point of view agnostic strategies are just algorithms

{
private:
std::vector<double> accumulated_areas;
// It is hard to see a reason not to use double here. If a triangles
// relative size is smaller than doubles epsilon, it is too unlikely to
// realistically occur in a random sample anyway.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type should probably be taken from area strategy if that's what's being calculated here (not side) .

public:
uniform_convex_fan(DomainGeometry const& g)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any clue for choosing that name? As far as I know, in geometry, fan is a collection of cones and thus fan makes no sense. Maybe I am missing some notation or something.

Copy link
Collaborator Author

@tinko92 tinko92 Oct 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name was chosen based on the fact that this class implicitly uses the fan triangulation of convex polygons for sampling. Reconsidering that choice, the fact that it uses the fan triangulation may be an implementation detail and alluding to it may be unnecessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so this is probably not the right name. The name should declare what this class is doing, e.g. uniform_convex_polygon_sampler makes sense?

{
accumulated_areas.push_back(0);
for (int i = 2 ; i < g.size() ; ++i) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We put { in the next line.

accumulated_areas.push_back(
accumulated_areas.back() +
std::abs(SideStrategy::template side_value<double, double>(
*g.begin(),
*(g.begin() + i - 1),
*(g.begin() + i))));
}
}
bool equals(DomainGeometry const& l_domain,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see why do you need this function? BG has functions for checking equality of geometries.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also you check all the possible linestrings of the convex polygons from a fixed point. What if the geometries are equal and they start at different vertices i.e. domain.begin(). What am I missing ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The notion of equality as defined by the C++ named requirement "RandomNumberDistribution" on which I based my design of random point distributions considers two distribution instances A, B equal if their parameters are equal and they will produce the same output given the same generators, i.e. from A == B and g1 == g2 it needs to follow that A(g1) == A(g2).

Because of the way I generate samples for convex rings, two distributions will only produce the same output if the convex rings start from the same point.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I see, I remember also our discussions on this topic. Please add a comment or document this for future reference

DomainGeometry const& r_domain,
uniform_convex_fan const& r_strategy) const
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this strategy is unused

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have that parameter because the rhs strategy is used in equal-comparison for other strategies and so the code in uniform_point_distribution passes it. Would it be ok, if I just removed the name (r_strategy)? Or is their a way around the necessity for having all strategies take all values that other strategies could need for comparison?

Copy link
Member

@vissarion vissarion Oct 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think removing the name r_strategy would be ok for now

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively you can use boost::ignore_unused(r_strategy)

{
if( l_domain.size() != r_domain.size() ) return false;
Copy link
Member

@awulkiew awulkiew Oct 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
    return false;
}

Yeah, I know it looks ugly. I personally would put return false; without brackets in the next line but this is what our guidelines say about formatting.

Btw, @barendgehrels what do you think about allowing single-line returns without brackets?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for one line returns.
also in if( add a space

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is DomainGeometry? Is it always STL container? Always std::vector? I'm asking because it's a generic type but you call .size(). For generic ranges we use Boost.Range's boost::size().

for (int i = 0; i < l_domain.size(); ++i) {
Copy link
Member

@awulkiew awulkiew Oct 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And then here int should either be DomainGeometry::size_type, std::vector<...>::size_type or boost::size_type<DomainGeometry>::type. Or at least std::size_t.

if( !boost::geometry::equals(*(l_domain.begin() + i),
*(r_domain.begin() + i)))
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So like that, though in this case it would make sense to have brakcets because it's hard to see the return false.

Copy link
Member

@vissarion vissarion Oct 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the for's { should be in new line and a space between if and (

}
return true;
}
template<typename Gen>
Point apply(Gen& g, DomainGeometry const& d)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is more clear to name it Generator instead of Gen

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also please be consistent; above g had DomainGeometry type, here d is DomainGeometry and g type Gen

{
std::uniform_real_distribution<double> dist(0, 1);
double r = dist(g) * accumulated_areas.back(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot find it in guidelines but bg uses one initialization per variable as

double r = dist(g) * accumulated_areas.back();
double s = dist(g);

@barendgehrels right?

s = dist(g);
std::size_t i = std::distance(
accumulated_areas.begin(),
std::lower_bound(accumulated_areas.begin(),
accumulated_areas.end(),
r));
return TriangleStrategy::template map
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why map and not sampling or something that declares sampling from a triangle with TriangleStrategy?

<
double
>(* d.begin(),
*(d.begin() + i),
*(d.begin() + i + 1),
( r - accumulated_areas[ i - 1 ]) /
( accumulated_areas[ i ] - accumulated_areas[ i - 1 ] ),
s);
}
void reset(DomainGeometry const&) {};
};

}} // namespace strategy::uniform_point_distribution

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_EXTENSIONS_RANDOM_STRATEGIES_AGNOSTIC_UNIFORM_CONVEX_FAN_HPP
Loading