Port osmium point-in-polygon function
This commit is contained in:
parent
b15288e0ea
commit
421115200b
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <boost/geometry.hpp>
|
#include <boost/geometry.hpp>
|
||||||
|
#include <boost/geometry/geometries/point_xy.hpp>
|
||||||
#include <boost/geometry/index/rtree.hpp>
|
#include <boost/geometry/index/rtree.hpp>
|
||||||
|
|
||||||
#include <osmium/osm/way.hpp>
|
#include <osmium/osm/way.hpp>
|
||||||
@ -14,11 +15,14 @@ namespace osrm
|
|||||||
{
|
{
|
||||||
namespace extractor
|
namespace extractor
|
||||||
{
|
{
|
||||||
|
|
||||||
struct LocationDependentData
|
struct LocationDependentData
|
||||||
{
|
{
|
||||||
using point_t = boost::geometry::model::
|
using point_t = boost::geometry::model::d2::
|
||||||
point<double, 2, boost::geometry::cs::spherical_equatorial<boost::geometry::degree>>;
|
point_xy<double, boost::geometry::cs::spherical_equatorial<boost::geometry::degree>>;
|
||||||
|
using segment_t = std::pair<point_t, point_t>;
|
||||||
using polygon_t = boost::geometry::model::polygon<point_t>;
|
using polygon_t = boost::geometry::model::polygon<point_t>;
|
||||||
|
using polygon_bands_t = std::vector<std::vector<segment_t>>;
|
||||||
using box_t = boost::geometry::model::box<point_t>;
|
using box_t = boost::geometry::model::box<point_t>;
|
||||||
|
|
||||||
using polygon_position_t = std::size_t;
|
using polygon_position_t = std::size_t;
|
||||||
@ -42,7 +46,7 @@ struct LocationDependentData
|
|||||||
void loadLocationDependentData(const boost::filesystem::path &file_path);
|
void loadLocationDependentData(const boost::filesystem::path &file_path);
|
||||||
|
|
||||||
rtree_t rtree;
|
rtree_t rtree;
|
||||||
std::vector<std::pair<polygon_t, std::size_t>> polygons;
|
std::vector<std::pair<polygon_bands_t, std::size_t>> polygons;
|
||||||
std::vector<properties_t> properties;
|
std::vector<properties_t> properties;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/function_output_iterator.hpp>
|
#include <boost/function_output_iterator.hpp>
|
||||||
|
#include <boost/geometry/algorithms/equals.hpp>
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -109,7 +110,62 @@ void LocationDependentData::loadLocationDependentData(const boost::filesystem::p
|
|||||||
}
|
}
|
||||||
auto envelop = boost::geometry::return_envelope<box_t>(polygon);
|
auto envelop = boost::geometry::return_envelope<box_t>(polygon);
|
||||||
bounding_boxes.emplace_back(envelop, polygons.size());
|
bounding_boxes.emplace_back(envelop, polygons.size());
|
||||||
polygons.emplace_back(std::make_pair(polygon, properties_index));
|
|
||||||
|
// here is a part of ExtractPolygon::ExtractPolygon code
|
||||||
|
// TODO: remove copy overhead
|
||||||
|
constexpr const int32_t segments_per_band = 10;
|
||||||
|
constexpr const int32_t max_bands = 10000;
|
||||||
|
const auto y_min = envelop.min_corner().y();
|
||||||
|
const auto y_max = envelop.max_corner().y();
|
||||||
|
|
||||||
|
std::vector<segment_t> segments;
|
||||||
|
auto add_ring = [&segments](const auto &ring) {
|
||||||
|
auto it = ring.begin();
|
||||||
|
const auto end = ring.end();
|
||||||
|
|
||||||
|
BOOST_ASSERT(it != end);
|
||||||
|
auto last_it = it++;
|
||||||
|
while (it != end)
|
||||||
|
{
|
||||||
|
segments.emplace_back(*last_it, *it);
|
||||||
|
last_it = it++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
add_ring(polygon.outer());
|
||||||
|
for (const auto &ring : polygon.inners())
|
||||||
|
add_ring(ring);
|
||||||
|
|
||||||
|
int32_t num_bands = static_cast<int32_t>(segments.size()) / segments_per_band;
|
||||||
|
if (num_bands < 1)
|
||||||
|
{
|
||||||
|
num_bands = 1;
|
||||||
|
}
|
||||||
|
else if (num_bands > max_bands)
|
||||||
|
{
|
||||||
|
num_bands = max_bands;
|
||||||
|
}
|
||||||
|
|
||||||
|
polygon_bands_t bands(num_bands);
|
||||||
|
const auto dy = (y_max - y_min) / num_bands;
|
||||||
|
|
||||||
|
for (const auto &segment : segments)
|
||||||
|
{
|
||||||
|
using coord_t = boost::geometry::traits::coordinate_type<point_t>::type;
|
||||||
|
const std::pair<coord_t, coord_t> mm =
|
||||||
|
std::minmax(segment.first.y(), segment.second.y());
|
||||||
|
const auto band_min = std::min<coord_t>(num_bands - 1, (mm.first - y_min) / dy);
|
||||||
|
const auto band_max = std::min<coord_t>(
|
||||||
|
num_bands, ((mm.second - y_min) / dy) + 1); // TODO: use integer coordinates
|
||||||
|
|
||||||
|
for (auto band = band_min; band < band_max; ++band)
|
||||||
|
{
|
||||||
|
bands[band].push_back(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// EOC
|
||||||
|
|
||||||
|
polygons.emplace_back(std::make_pair(bands, properties_index));
|
||||||
};
|
};
|
||||||
|
|
||||||
for (rapidjson::SizeType ifeature = 0; ifeature < features_array.Size(); ifeature++)
|
for (rapidjson::SizeType ifeature = 0; ifeature < features_array.Size(); ifeature++)
|
||||||
@ -156,7 +212,59 @@ LocationDependentData::properties_t LocationDependentData::operator()(const poin
|
|||||||
// Search the R-tree and collect a Lua table of tags that correspond to the location
|
// Search the R-tree and collect a Lua table of tags that correspond to the location
|
||||||
rtree.query(boost::geometry::index::intersects(point) &&
|
rtree.query(boost::geometry::index::intersects(point) &&
|
||||||
boost::geometry::index::satisfies([this, &point](const rtree_t::value_type &v) {
|
boost::geometry::index::satisfies([this, &point](const rtree_t::value_type &v) {
|
||||||
return boost::geometry::within(point, polygons[v.second].first);
|
|
||||||
|
// Simple point-in-polygon algorithm adapted from
|
||||||
|
// https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
|
||||||
|
|
||||||
|
const auto &envelop = v.first;
|
||||||
|
const auto &bands = polygons[v.second].first;
|
||||||
|
|
||||||
|
const auto y_min = envelop.min_corner().y();
|
||||||
|
const auto y_max = envelop.max_corner().y();
|
||||||
|
auto dy = (y_max - y_min) / bands.size();
|
||||||
|
|
||||||
|
std::size_t band = (point.y() - y_min) / dy;
|
||||||
|
if (band >= bands.size())
|
||||||
|
{
|
||||||
|
band = bands.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inside = false;
|
||||||
|
|
||||||
|
for (const auto &segment : bands[band])
|
||||||
|
{
|
||||||
|
const auto point_x = point.x(), point_y = point.y();
|
||||||
|
const auto from_x = segment.first.x(), from_y = segment.first.y();
|
||||||
|
const auto to_x = segment.second.x(), to_y = segment.second.y();
|
||||||
|
|
||||||
|
if (to_y == from_y)
|
||||||
|
{ // handle horizontal segments: check if on boundary or skip
|
||||||
|
if ((to_y == point_y) &&
|
||||||
|
(from_x == point_x || (to_x > point_x) != (from_x > point_x)))
|
||||||
|
return true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((to_y > point_y) != (from_y > point_y))
|
||||||
|
{
|
||||||
|
const auto ax = to_x - from_x;
|
||||||
|
const auto ay = to_y - from_y;
|
||||||
|
const auto tx = point_x - from_x;
|
||||||
|
const auto ty = point_y - from_y;
|
||||||
|
|
||||||
|
const auto cross_product = tx * ay - ax * ty;
|
||||||
|
|
||||||
|
if (cross_product == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ((ay > 0) == (cross_product > 0))
|
||||||
|
{
|
||||||
|
inside = !inside;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inside;
|
||||||
}),
|
}),
|
||||||
boost::make_function_output_iterator(std::ref(merger)));
|
boost::make_function_output_iterator(std::ref(merger)));
|
||||||
|
|
||||||
|
@ -51,10 +51,10 @@ BOOST_AUTO_TEST_CASE(polygon_tests)
|
|||||||
|
|
||||||
LocationDependentData data(fixture.temporary_file);
|
LocationDependentData data(fixture.temporary_file);
|
||||||
BOOST_CHECK(data(point_t(0, 0)).empty());
|
BOOST_CHECK(data(point_t(0, 0)).empty());
|
||||||
BOOST_CHECK(data(point_t(1, 1)).empty());
|
BOOST_CHECK(!data(point_t(1, 1)).empty());
|
||||||
BOOST_CHECK(data(point_t(0, 1)).empty());
|
BOOST_CHECK(!data(point_t(0, 1)).empty());
|
||||||
BOOST_CHECK(data(point_t(0.5, -0.5)).empty());
|
BOOST_CHECK(!data(point_t(0.5, -0.5)).empty());
|
||||||
BOOST_CHECK(data(point_t(0, -3)).empty());
|
BOOST_CHECK(!data(point_t(0, -3)).empty());
|
||||||
BOOST_CHECK(!data(point_t(-0.75, 0.75)).empty());
|
BOOST_CHECK(!data(point_t(-0.75, 0.75)).empty());
|
||||||
BOOST_CHECK(!data(point_t(-2, 6)).empty());
|
BOOST_CHECK(!data(point_t(-2, 6)).empty());
|
||||||
BOOST_CHECK_EQUAL(boost::get<double>(data(point_t(2, 0))["answer"]), 42.);
|
BOOST_CHECK_EQUAL(boost::get<double>(data(point_t(2, 0))["answer"]), 42.);
|
||||||
@ -110,13 +110,61 @@ BOOST_AUTO_TEST_CASE(polygon_merging_tests)
|
|||||||
]})json");
|
]})json");
|
||||||
|
|
||||||
LocationDependentData data(fixture.temporary_file);
|
LocationDependentData data(fixture.temporary_file);
|
||||||
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(0, 0))["answer"]), "a");
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(-3, 3))["answer"]), "a");
|
||||||
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(-3, 1))["answer"]), "a");
|
||||||
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(-3, -3))["answer"]), "a");
|
||||||
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(0, 3))["answer"]), "a");
|
||||||
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(1, 0))["answer"]), "a");
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(1, 0))["answer"]), "a");
|
||||||
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(2, 0))["answer"]), "a");
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(2, -3))["answer"]), "a");
|
||||||
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(3, 0))["answer"]), "b");
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(3, 0))["answer"]), "a");
|
||||||
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(4, 0))["answer"]), "b");
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(4, 3))["answer"]), "b");
|
||||||
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(6, 0))["answer"]), "b");
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(6, 1))["answer"]), "b");
|
||||||
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(7, 0))["answer"]), "c");
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(7, 0))["answer"]), "b");
|
||||||
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(8, 3))["answer"]), "c");
|
||||||
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(8, -1))["answer"]), "c");
|
||||||
|
BOOST_CHECK_EQUAL(boost::get<std::string>(data(point_t(8, -3))["answer"]), "c");
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(staircase_polygon)
|
||||||
|
{
|
||||||
|
LocationDataFixture fixture(R"json({
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": { "answer": "a" },
|
||||||
|
"geometry": { "type": "Polygon", "coordinates": [ [ [0, 0], [3, 0], [3, 3], [2, 3], [2, 2], [1, 2], [1, 1], [0, 1], [0, 0] ] ] }
|
||||||
|
}
|
||||||
|
]})json");
|
||||||
|
|
||||||
|
LocationDependentData data(fixture.temporary_file);
|
||||||
|
|
||||||
|
// all corners
|
||||||
|
BOOST_CHECK(!data(point_t(0, 0)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(0, 1)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(1, 1)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(1, 2)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(2, 2)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(2, 3)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(3, 3)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(3, 0)).empty());
|
||||||
|
|
||||||
|
// at x = 1
|
||||||
|
BOOST_CHECK(data(point_t(1, -0.5)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(1, 0)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(1, 0.5)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(1, 1.5)).empty());
|
||||||
|
BOOST_CHECK(data(point_t(1, 2.5)).empty());
|
||||||
|
BOOST_CHECK(data(point_t(3.5, 2)).empty());
|
||||||
|
|
||||||
|
// at y = 2
|
||||||
|
BOOST_CHECK(data(point_t(0.5, 2)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(1, 2)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(1.5, 2)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(2, 2)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(2.5, 2)).empty());
|
||||||
|
BOOST_CHECK(!data(point_t(3, 2)).empty());
|
||||||
|
BOOST_CHECK(data(point_t(3.5, 2)).empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
Loading…
Reference in New Issue
Block a user