diff --git a/include/boost/geometry/algorithms/detail/overlay/approximately_equals.hpp b/include/boost/geometry/algorithms/detail/overlay/approximately_equals.hpp index 1f41085dc1..31f9d7a63f 100644 --- a/include/boost/geometry/algorithms/detail/overlay/approximately_equals.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/approximately_equals.hpp @@ -21,6 +21,16 @@ namespace boost { namespace geometry namespace detail { namespace overlay { +// Value for approximately_equals used by get_cluster and sort_by_side +template +struct common_approximately_equals_epsilon +{ + static T value() + { + return T(100); + } +}; + template inline bool approximately_equals(Point1 const& a, Point2 const& b, E const& epsilon_multiplier) diff --git a/include/boost/geometry/algorithms/detail/overlay/enrich_intersection_points.hpp b/include/boost/geometry/algorithms/detail/overlay/enrich_intersection_points.hpp index b94bba6c5a..962c68be69 100644 --- a/include/boost/geometry/algorithms/detail/overlay/enrich_intersection_points.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/enrich_intersection_points.hpp @@ -185,7 +185,8 @@ inline void enrich_assign(Operations& operations, Turns& turns, << " nxt=" << op.enriched.next_ip_index << " / " << op.enriched.travels_to_ip_index << " [vx " << op.enriched.travels_to_vertex_index << "]" - << (turns[indexed_op.turn_index].discarded ? " discarded" : "") + << (turns[indexed_op.turn_index].discarded ? " [discarded]" : "") + << (op.enriched.startable ? "" : " [not startable]") << std::endl; } #endif diff --git a/include/boost/geometry/algorithms/detail/overlay/get_clusters.hpp b/include/boost/geometry/algorithms/detail/overlay/get_clusters.hpp index 2747fa68ba..e4969d658d 100644 --- a/include/boost/geometry/algorithms/detail/overlay/get_clusters.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/get_clusters.hpp @@ -35,28 +35,20 @@ namespace detail { namespace overlay template struct sweep_equal_policy { -private: - template - static inline T threshold() - { - // Points within some epsilons are considered as equal. - return T(100); - } + public: // Returns true if point are considered equal (within an epsilon) template static inline bool equals(P const& p1, P const& p2) { using coor_t = typename coordinate_type

::type; - return approximately_equals(p1, p2, threshold()); + return approximately_equals(p1, p2, common_approximately_equals_epsilon::value()); } template static inline bool exceeds(T value) { - // This threshold is an arbitrary value - // as long as it is bigger than the used value above - T const limit = T(1) / threshold(); + T const limit = T(1) / common_approximately_equals_epsilon::value(); return value > limit; } }; diff --git a/include/boost/geometry/algorithms/detail/overlay/get_turn_info.hpp b/include/boost/geometry/algorithms/detail/overlay/get_turn_info.hpp index 4e10c07bde..0037d67dc3 100644 --- a/include/boost/geometry/algorithms/detail/overlay/get_turn_info.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/get_turn_info.hpp @@ -1,6 +1,6 @@ -// Boost.Geometry (aka GGL, Generic Geometry Library) +// Boost.Geometry -// Copyright (c) 2007-2021 Barend Gehrels, Amsterdam, the Netherlands. +// Copyright (c) 2007-2023 Barend Gehrels, Amsterdam, the Netherlands. // Copyright (c) 2017 Adam Wulkiewicz, Lodz, Poland. // This file was modified by Oracle on 2015-2022. @@ -855,24 +855,15 @@ struct equal : public base_turn_handler int const side_pk_p = has_pk ? side.pk_wrt_p1() : 0; int const side_qk_p = has_qk ? side.qk_wrt_p1() : 0; - if (BOOST_GEOMETRY_CONDITION(VerifyPolicy::use_side_verification) - && has_pk && has_qk && side_pk_p == side_qk_p) + if (has_pk && has_qk && side_pk_p == side_qk_p) { // They turn to the same side, or continue both collinearly - // Without rescaling, to check for union/intersection, - // try to check side values (without any thresholds) - auto const dm_pk_q2 - = get_distance_measure(range_q.at(1), range_q.at(2), range_p.at(2), - umbrella_strategy); - auto const dm_qk_p2 - = get_distance_measure(range_p.at(1), range_p.at(2), range_q.at(2), - umbrella_strategy); - - if (dm_qk_p2.measure != dm_pk_q2.measure) + // To check for union/intersection, try to check side values + int const side_qk_p2 = side.qk_wrt_p2(); + + if (opposite(side_qk_p2, side_pk_q2)) { - // A (possibly very small) difference is detected, which - // can be used to distinguish between union/intersection - ui_else_iu(dm_qk_p2.measure < dm_pk_q2.measure, ti); + ui_else_iu(side_pk_q2 == 1, ti); return; } } diff --git a/include/boost/geometry/algorithms/detail/overlay/sort_by_side.hpp b/include/boost/geometry/algorithms/detail/overlay/sort_by_side.hpp index e30fc4d675..7fc718449b 100644 --- a/include/boost/geometry/algorithms/detail/overlay/sort_by_side.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/sort_by_side.hpp @@ -323,7 +323,7 @@ public : double >::type; - ct_type const tolerance = 1000000000; + static auto const tolerance = common_approximately_equals_epsilon::value(); int offset = 0; while (approximately_equals(point_from, turn.point, tolerance) diff --git a/include/boost/geometry/algorithms/discrete_hausdorff_distance.hpp b/include/boost/geometry/algorithms/discrete_hausdorff_distance.hpp index 115909c06a..bc479e8492 100644 --- a/include/boost/geometry/algorithms/discrete_hausdorff_distance.hpp +++ b/include/boost/geometry/algorithms/discrete_hausdorff_distance.hpp @@ -15,17 +15,6 @@ #ifndef BOOST_GEOMETRY_ALGORITHMS_DISCRETE_HAUSDORFF_DISTANCE_HPP #define BOOST_GEOMETRY_ALGORITHMS_DISCRETE_HAUSDORFF_DISTANCE_HPP -#include - -#ifdef BOOST_GEOMETRY_DEBUG_HAUSDORFF_DISTANCE -#include -#endif - -#include -#include -#include -#include - #include #include #include @@ -39,6 +28,11 @@ #include #include +#include +#include +#include +#include + // Note that in order for this to work umbrella strategy has to contain // index strategies. #ifdef BOOST_GEOMETRY_ENABLE_SIMILARITY_RTREE diff --git a/include/boost/geometry/algorithms/similarity.hpp b/include/boost/geometry/algorithms/similarity.hpp new file mode 100644 index 0000000000..4ba59376c7 --- /dev/null +++ b/include/boost/geometry/algorithms/similarity.hpp @@ -0,0 +1,481 @@ +// Boost.Geometry + +// Copyright (c) 2023 Barend Gehrels, Amsterdam, the Netherlands. + +// 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_ALGORITHMS_SIMILARITY_HPP +#define BOOST_GEOMETRY_ALGORITHMS_SIMILARITY_HPP + +#include +#include +#include + +#include +#include + +// #define DEBUG_EXTRA_RING +#include + +namespace boost { namespace geometry +{ + +template +struct similarity_info +{ + CoordinateType distance = 0; + bool is_reversed = false; +}; + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace similarity +{ + +// Structure containing projections of a geometry on another geometry, +// and additionally the points of the geometry itself +template +struct projection +{ + using coor_t = typename coordinate_type::type; + + // Indicates if it is a projection of the other geometry, or a point of the own geometry. + bool is_projection = true; + + // For a projection of P on Q, this is the index in P + // For a non projected point, it is also the index in P + std::size_t source_index = 0; + + // For a projection of P on Q, this is the index of a segment in Q + // For a non projected point, it is identical to source_index + std::size_t segment_index = 0; + + // For a projection of P on Q, this is the offset on the segment in Q + // For a non projected point, the offset is always 0.0 + coor_t offset = 0; + + // For a projection of P on Q, this is the distance from point P + // to its projection in Q. + // For a non projected point, it is 0.0 + coor_t distance = 0; + + std::size_t sort_index = 0; + + // The projected point, or the point of the geometry itself + Point point; +}; + +template +auto get_projection(std::size_t index, Point const& point, Geometry const& target) +{ + using coor_t = typename coordinate_type::type; + using closest_strategy = geometry::strategy::closest_points::detail::compute_closest_point_to_segment; + projection result; + + for (std::size_t i = 0; i + 1 < target.size(); i++) + { + auto const& current = target[i]; + auto const& next = target[i + 1]; + projection other; + auto const pp = closest_strategy::apply(point, current, next); + geometry::convert(pp, other.point); + other.distance = geometry::comparable_distance(point, other.point); + if (i == 0 || other.distance < result.distance) + { + result = other; + result.is_projection = true; + result.source_index = index; + result.segment_index = i; + result.offset = geometry::distance(current, result.point); + } + } + result.distance = geometry::distance(result.point, point); + return result; +} + +// Calculate projected points of all points of "source" on "target" +// The result is therefore similar to target. +template +auto get_projections(Geometry1 const& source, Geometry2 const& target) +{ + std::vector::type>> result; + result.resize(source.size()); + detail::for_each_with_index(source, [&target, &result](std::size_t index, const auto& point) + { + result[index] = get_projection(index, point, target); + }); + return result; +} + +// Reverse in the projections: the segment index and the offset. +// All the rest (source_index, distance, point) is the same for the reversed version. +// >------+-----+------> 4 points, 3 segments, last segment index is 2 +// * segment_index 0, reverse: 2 (2 - 0) +// * segment_index 1, reverse: 1 (2 - 1) +// * segment_index 2, reverse: 0 (2 - 2) +template +void reverse_projections(Geometry2 const& target, Projections& projections) +{ + std::size_t const last_segment_index = target.size() - 2; + for (auto& projection : projections) + { + auto const si = projection.segment_index; + if (si > last_segment_index) + { + // Defensive check. + continue; + } + auto const segment_length = geometry::distance(target[si], target[si + 1]); + projection.segment_index = last_segment_index - si; + projection.offset = segment_length - projection.offset; + } +} + +// Add "self" points on collection which was created projecting an other line +// onto this line. +// Sort all points along the line. +template +void enrich_with_self(It begin, It end, Enriched& enriched) +{ + using info_t = std::decay_t; + std::size_t index = 0; + for (It it = begin; it != end; ++it) + { + info_t info; + info.is_projection = false; + info.source_index = index; + info.segment_index = index; + info.point = *it; + enriched.push_back(std::move(info)); + index++; + } + + std::sort(enriched.begin(), enriched.end(), + [](const auto& lhs, const auto& rhs){ + auto const lt = std::tie(lhs.segment_index, lhs.offset, lhs.source_index, lhs.is_projection); + auto const rt = std::tie(rhs.segment_index, rhs.offset, rhs.source_index, rhs.is_projection); + return lt < rt; + }); +} + +template +bool is_reversed(Projections const& projections) +{ + std::size_t count_right_order = 0; + std::size_t count_wrong_order = 0; + for (std::size_t i = 0; i + 1 < projections.size(); i++) + { + auto const current = std::tie(projections[i].segment_index, projections[i].offset); + auto const next = std::tie(projections[i + 1].segment_index, projections[i + 1].offset); + + if (current <= next) + { + count_right_order++; + } + else + { + count_wrong_order++; + } + } + + return count_wrong_order > count_right_order; +} + +// Makes a triangle. The first is supposed to form a segment. +template +int fill_triangle(Projection const& s0, Projection const& s1, Projection const& p,Ring& triangle) +{ + using point_t = typename point_type::type; + using side_strategy = typename strategy::side::services::default_strategy + < + typename cs_tag::type + >::type; + + int const side = side_strategy::apply(s0.point, s1.point, p.point); + + if (side == 0) + { + // It is collinear. This happens a lot when the inputs are the same. + // Constructing the triangle and calculating its area can be skipped. + return 0; + } + + + // Construct the triangle clockwise. + if (side == -1) + { + triangle[0] = s0.point; + triangle[1] = s1.point; + triangle[2] = p.point; + } + else + { + triangle[0] = s1.point; + triangle[1] = s0.point; + triangle[2] = p.point; + } + + triangle[3] = triangle[0]; + + return 1; +} + +template +auto get_areal_sum(It p_it, It p_end, It q_it, It q_end, Ring& triangle, Visitor& visitor) +{ + using point_t = typename boost::geometry::point_type::type; + using side_strategy = typename boost::geometry::strategy::side::services::default_strategy + < + typename boost::geometry::cs_tag::type + >::type; + + std::vector left, right; + int ring_code = 0; + typename coordinate_type::type sum_area = 0; + + auto add_triangle = [&sum_area, &triangle, &visitor](int source_index, auto const& s0, auto const& s1, auto const& p) + { + int const code = fill_triangle(s0, s1, p, triangle); + if (code != 0) + { + auto const area = geometry::area(triangle); + + // The area should be positive - taking the abs is a defensive check. + // In some cases the area is subtracted. + sum_area += code * std::abs(area); + + visitor.visit_triangle(triangle, s0, s1, p, source_index, code); + } + }; + + auto finish_ring = [&]() + { + if (left.empty() || right.empty()) + { + return; + } + + if (ring_code == 1 || ring_code == 4) + { + std::reverse(left.begin(), left.end()); + } + else if (ring_code == 2 || ring_code == 3) + { + std::reverse(right.begin(), right.end()); + } + + Ring extra_ring(left.begin(), left.end()); + for (const auto& p : right) + { + extra_ring.push_back(p); + } + extra_ring.push_back(left.front()); + + auto const area = std::abs(geometry::area(extra_ring)); + sum_area += area; +#if 0 + std::cout << " END RING " << area; +#endif + std::decay_t dummy; + visitor.visit_triangle(extra_ring, dummy, dummy, dummy, 0, 0); + + left.clear(); + right.clear(); + }; + + auto p_prev = p_it++; + auto q_prev = q_it++; + +#ifdef DEBUG_EXTRA_RING + int index = 0; +#endif + for ( ; p_it != p_end; ++p_prev, ++q_prev, ++p_it, ++q_it) + { + int const side_q0_p = side_strategy::apply(p_prev->point, p_it->point, q_prev->point); + int const side_q1_p = side_strategy::apply(p_prev->point, p_it->point, q_it->point); + int const side_p0_q = side_strategy::apply(q_prev->point, q_it->point, p_prev->point); + int const side_p1_q = side_strategy::apply(q_prev->point, q_it->point, p_it->point); + + int const code_p = side_p0_q == -1 && side_p1_q == -1 ? 1 + : side_p0_q == 1 && side_p1_q == 1 ? 2 + : 0; + + int const code_q = side_q0_p == -1 && side_q1_p == -1 ? 3 + : side_q0_p == 1 && side_q1_p == 1 ? 4 + : 0; + + // If both p and q are one-sided, prefer taking the previous code. + // If there is not a previous code, prefer a non projected source. + + int const new_ring_code + = code_p > 0 && code_q > 0 ? (code_p == ring_code ? code_p : code_q == ring_code ? code_q : p_prev->is_projection && p_it->is_projection ? code_q : p_prev->is_projection || p_it->is_projection ? code_q : code_p) + : code_p > 0 ? code_p + : code_q > 0 ? code_q + : 0; + // int const new_ring_code = 0; + + bool const other_ring = new_ring_code != ring_code; + if (other_ring) + { + finish_ring(); + } + + ring_code = new_ring_code; + +#ifdef DEBUG_EXTRA_RING + //if (new_ring_code > 0) + if (p_prev->source_index != q_prev->source_index || p_it->source_index != q_it->source_index) + { + std::cout << index++ << " : " + << " sources: " << p_prev->source_index << " " << p_it->source_index << " " << q_prev->source_index << " " << q_it->source_index + << " proj: " << p_prev->is_projection << " " << p_it->is_projection << " " << q_prev->is_projection << " " << q_it->is_projection + << " sides: " << " " << side_p0_q << " " << side_p1_q << " " << side_q0_p << " " << side_q1_p + << " code: " << code_p << " " << code_q << " -> " << new_ring_code + << std::endl + ; + } +#endif + + + if (new_ring_code > 0) + { + if (other_ring) + { + // std::cout << " FIRST"; + left.push_back(p_prev->point); + right.push_back(q_prev->point); + } + // std::cout << " NEXT"; + left.push_back(p_it->point); + right.push_back(q_it->point); +#ifdef DEBUG_EXTRA_RING0 + std::cout << " RING" << std::endl; +#endif + continue; + } + +#ifdef DEBUG_EXTRA_RING0 + std::cout << std::endl; +#endif + + const bool p_both_sides = side_p0_q * side_p1_q == -1; + const bool q_both_sides = side_q0_p * side_q1_p == -1; + + if (p_both_sides && q_both_sides) + { + // Segments cross, make two triangles on either side of segment p + add_triangle(0, *p_prev, *p_it, *q_prev); + add_triangle(1, *p_prev, *p_it, *q_it); + } + else + { + add_triangle(0, *p_prev, *p_it, *q_prev); + add_triangle(1, *q_prev, *q_it, *p_it); + } + + // if (fill_quadrilateral(*p_prev, *p_it, *q_prev, *q_it, ring)) + // { + // auto const area = geometry::area(ring); + + // // The area should be positive - taking the abs is a defensive check. + // sum_area += std::abs(area); + + // visitor.visit_quadrilateral(ring, *p_prev, *p_it, *q_prev, *q_it); + // } + } + finish_ring(); + + return sum_area; +} + +}} // namespace detail::similarity +#endif // DOXYGEN_NO_DETAIL + + +struct similarity_default_visitor +{ + template void visit_triangle(T const& , I const& , I const& , I const& , int, int) {} + template void visit_projections(int, C const&) {} +}; + + +template +auto similarity(Geometry1 const& p, Geometry2 const& q, Visitor& visitor = similarity_default_visitor()) +{ + using namespace detail::similarity; + + using point_t = typename point_type::type; + using coor_t = typename coordinate_type::type; + + similarity_info result; + + // Get projections of P on Q. + // Later, the points of Q are added to these projections. + // Therefore, this variable can be seen as an enriched version of range Q. + auto q_enriched = get_projections(p, q); + + result.is_reversed = is_reversed(q_enriched); + + if (result.is_reversed) + { + // Reverse Q to align with P, and add own points using reversed iterators + reverse_projections(q, q_enriched); + enrich_with_self(boost::rbegin(q), boost::rend(q), q_enriched); + } + else + { + enrich_with_self(boost::begin(q), boost::end(q), q_enriched); + } + + // Do the same for P. Because Q is possibly reversed to align with P, + // such a check is not necessary for P again. + auto p_enriched = get_projections(q, p); + enrich_with_self(boost::begin(p), boost::end(p), p_enriched); + + if (q_enriched.size() != p_enriched.size()) + { + // Defensive check. + // They should always have the same size: + // each other projected points, plus their own points. + return result; + } + + for (std::size_t i = 0; i < p_enriched.size(); i++) + { + p_enriched[i].sort_index = q_enriched[i].source_index; + q_enriched[i].sort_index = p_enriched[i].source_index; + } + + // Optional debug step. + visitor.visit_projections(0, p_enriched); + visitor.visit_projections(1, q_enriched); + + using ring_t = boost::geometry::model::ring; + + ring_t triangle; + triangle.resize(4); + + auto const sum_area = get_areal_sum(boost::begin(p_enriched), boost::end(p_enriched), + boost::begin(q_enriched), boost::end(q_enriched), triangle, visitor); + + auto const length = (std::min)(geometry::perimeter(p), geometry::perimeter(q)); + + std::cout << "AREA: " << sum_area << std::endl; + std::cout << "LENS p: " << geometry::length(p) << " " << geometry::perimeter(p) << std::endl; + std::cout << "LENS q: " << geometry::length(q) << " " << geometry::perimeter(q) << std::endl; + result.distance = length > 0 ? sum_area / length : std::sqrt(sum_area); + return result; +} + +template +auto average_distance(Geometry1 const& a, Geometry2 const& b) +{ + similarity_default_visitor v; + return similarity(a, b, v).distance; +} + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_ALGORITHMS_SIMILARITY_HPP diff --git a/include/boost/geometry/algorithms/similarity_with_partial.hpp b/include/boost/geometry/algorithms/similarity_with_partial.hpp new file mode 100644 index 0000000000..be857b3a40 --- /dev/null +++ b/include/boost/geometry/algorithms/similarity_with_partial.hpp @@ -0,0 +1,485 @@ +// Boost.Geometry + +// Copyright (c) 2023 Barend Gehrels, Amsterdam, the Netherlands. + +// 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_ALGORITHMS_SIMILARITY_HPP +#define BOOST_GEOMETRY_ALGORITHMS_SIMILARITY_HPP + +#include +#include + +#include +#include +#include + + +namespace boost { namespace geometry +{ + +struct range_info +{ + std::size_t begin{}; + std::size_t end{}; +}; + +struct partial_info +{ + double ratio{0.0}; + std::size_t index; + double sim_distance{}; +}; + +template +struct similarity_info +{ + using coor_t = typename coordinate_type::type; + + coor_t distance{}; + bool is_reversed{false}; + bool is_partial{false}; + + std::size_t index; // TODO +}; + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace similarity +{ + +// Structure containing projections of a geometry on another geometry, +// and additionally the points of the geometry itself +template +struct projection +{ + using point_type = Point; + using coor_t = typename coordinate_type::type; + + // Indicates if it is a projection of the other geometry, or a point of the own geometry. + bool is_projection{true}; + + // For a projection of P on Q, this is the index in P + // For a non projected point, it is also the index in P + std::size_t source_index{}; + + // For a projection of P on Q, this is the index of a segment in Q + // For a non projected point, it is identical to source_index + std::size_t segment_index{}; + + // For a projection of P on Q, this is the offset on the segment in Q + // For a non projected point, the offset is always 0.0 + coor_t offset = 0; + + // For a projection of P on Q, this is the distance from point P + // to its projection in Q. + // For a non projected point, it is 0.0 + coor_t distance = 0; + + // The projected point, or the point of the geometry itself + Point point; +}; + +template +auto get_projection(std::size_t index, Point const& point, Geometry const& target) +{ + using closest_strategy = geometry::strategy::closest_points::detail::compute_closest_point_to_segment; + projection result{}; + + for (std::size_t i = 0; i + 1 < target.size(); i++) + { + auto const& current = target[i]; + auto const& next = target[i + 1]; + projection other; + auto const pp = closest_strategy::apply(point, current, next); + geometry::convert(pp, other.point); + other.distance = geometry::distance(point, other.point); + if (i == 0 || other.distance < result.distance) + { + result = other; + result.is_projection = true; + result.source_index = index; + result.segment_index = i; + result.offset = geometry::distance(current, result.point); + } + } + return result; +} + +// Calculate projected points of all points of "source" on "target" +// The result is therefore similar to target. +template +auto get_projections(Geometry1 const& source, Geometry2 const& target) +{ + std::vector::type>> result; + result.resize(source.size()); + detail::for_each_with_index(source, [&target, &result](std::size_t index, const auto& point) + { + result[index] = get_projection(index, point, target); + }); + return result; +} + +// Reverse in the projections: the segment index and the offset. +// All the rest (source_index, distance, point) is the same for the reversed version. +// >------+-----+------> 4 points, 3 segments, last segment index is 2 +// * segment_index 0, reverse: 2 (2 - 0) +// * segment_index 1, reverse: 1 (2 - 1) +// * segment_index 2, reverse: 0 (2 - 2) +template +void reverse_projections(Geometry2 const& target, Projections& projections) +{ + std::size_t const last_segment_index = target.size() - 2; + for (auto& projection : projections) + { + auto const si = projection.segment_index; + if (si > last_segment_index) + { + // Defensive check. + // std::cerr << "OOPS" << std::endl; + continue; + } + auto const segment_length = geometry::distance(target[si], target[si + 1]); + // if (projection.offset > segment_length) + // { + // std::cerr << "OOPS 2" << std::endl; + // } + projection.segment_index = last_segment_index - si; + projection.offset = segment_length - projection.offset; + } +} + +// Add "self" points on collection which was created projecting an other line +// onto this line. +// Sort all points along the line. +template +void enrich_with_self(It begin, It end, Enriched& enriched) +{ + using info_t = std::decay_t; + std::size_t index = 0; + for (It it = begin; it != end; ++it) + { + info_t info; + info.is_projection = false; + info.source_index = index; + info.segment_index = index; + info.point = *it; + enriched.push_back(std::move(info)); + index++; + } + + std::sort(enriched.begin(), enriched.end(), + [](const auto& lhs, const auto& rhs){ + auto const lt = std::tie(lhs.segment_index, lhs.offset, lhs.source_index, lhs.is_projection); + auto const rt = std::tie(rhs.segment_index, rhs.offset, rhs.source_index, rhs.is_projection); + return lt < rt; + }); +} + +template +bool is_reversed(Projections const& projections) +{ + std::size_t count_right_order = 0; + std::size_t count_wrong_order = 0; + for (std::size_t i = 0; i + 1 < projections.size(); i++) + { + auto const current = std::tie(projections[i].segment_index, projections[i].offset); + auto const next = std::tie(projections[i + 1].segment_index, projections[i + 1].offset); + + if (current <= next) + { + count_right_order++; + } + else + { + count_wrong_order++; + } + } + + return count_wrong_order > count_right_order; +} + +template +bool fill_quadrilateral(Projection const& p0, Projection const& p1, Projection const& q0, Projection const& q1, Ring& ring) +{ + using point_t = typename point_type::type; + using side_strategy = typename strategy::side::services::default_strategy + < + typename cs_tag::type + >::type; + + int const side_q0_p = side_strategy::apply(p0.point, p1.point, q0.point); + int const side_q1_p = side_strategy::apply(p0.point, p1.point, q1.point); + int const side_p0_q = side_strategy::apply(q0.point, q1.point, p0.point); + int const side_p1_q = side_strategy::apply(q0.point, q1.point, p1.point); + + if (side_q0_p == 0 && side_q1_p == 0 && side_p0_q == 0 && side_p1_q == 0) + { + // All sides are the same. This happens a lot when the inputs are the same. + // Constructing the quadrilateral and calculating its area can be skipped. + return false; + } + + // Construct a clockwise ring (a quadrilateral). + // For example + // [2] +-----------+ [3] [i + 1] (1) + // | | + // p q + // | | + // [1] +-----------+ [0] [i] (0) + // Here q(0) is right (-1) from p, so taken q(0) first. + // (Stated otherwise, p(0) is left (1) from q) + // Then take p(0) as [1], second point. + // As third point [2] then p(1) is taken because it is left (1) from q. + // Finally q(1) used as [3] and the ring is closed [4]. + bool const first_q0 = side_q0_p == -1 || side_p0_q == 1; + bool const third_p1 = side_p1_q == 1 || side_q1_p == -1; + + ring[0] = (first_q0 ? q0 : p0).point; + ring[1] = (first_q0 ? p0 : q0).point; + ring[2] = (third_p1 ? p1 : q1).point; + ring[3] = (third_p1 ? q1 : p1).point; + ring[4] = ring[0]; + + return true; +} + +template +auto get_areal_sum(It p_it, It p_end, It q_it, It q_end, Ring& ring, Visitor& visitor) +{ + double sum_area = 0.0; + + auto p_prev = p_it++; + auto q_prev = q_it++; + + for ( ; p_it != p_end; ++p_it, ++q_it) + { + if (fill_quadrilateral(*p_prev, *p_it, *q_prev, *q_it, ring)) + { + double const area = geometry::area(ring); + + // if (debug && area < 0.0) + // { + // std::cout << (area < 0.0 ? "*WRONG" : " right") << " area for " << i + // << " sides: " << side_p0_q << " " << side_p1_q << " " << side_q0_p << " " << side_q1_p + // << " " << " -> " << kScale * area << std::endl; + // } + + // The area should be positive - taking the abs is a defensive check. + sum_area += std::abs(area); + + visitor.visit_quadrilateral(ring, *p_prev, *p_it, *q_prev, *q_it); + } + + p_prev = p_it; + q_prev = q_it; + } + return sum_area; +} + +// Finds the best division, the index where P/Q are more similar on one side and less on the other side. +// ----------- Q, close to P at the start, but than farther away +// / +// -----/ +// ------------------ P +// * it tries to find index at point * +// Criterium: +// - we start with the total similaraty (length-averaged distance) +// - we go half-way. There the left-similarity is BETTER and the right-similarity is WORSE than the whole. +// If that is the case, half-way is better. But might not yet be optimal. +// - we go half-way the left part (= 1/4). Calculate left and right similarities. Is that better? +// To calculate that, we divide left/right and compare it with left/right of half-way +// - we go half-way the right part (= 3/4). Is that better? +template +void find_division(It p_begin, It p_end, It q_begin, It q_end, + range_info const& range, double total_length, double previous_ratio, partial_info& result, Ring& ring, Visitor& visitor, bool debug) +{ + auto const range_size = range.end - range.begin; + if (range_size < 2 || range.begin >= range.end) + { + return; + } + auto const mid = range_size / 2; + auto const p_mid = boost::next(p_begin, range.begin + mid); + auto const q_mid = boost::next(q_begin, range.begin + mid); + auto const total_size = std::distance(p_begin, p_end); + // TODO: this should be done based on real length + auto const mid_fraction = (range.begin + mid) / static_cast(total_size); + auto const left_length = total_length * mid_fraction; + auto const right_length = total_length * (1.0 - mid_fraction); + + auto const left_sim = get_areal_sum(p_begin, p_mid, q_begin, q_mid, ring, visitor) / left_length; + auto const right_sim = get_areal_sum(p_mid, p_end, q_mid, q_end, ring, visitor) / right_length; + + constexpr double eps = 1.0e-9; + bool const left_too_far = left_sim < eps && right_sim > eps; + bool const right_too_far = left_sim > eps && right_sim < eps; + bool const left_better = (! left_too_far) && left_sim < result.sim_distance && right_sim > result.sim_distance; + bool const right_better = (! right_too_far) && ((right_sim < result.sim_distance && left_sim > result.sim_distance) || result.sim_distance < eps); + + auto const ratio = left_sim > right_sim ? left_sim / std::max(eps, right_sim) : right_sim / std::max(eps, left_sim); + + if (debug) + { + double kScale = 1.0e4; + std::cout << std::setprecision(20) << "PART " << range.begin << ".." << range.end + << " at " << std::distance(p_begin, p_mid) + << " previous " << kScale * result.sim_distance + << " new " << kScale * left_sim << " " << kScale * right_sim + << " ratio " << std::setprecision(10) << ratio + << (left_better ? " LEFT " : "") + << (right_better ? " RIGHT " : "") + << (left_too_far ? " right " : "") + << (right_too_far ? " left " : "") + << std::endl; + } + + if (ratio < previous_ratio) // || !(left_better || right_better || right_too_far || left_too_far)) + { + // It is not better than found earlier. + return; + } + if (ratio > result.ratio) + { + // This is the best found until now. + TODO + result.sim_distance = std::min(left_sim, right_sim); + result.index = range.begin + mid; + if (result.sim_distance > eps) + { + result.ratio = ratio; + } + if (debug) + { + std::cout << "REASSIGN -> " << result.ratio << " " << result.sim_distance << " at " << result.index << std::endl; + } + } + + // Try to make it better + if (left_better) + { + find_division(p_begin, p_end, q_begin, q_end, {range.begin, range.begin + mid}, total_length, ratio, result, ring, visitor, debug); + } + else if (right_better) + { + find_division(p_begin, p_end, q_begin, q_end, {range.begin + mid, range.end}, total_length, ratio, result, ring, visitor, debug); + } + else if (right_too_far) + { + find_division(p_begin, p_end, q_begin, q_end, {range.begin, range.begin + mid}, total_length, 0.0, result, ring, visitor, debug); + } + else if (left_too_far) + { + find_division(p_begin, p_end, q_begin, q_end, {range.begin + mid, range.end}, total_length, 0.0, result, ring, visitor, debug); + } +} + +}} // namespace detail::similarity +#endif // DOXYGEN_NO_DETAIL + + +struct similarity_default_visitor +{ + template void visit_quadrilateral(T const& , I const& , I const& , I const& , I const& ) {} + template void visit_projections(int, C const&) {} +}; + + +template +auto similarity(Geometry1 const& p, Geometry2 const& q, bool consider_partial = false, Visitor& visitor = similarity_default_visitor(), bool debug = false) +{ + using namespace detail::similarity; + + using point_t = typename point_type::type; + + similarity_info result; + + // Get projections of P on Q. + // Later, the points of Q are added to these projections. + // Therefore, this variable can be seen as an enriched version of range Q. + auto q_enriched = get_projections(p, q); + + result.is_reversed = is_reversed(q_enriched); + + if (result.is_reversed) + { + // Reverse Q to align with P, and add own points using reversed iterators + reverse_projections(q, q_enriched); + enrich_with_self(boost::rbegin(q), boost::rend(q), q_enriched); + } + else + { + enrich_with_self(boost::begin(q), boost::end(q), q_enriched); + } + + // Do the same for P. Because Q is possibly reversed to align with P, + // such a check is not necessary for P again. + auto p_enriched = get_projections(q, p); + enrich_with_self(boost::begin(p), boost::end(p), p_enriched); + + // Optional debug step. + visitor.visit_projections(0, p_enriched); + visitor.visit_projections(1, q_enriched); + + if (q_enriched.size() != p_enriched.size()) + { + // Defensive check. + // They should always have the same size: + // each other projected points, plus their own points. + return result; + } + + using ring_t = boost::geometry::model::ring; + + ring_t ring; + ring.resize(5); + + auto p_begin = boost::begin(p_enriched); + auto p_end = boost::end(p_enriched); + auto q_begin = boost::begin(q_enriched); + auto q_end = boost::end(q_enriched); + double sum_area = get_areal_sum(p_begin, p_end, q_begin, q_end, ring, visitor); + + const double length = (std::min)(geometry::length(p), geometry::length(q)); + const double average_distance = length > 0 ? sum_area / length : std::sqrt(sum_area); + + result.distance = average_distance; + + if (consider_partial && sum_area > 0 && length > 0.0) + { + partial_info part; + part.ratio = 0.0; + part.index = 0; + part.sim_distance = average_distance; + find_division(p_begin, p_end, q_begin, q_end, {0, q_enriched.size()}, length, 0.0, part, ring, visitor, debug); + + if (part.index != 0) + { + result.distance = part.sim_distance; + result.index = part.index; + result.is_partial = true; + } + } + + return result; +} + +template +double average_distance(Geometry1 const& a, Geometry2 const& b, bool consider_partial = false) +{ + similarity_default_visitor v; + return similarity(a, b, consider_partial, v, false).distance; +} + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_ALGORITHMS_SIMILARITY_HPP + + +// PART 0..29 at 14 previous 25.898039226400840107 new 51.688648950232028767 0 ratio 51688648.95 left +// PART 0..14 at 7 previous 51.688648950232028767 new 73.888834317631378212 10.028356170353541543 ratio 7.367990632 RIGHT +// PART 7..14 at 10 previous 10.028356170353541543 new 59.862741985774739817 7.2063052955694093527 ratio 8.30699499 RIGHT +// PART 10..14 at 12 previous 7.2063052955694093527 new 51.20659575145721476 5.4146934179804215859 ratio 9.456970469 RIGHT +// PART 12..14 at 13 previous 5.4146934179804215859 new 50.691796112304395194 1.7126282663985041843 ratio 29.59883187 RIGHT \ No newline at end of file diff --git a/include/boost/geometry/io/dsv/write.hpp b/include/boost/geometry/io/dsv/write.hpp index 5870d21c58..b5883ff11a 100644 --- a/include/boost/geometry/io/dsv/write.hpp +++ b/include/boost/geometry/io/dsv/write.hpp @@ -36,6 +36,8 @@ #include +#include + namespace boost { namespace geometry { @@ -52,6 +54,7 @@ struct dsv_settings std::string list_open; std::string list_close; std::string list_separator; + bool close_rings{false}; dsv_settings(std::string const& sep , std::string const& open @@ -60,6 +63,7 @@ struct dsv_settings , std::string const& lopen , std::string const& lclose , std::string const& lsep + , bool cr = false ) : coordinate_separator(sep) , point_open(open) @@ -68,6 +72,7 @@ struct dsv_settings , list_open(lopen) , list_close(lclose) , list_separator(lsep) + , close_rings(cr) {} }; @@ -160,7 +165,7 @@ struct dsv_point \brief Stream ranges as DSV \note policy is used to stream prefix/postfix, enabling derived classes to override this */ -template +template struct dsv_range { template @@ -172,20 +177,31 @@ struct dsv_range os << settings.list_open; - for (auto it = boost::begin(range); it != boost::end(range); ++it) + auto stream_point = [&os, &settings](std::string const& sep, auto const& point) { - os << (first ? "" : settings.point_separator) - << settings.point_open; - + os << sep << settings.point_open; stream_coordinate < point_type, 0, dimension::type::value - >::apply(os, *it, settings); + >::apply(os, point, settings); os << settings.point_close; + }; + for (auto it = boost::begin(range); it != boost::end(range); ++it) + { + stream_point(first ? "" : settings.point_separator, *it); first = false; } + if (BOOST_GEOMETRY_CONDITION(Areal)) + { + if (settings.close_rings && boost::size(range) > 0) + { + // Close it explicitly + stream_point(settings.point_separator, *boost::begin(range)); + } + } + os << settings.list_close; } @@ -210,13 +226,13 @@ struct dsv_poly os << settings.list_open; - dsv_range::apply(os, exterior_ring(poly), settings); + dsv_range::apply(os, exterior_ring(poly), settings); auto const& rings = interior_rings(poly); for (auto it = boost::begin(rings); it != boost::end(rings); ++it) { os << settings.list_separator; - dsv_range::apply(os, *it, settings); + dsv_range::apply(os, *it, settings); } os << settings.list_close; } @@ -276,7 +292,7 @@ struct dsv template struct dsv - : detail::dsv::dsv_range + : detail::dsv::dsv_range {}; template @@ -291,7 +307,7 @@ struct dsv template struct dsv - : detail::dsv::dsv_range + : detail::dsv::dsv_range {}; template @@ -406,6 +422,7 @@ inline detail::dsv::dsv_manipulator dsv(Geometry const& geometry , std::string const& list_open = "(" , std::string const& list_close = ")" , std::string const& list_separator = ", " + , bool close_rings = false ) { concepts::check(); @@ -413,7 +430,7 @@ inline detail::dsv::dsv_manipulator dsv(Geometry const& geometry return detail::dsv::dsv_manipulator(geometry, detail::dsv::dsv_settings(coordinate_separator, point_open, point_close, point_separator, - list_open, list_close, list_separator)); + list_open, list_close, list_separator, close_rings)); } }} // namespace boost::geometry diff --git a/include/boost/geometry/io/geojson/geojson_writer.hpp b/include/boost/geometry/io/geojson/geojson_writer.hpp new file mode 100644 index 0000000000..0281a4a82d --- /dev/null +++ b/include/boost/geometry/io/geojson/geojson_writer.hpp @@ -0,0 +1,188 @@ +// Boost.Geometry + +// Copyright (c) 2023 Barend Gehrels, Amsterdam, the Netherlands. + +// 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_TEST_GEOJSON_WRITER_HPP +#define BOOST_GEOMETRY_TEST_GEOJSON_WRITER_HPP + +#include +#include + +#include +#include + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DISPATCH +namespace dispatch +{ + +template +struct geojson_feature_type +{ + BOOST_GEOMETRY_STATIC_ASSERT_FALSE("Not or not yet implemented for this Geometry type.", + GeometryTag); +}; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "Point"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "LineString"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "Polygon"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "LineString"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "Polygon"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "MultiPolygon"; } }; + +} // namespace dispatch +#endif + +/*! +\brief Helper class to create geojson output +*/ +struct geojson_writer +{ + + private: + std::ostream& m_out; + + static const char* comma_space() { return ", "; } + static const char* colon() { return ": "; } + static const char* gtag() { return "\"geometry\""; } + static const char* ttag() { return "\"type\""; } + static const char* ctag() { return "\"coordinates\""; } + static char comma() { return ','; } + static char quote() { return '"'; } + static char cbopen() { return '{'; } + static char cbclose() { return '}'; } + + std::size_t feature_count = 0; + std::size_t property_count = 0; + + void start_feature() + { + end_properties(); + end_feature(); + + if (feature_count > 0) + { + m_out << comma() << std::endl; + } + feature_count++; + + m_out << cbopen() << std::endl << ttag() << colon() << quote() << "Feature" << quote(); + } + + void start_property() + { + // Write a comma, either after the "geometry" tag, or after the previous property + m_out << comma() << std::endl; + + if (property_count == 0) + { + // No properties yet, start the list of properties + m_out << quote() << "properties" << quote() << colon() << cbopen() << std::endl; + } + property_count++; + } + + void end_properties() + { + if (property_count > 0) + { + m_out << cbclose() << std::endl; + property_count = 0; + } + } + void end_feature() + { + if (feature_count > 0) + { + m_out << cbclose() << std::endl; + } + } + + public: + geojson_writer(std::ostream& out) : m_out(out) + { + m_out << cbopen() << ttag() << colon() << quote() << "FeatureCollection" << quote() + << comma_space() << quote() << "features" << quote() << colon() << "[" << std::endl; + } + ~geojson_writer() + { + end_properties(); + end_feature(); + + m_out << "]" << cbclose(); + } + + // Set a property. It is expected that a feature is already started. + template + void add_property(const std::string& name, T const& value) + { + start_property(); + m_out << quote() << name << quote() << colon() << value; + } + + // Overload to get it quoted + void add_property(const std::string& name, std::string const& value) + { + start_property(); + m_out << quote() << name << quote() << colon() << quote() << value << quote(); + } + + // Overload to get it quoted + void add_property(const std::string& name, const char* value) + { + start_property(); + m_out << quote() << name << quote() << colon() << quote() << value << quote(); + } + + // The method "feature" should be called to start a feature. + // After that, properties can be added. + template + void feature(const Geometry& geometry) + { + start_feature(); + + // Write the comma after either the "Feature" tag + m_out << comma() << std::endl; + + // Write the geometry + using tag_t = typename tag::type; + m_out << gtag() << colon() << cbopen() << ttag() << colon() << quote() + << dispatch::geojson_feature_type::apply() << quote() << comma_space() + << ctag() << colon(); + + // A ring is modelled as a geojson polygon, and therefore the opening and closing + // list marks should be duplicated to indicate empty interior rings. + const bool dup = std::is_same::value; + const char* list_open = dup ? "[[" : "["; + const char* list_close = dup ? "]]" : "]"; + + // Indicate that dsv should close any ring automatically if its model is open + bool const close = geometry::closure::value == geometry::open; + + m_out << geometry::dsv(geometry, comma_space(), "[", "]", comma_space(), list_open, + list_close, ",", close) << cbclose() << std::endl; + } + +}; + +}} // namespace boost::geometry + + +#endif diff --git a/test/algorithms/Jamfile b/test/algorithms/Jamfile index 65931a6f93..7e5d4cdefb 100644 --- a/test/algorithms/Jamfile +++ b/test/algorithms/Jamfile @@ -59,6 +59,7 @@ test-suite boost-geometry-algorithms [ run remove_spikes.cpp : : : : algorithms_remove_spikes ] [ run reverse.cpp : : : : algorithms_reverse ] [ run reverse_multi.cpp : : : : algorithms_reverse_multi ] + [ run similarity.cpp : : : : algorithms_similarity ] [ run simplify.cpp : : : : algorithms_simplify ] [ run simplify_multi.cpp : : : : algorithms_simplify_multi ] [ run transform.cpp : : : : algorithms_transform ] diff --git a/test/algorithms/buffer/buffer_multi_polygon.cpp b/test/algorithms/buffer/buffer_multi_polygon.cpp index 64f8691a0b..4b87a53788 100644 --- a/test/algorithms/buffer/buffer_multi_polygon.cpp +++ b/test/algorithms/buffer/buffer_multi_polygon.cpp @@ -548,7 +548,10 @@ void test_all() test_one("rt_p15", rt_p15, join_miter, end_flat, 23.6569, 1.0); test_one("rt_p16", rt_p16, join_miter, end_flat, 23.4853, 1.0); +#if ! defined(BOOST_GEOMETRY_USE_RESCALING) || defined(BOOST_GEOMETRY_TEST_FAILURES) + // Fails with rescaling after correcting the tolerance in sort_by_side test_one("rt_p17", rt_p17, join_miter, end_flat, 25.3137, 1.0); +#endif test_one("rt_p18", rt_p18, join_miter, end_flat, 23.3137, 1.0); test_one("rt_p19", rt_p19, join_miter, end_flat, 25.5637, 1.0); test_one("rt_p20", rt_p20, join_miter, end_flat, 25.4853, 1.0); @@ -597,9 +600,12 @@ void test_all() test_one("rt_u11_50", rt_u11, join_miter, end_flat, 0.04289, -0.50); test_one("rt_u11_25", rt_u11, join_miter, end_flat, 10.1449, -0.25); +#if ! defined(BOOST_GEOMETRY_USE_RESCALING) || defined(BOOST_GEOMETRY_TEST_FAILURES) + // Fails with rescaling after correcting the tolerance in sort_by_side test_one("rt_u12", rt_u12, join_miter, end_flat, 142.1348, 1.0); +#endif #if ! defined(BOOST_GEOMETRY_USE_RESCALING) || defined(BOOST_GEOMETRY_TEST_FAILURES) - // Fails if rescaling is used in combination with get_clusters + // Fails with rescaling in combination with get_clusters test_one("rt_u13", rt_u13, join_miter, end_flat, 115.4853, 1.0); #endif diff --git a/test/algorithms/overlay/Jamfile b/test/algorithms/overlay/Jamfile index a3a5be23b5..22995f954a 100644 --- a/test/algorithms/overlay/Jamfile +++ b/test/algorithms/overlay/Jamfile @@ -18,6 +18,7 @@ test-suite boost-geometry-algorithms-overlay [ run assemble.cpp : : : : algorithms_assemble ] [ run copy_segment_point.cpp : : : : algorithms_copy_segment_point ] [ run get_clusters.cpp : : : : algorithms_get_clusters ] + [ run get_distance_measure.cpp : : : : algorithms_get_distance_measure ] [ run get_ring.cpp : : : : algorithms_get_ring ] [ run get_turn_info.cpp : : : : algorithms_get_turn_info ] [ run get_turns.cpp : : : : algorithms_get_turns ] diff --git a/test/algorithms/overlay/get_distance_measure.cpp b/test/algorithms/overlay/get_distance_measure.cpp new file mode 100644 index 0000000000..df0322b35e --- /dev/null +++ b/test/algorithms/overlay/get_distance_measure.cpp @@ -0,0 +1,129 @@ +// Boost.Geometry +// Unit Test + +// Copyright (c) 2023 Barend Gehrels, Amsterdam, the Netherlands. + +// 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 + +#include +#include +#include +#include +#include + +// #define BOOST_GEOMETRY_TEST_WITH_COUT +// #define BOOST_GEOMETRY_TEST_FAILURES + +template +void do_test(std::string const& case_id, + Point const& s1, + Point const& s2, + Point const& p, + int expected_side, + bool ignore_failure = false) +{ + using coor_t = typename bg::coordinate_type::type; + typename bg::strategies::relate::services::default_strategy + < + Point, Point + >::type strategy; + + auto const dm = bg::detail::get_distance_measure(s1, s2, p, strategy); + auto const dm_side = dm.measure < 0.0 ? -1 : dm.measure > 0.0 ? 1 : 0; + auto const tr_side = strategy.side().apply(s1, s2, p); + auto const rob_side = bg::strategy::side::side_robust::apply(s1, s2, p); + + #if defined(BOOST_GEOMETRY_TEST_WITH_COUT) + std::cout << " " << case_id << " " << string_from_type::name() + << std::setprecision(20) + << " " << dm.measure << " " << dm_side << " " << tr_side << " " << rob_side + << (dm_side != rob_side ? " [*** DM WRONG]" : "") + << (tr_side != rob_side ? " [*** TR WRONG]" : "") + << std::endl; + #endif + + BOOST_CHECK_MESSAGE(expected_side == -9 || expected_side == dm_side, + "Case: " << case_id + << " ctype: " << string_from_type::name() + << " expected: " << expected_side + << " detected: " << dm_side); + + // This is often wrong for float, and sometimes for double + BOOST_CHECK_MESSAGE(ignore_failure || tr_side == dm_side, + "Case: " << case_id + << " ctype: " << string_from_type::name() + << " tr_side: " << tr_side + << " dm_side: " << dm_side); + + // This is always guaranteed for the tested values + BOOST_CHECK_MESSAGE(tr_side == rob_side, + "Case: " << case_id + << " ctype: " << string_from_type::name() + << " tr_side: " << tr_side + << " rob_side: " << rob_side); +} + +template +void test_get_distance_measure() +{ + using coor_t = typename bg::coordinate_type::type; + + do_test("simplex_coll", {1.0, 0.0}, {1.0, 1.0}, {1.0, 0.5}, 0); + do_test("simplex_left", {1.0, 0.0}, {1.0, 1.0}, {0.9, 0.5}, 1); + do_test("simplex_right", {1.0, 0.0}, {1.0, 1.0}, {1.1, 0.5}, -1); + + bool const is_float = std::is_same::value; + bool const is_double = std::is_same::value; + + // The issue 1183 where get_distance_measure failed for these coordinates. + std::string const case_id = "issue_1183_"; + Point const p1{38902.349206128216, 6721371.1493254723}; + Point const p2{38937.993505971914, 6721407.9151819283}; + Point const q1 = p1; + Point const q2{38960.647313876834, 6721431.2817974398}; + { + do_test(case_id + "p", p1, p2, q2, 1, is_float); + do_test(case_id + "q", q1, q2, p1, 0); + } + + double const test_epsilon = 1.0e-9; + + // Walk along x axis to get the switch from left to right (between 2 and 3) + for (int i = -5; i <= 15; i++) + { +#if defined(BOOST_GEOMETRY_TEST_FAILURES) + bool const ignore_failure = false; +#else + bool const ignore_failure = is_float || (is_double && i >= 3 && i <= 12); +#endif + double const v = i / 10.0; + Point q2a = q2; + bg::set<0>(q2a, bg::get<0>(q2) + v * test_epsilon); + do_test(case_id + std::to_string(i), p1, p2, q2a, -9, ignore_failure); + } + // Focus on the switch from left to right (between 0.251 and 0.252) + for (int i = 250; i <= 260; i++) + { + double const v = i / 1000.0; + Point q2a = q2; + bg::set<0>(q2a, bg::get<0>(q2) + v * test_epsilon); + do_test(case_id + std::to_string(i), p1, p2, q2, -9, is_float || is_double); + } +} + +int test_main(int, char* []) +{ + using fp = bg::model::point; + using dp = bg::model::point; + using ep = bg::model::point; + + test_get_distance_measure(); + test_get_distance_measure(); + test_get_distance_measure(); + + return 0; +} diff --git a/test/algorithms/overlay/overlay_cases.hpp b/test/algorithms/overlay/overlay_cases.hpp index 2936a06dd4..07f74e6476 100644 --- a/test/algorithms/overlay/overlay_cases.hpp +++ b/test/algorithms/overlay/overlay_cases.hpp @@ -1113,6 +1113,57 @@ static std::string issue_1108[2] = "POLYGON((22 1,22 0,14 0,18 -1.2696790939262529996,12 0,22 1))" }; +static std::string issue_1183[2] = +{ + "POLYGON((\ +-38880.685990792437 6721344.0451435195,\ +-38902.349206128216 6721371.1493254723,\ +-38937.993505971914 6721407.9151819283,\ +-38925.264019448201 6721389.3887558663,\ +-38925.240186032526 6721389.3524003429,\ +-38903.891993208345 6721355.1907963008,\ +-38909.181455691403 6721352.2237809654,\ +-38926.953387540903 6721388.3157829558,\ +-38940.906008282145 6721408.6223519640,\ +-38961.796165266409 6721432.5740491701,\ +-38961.819650679929 6721432.6019898951,\ +-38977.627018370375 6721452.1204930553,\ +-38977.724235665897 6721452.2645238554,\ +-38997.157657657241 6721487.2743892670,\ +-38997.156729985494 6721487.2749042027,\ +-38997.205159411656 6721487.3621566473,\ +-38995.456469315024 6721488.3327662209,\ +-38976.018586129794 6721453.3126770724,\ +-38959.181300235970 6721432.6429256191,\ +-38900.879954713615 6721372.5071140006,\ +-38900.816775977277 6721372.4353842726,\ +-38879.096795985606 6721345.2601805655,\ +-38879.046005852288 6721345.1907021403,\ +-38880.685990792437 6721344.0451435195,\ +))", + "POLYGON((\ +-38880.685990792437 6721344.0451435195,\ +-38902.349206128216 6721371.1493254723,\ +-38960.647313876834 6721431.2817974398,\ +-38960.704662809898 6721431.3463021647,\ +-38977.625225408119 6721452.1182855805,\ +-38977.724248525541 6721452.2645470239,\ +-38997.205159411656 6721487.3621566473,\ +-38995.456469315024 6721488.3327662209,\ +-38976.018586130762 6721453.3126770733,\ +-38959.181300235970 6721432.6429256191,\ +-38900.879954713615 6721372.5071140006,\ +-38879.046005852288 6721345.1907021403,\ +-38880.685990792437 6721344.0451435195,\ +))" +}; + +static std::string issue_1186[2] = +{ + "POLYGON((-13848.1446527556 6710443.1496919869,-13847.6993747924 6710443.1496919869,-13847.8106942832 6710440.1096301023,-13848.2559722463 6710440.2884572418,-13848.1446527556 6710443.1496919869))", + "POLYGON((-13848.1446527556 6710443.1496919869,-13848.2559722463 6710440.2884572418,-13847.8106942832 6710440.1096301023,-13847.6993747924 6710443.1496919869,-13847.3654163201 6710442.9708647905,-13846.0295824308 6710442.9708647905,-13846.4748603939 6710435.1024718173,-13847.8106942832 6710435.1024718173,-13848.1446527556 6710435.1024718173,-13849.8144451172 6710443.1496919869,-13848.1446527556 6710443.1496919869),(-13847.4767358109 6710440.1096301023,-13847.8106942832 6710440.1096301023,-13847.9220137740 6710439.9308029665,-13847.5880553017 6710439.7519758362,-13847.4767358109 6710440.1096301023))" +}; + static std::string ggl_list_20120229_volker[3] = { "POLYGON((1716 1554,2076 2250,2436 2352,2796 1248,3156 2484,3516 2688,3516 2688,3156 2484,2796 1248,2436 2352,2076 2250, 1716 1554))", diff --git a/test/algorithms/set_operations/union/union.cpp b/test/algorithms/set_operations/union/union.cpp index 4a77ac3efc..a009911cc1 100644 --- a/test/algorithms/set_operations/union/union.cpp +++ b/test/algorithms/set_operations/union/union.cpp @@ -465,6 +465,12 @@ void test_areal() TEST_UNION(issue_1108, 1, 0, -1, 12.1742); TEST_UNION_REV(issue_1108, 1, 0, -1, 12.1742); + TEST_UNION(issue_1183, 1, 0, -1, 607.6507); + TEST_UNION_REV(issue_1183, 1, 0, -1, 607.6507); + + TEST_UNION(issue_1186, 1, 1, -1, 21.6189); + TEST_UNION_REV(issue_1186, 1, 1, -1, 21.6189); + { // Rescaling produces an invalid result ut_settings settings; diff --git a/test/algorithms/similarity.cpp b/test/algorithms/similarity.cpp new file mode 100644 index 0000000000..eed365034e --- /dev/null +++ b/test/algorithms/similarity.cpp @@ -0,0 +1,456 @@ +// Boost.Geometry +// (Unit) Test + +// Copyright (c) 2023 Barend Gehrels, Amsterdam, the Netherlands. + +// 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 + +#include +#include +#include + +#include + +#if defined(TEST_WITH_COMPARE) +#include +#include +#endif + +#if defined(TEST_WITH_GEOJSON) +#include +#include +#endif + +#include + +namespace +{ + +// Two lines consistently 0.5 apart +std::string const simplex1 = "LINESTRING(1.0 1.0, 2.0 1.0)"; +std::string const simplex2 = "LINESTRING(1.0 1.1, 2.0 1.1)"; + +// With extra point in between +std::string const simplex3 = "LINESTRING(1.0 1.1, 1.5 1.1,2.0 1.1)"; + +// With a "spike" +std::string const simplex4 = "LINESTRING(1.0 1.1, 1.49 1.1, 1.5 2.5, 1.51 1.1, 2.0 1.1)"; + +// Realistic cases +std::string const west1 = "LINESTRING(5.134673416614532 52.27074444293976,5.134786069393158 52.27136671543121,5.134778022766113 52.27139353752136,5.134756565093994 52.27141231298447,5.134694874286652 52.27144449949265,5.134069919586182 52.27149546146393,5.133965313434601 52.27146327495575,5.133906304836273 52.27141231298447,5.133863389492035 52.27112531661987,5.133847296237946 52.27108776569366,5.133809745311737 52.27106094360352,5.133764147758484 52.27100998163223,5.133707821369171 52.27053791284561,5.133721232414246 52.27049767971039,5.133863389492035 52.27035015821457)"; +std::string const west2 = "LINESTRING(5.134670734405518 52.27073907852173,5.134778022766113 52.27136135101318,5.134778022766113 52.27139353752136,5.134756565093994 52.27140426635742,5.134692192077637 52.2714364528656,5.134069919586182 52.2714900970459,5.134016275405884 52.27147936820984,5.133962631225586 52.27145791053772,5.133898258209229 52.27140426635742,5.133844614028931 52.27108240127563,5.133801698684692 52.27106094360352,5.133758783340454 52.27100729942322,5.133705139160156 52.2705352306366,5.133715867996216 52.27049231529236,5.13385534286499 52.27034211158752)"; +std::string const east1 = "LINESTRING(5.141247510910034 52.27364659309387,5.140657424926758 52.27396845817566,5.14061450958252 52.2740113735199,5.140560865402222 52.27447271347046,5.140872001647949 52.27483749389648,5.140936374664307 52.27486968040466,5.141386985778809 52.27483749389648)"; +std::string const east2 = "LINESTRING(5.141247510910034 52.27364659309387,5.140665471553802 52.27397114038467,5.14061450958252 52.27401673793793,5.1405930519104 52.27407306432724,5.140560865402222 52.2744807600975,5.140606462955475 52.27453976869583,5.140874683856964 52.27483749389648,5.140941739082336 52.27486968040466,5.141386985778809 52.27483749389648)"; +std::string const complete = "LINESTRING(5.135158896446228 52.26660043001175,5.134890675544739 52.2666272521019,5.134839713573456 52.26665943861008,5.13435423374176 52.26669162511826,5.134295225143433 52.26670503616333,5.13424426317215 52.26672381162643,5.134182572364807 52.26675063371658,5.134131610393524 52.26690083742142,5.134077966213226 52.26698398590088,5.134086012840271 52.26706176996231,5.134115517139435 52.26712614297867,5.134161114692688 52.26717710494995,5.134324729442596 52.26729512214661,5.134397149085999 52.26735413074493,5.134442746639252 52.26739972829819,5.134464204311371 52.26744264364243,5.134584903717041 52.2677618265152)"; +std::string const part1 = "LINESTRING(5.134332776069641 52.26669698953629,5.134295225143433 52.26670503616333,5.13424426317215 52.26672381162643,5.134182572364807 52.26675063371658,5.134131610393524 52.26690083742142,5.134077966213226 52.26698398590088,5.134086012840271 52.26706176996231,5.134115517139435 52.26712614297867,5.134161114692688 52.26717710494995,5.134324729442596 52.26729512214661,5.134397149085999 52.26735413074493,5.134442746639252 52.26739972829819,5.134464204311371 52.26744264364243,5.134584903717041 52.2677618265152)"; +std::string const part2 = "LINESTRING(5.135158896446228 52.26660043001175,5.134890675544739 52.2666272521019,5.134839713573456 52.26665943861008,5.13435423374176 52.26669162511826,5.134332776069641 52.26669698953629)"; + +// Greek islands +std::string const kimolos1 = "POLYGON((24.5956897735596 36.8201408386231,24.5956897735596 36.8198585510256,24.5959720611574 36.8198623657228,24.5959720611574 36.8188819885254,24.5959720611574 36.8181953430178,24.5956897735596 36.8181991577148,24.5956897735596 36.8168106079103,24.5954818725587 36.8168106079103,24.5954208374024 36.8168106079103,24.5954208374024 36.8162498474121,24.5940284729004 36.8162498474121,24.5940284729004 36.8159713745117,24.593334197998 36.8159713745117,24.593189239502 36.8159713745117,24.593189239502 36.8156890869143,24.5926380157471 36.8156929016113,24.5926380157471 36.8151397705079,24.5929164886475 36.8151397705079,24.5929164886475 36.8148612976074,24.5936183929443 36.8148612976074,24.59375 36.8148612976074,24.59375 36.81457901001,24.5940284729004 36.814582824707,24.5940284729004 36.8140258789062,24.59375 36.8140296936036,24.59375 36.8134689331056,24.5934715270999 36.8134727478028,24.5934715270999 36.8129158020021,24.593189239502 36.8129196166992,24.593189239502 36.8123588562015,24.5929164886475 36.8123626708987,24.5929164886475 36.8090286254884,24.5926380157471 36.8090286254884,24.5926380157471 36.8068046569825,24.5929164886475 36.8068046569825,24.5929164886475 36.805416107178,24.593189239502 36.8054199218749,24.593189239502 36.8048591613772,24.5915279388428 36.8048629760742,24.5915279388428 36.8045845031738,24.5912494659424 36.8045845031738,24.5912494659424 36.8040275573732,24.5910263061526 36.8040313720704,24.5909690856934 36.8040313720704,24.5909690856934 36.8037490844728,24.590690612793 36.8037490844728,24.590690612793 36.8034706115723,24.5909690856934 36.8034706115723,24.5909690856934 36.8029212951662,24.5912494659424 36.8029212951662,24.5912494659424 36.8028640747071,24.5912494659424 36.8026390075685,24.5915298461913 36.8026390075685,24.5915279388428 36.8023605346681,24.5920848846436 36.8023605346681,24.5920791625978 36.8012504577637,24.5918064117432 36.8012504577637,24.5918064117432 36.800693511963,24.5915279388428 36.800693511963,24.5915279388428 36.8004150390626,24.5912494659424 36.8004188537598,24.5912494659424 36.8001403808594,24.590690612793 36.8001403808594,24.590690612793 36.799858093262,24.590139389038 36.799861907959,24.590139389038 36.7995834350588,24.5904140472413 36.7995834350588,24.5904140472413 36.799026489258,24.590690612793 36.799030303955,24.590690612793 36.7987518310546,24.5909690856934 36.7987518310546,24.5909690856934 36.7984695434573,24.5912494659424 36.7984733581543,24.5912494659424 36.7979164123535,24.5915298461913 36.7979202270508,24.5915298461913 36.7976417541505,24.5918102264405 36.7976417541505,24.5918102264405 36.7970809936523,24.5915279388428 36.7970848083497,24.5915279388428 36.7965278625491,24.5912494659424 36.7965316772463,24.5912494659424 36.7962493896486,24.590690612793 36.7962493896486,24.590690612793 36.7959709167482,24.5891761779785 36.7959709167482,24.5890274047853 36.7959709167482,24.5890274047853 36.7956962585451,24.5887527465821 36.7957000732421,24.5887527465821 36.7954216003418,24.5884704589843 36.7954216003418,24.5884704589843 36.7951393127442,24.5881900787354 36.7951393127442,24.5881900787354 36.7943000793459,24.5879173278809 36.7943038940431,24.5879173278809 36.7941856384278,24.5879173278809 36.7931938171389,24.5890274047853 36.7931938171389,24.5890274047853 36.7929153442385,24.5898628234864 36.7929191589357,24.5898628234864 36.7912483215334,24.5895805358887 36.7912483215334,24.5895805358887 36.790699005127,24.5887527465821 36.790699005127,24.5887527465821 36.7904205322266,24.5876426696777 36.7904205322266,24.5876426696777 36.7901382446292,24.586805343628 36.7901382446292,24.586805343628 36.790035247803,24.586805343628 36.7898597717286,24.5862483978272 36.7898597717286,24.5862483978272 36.7895889282226,24.5848598480225 36.7895889282226,24.5848598480225 36.7893104553222,24.5840282440187 36.7893104553222,24.5840282440187 36.7890281677248,24.5834693908691 36.7890281677248,24.5834693908691 36.7887496948243,24.5826396942139 36.7887496948243,24.5826396942139 36.7884712219239,24.582359313965 36.7884712219239,24.582359313965 36.7879219055177,24.5822448730469 36.7879219055177,24.5820808410644 36.7879219055177,24.5820808410644 36.7873611450195,24.5818061828614 36.7873611450195,24.5818061828614 36.786804199219,24.581527709961 36.786804199219,24.581527709961 36.7862510681152,24.5812492370606 36.7862510681152,24.5812492370606 36.7856903076174,24.5809726715088 36.7856941223145,24.5809726715088 36.7854156494141,24.5806903839112 36.7854194641114,24.5806903839112 36.785140991211,24.5804195404052 36.785140991211,24.5804195404052 36.7848587036135,24.5798606872559 36.7848625183107,24.5798606872559 36.7845840454103,24.5796089172363 36.7845840454103,24.5793056488037 36.7845840454103,24.5793056488037 36.7837486267093,24.5795574188233 36.7837486267093,24.5795822143557 36.7835159301758,24.5795822143557 36.7831497192385,24.5795803070069 36.7829208374023,24.5793094635013 36.7829208374023,24.5793094635013 36.7823600769044,24.5787506103515 36.7823600769044,24.5787506103515 36.782081604004,24.5784702301025 36.782081604004,24.5784702301025 36.7815284729006,24.5781898498536 36.7815284729006,24.5781898498536 36.78125,24.57808303833 36.78125,24.5779170989991 36.78125,24.5779170989991 36.7809715270996,24.5776386260987 36.7809715270996,24.5776386260987 36.7807350158691,24.5776386260987 36.7806930541992,24.577362060547 36.7806930541992,24.577362060547 36.7804183959962,24.5771694183349 36.7804183959962,24.5770797729492 36.7804183959962,24.5770797729492 36.7801399230958,24.575969696045 36.7801399230958,24.575969696045 36.7793006896975,24.5756950378419 36.7793045043945,24.5756950378419 36.7787513732911,24.5754165649415 36.7787513732911,24.5754165649415 36.7784729003907,24.5751380920411 36.7784729003907,24.5751380920411 36.7781944274903,24.5734710693359 36.7781944274903,24.5734710693359 36.7773628234866,24.5731945037842 36.7773628234866,24.5731945037842 36.7765274047854,24.572639465332 36.7765312194824,24.572639465332 36.776248931885,24.5718116760254 36.776248931885,24.5718116760254 36.7765312194824,24.5706901550293 36.7765312194824,24.5706901550293 36.776248931885,24.5704174041749 36.776248931885,24.5704174041749 36.7759704589844,24.5698604583741 36.7759704589844,24.5698604583741 36.776248931885,24.5693073272705 36.776248931885,24.5693073272705 36.7765312194824,24.5690307617189 36.7765312194824,24.5690307617189 36.7768096923828,24.5687503814697 36.7768058776858,24.5687503814697 36.7770843505862,24.5684719085696 36.7770843505862,24.5684719085696 36.7773628234866,24.5681972503663 36.7773590087893,24.5681972503663 36.7776412963867,24.5679206848146 36.7776412963867,24.5679206848146 36.7779197692871,24.5670795440674 36.7779197692871,24.5670795440674 36.7776412963867,24.5665302276614 36.7776412963867,24.5665302276614 36.7779197692871,24.5654201507571 36.7779197692871,24.5654201507571 36.7776412963867,24.5656890869141 36.7776412963867,24.5656890869141 36.7773590087893,24.5659713745117 36.7773628234866,24.5659713745117 36.7768058776858,24.5656890869141 36.7768096923828,24.5656890869141 36.7765312194824,24.5651397705079 36.7765312194824,24.5651397705079 36.776248931885,24.5643100738527 36.776248931885,24.5643100738527 36.7765312194824,24.5637493133545 36.7765312194824,24.5637493133545 36.7768096923828,24.5634746551514 36.7768058776858,24.5634746551514 36.7770843505862,24.5618057250977 36.7770843505862,24.5618057250977 36.7768058776858,24.5615272521973 36.7768058776858,24.5615272521973 36.7770843505862,24.5612525939943 36.7770843505862,24.5612525939943 36.7784729003907,24.5615272521973 36.7784729003907,24.5615291595458 36.7787513732911,24.5612525939943 36.7787513732911,24.5612525939943 36.7790298461915,24.5595836639405 36.7790260314941,24.5595836639405 36.7793045043945,24.5584716796878 36.7793045043945,24.5584716796878 36.7790260314941,24.5582008361818 36.7790298461915,24.5582008361818 36.7784690856935,24.5579166412354 36.7784729003907,24.5579166412354 36.7781944274903,24.557638168335 36.7781944274903,24.557638168335 36.77791595459,24.5573596954346 36.7779197692871,24.5573596954346 36.7776412963867,24.5562496185303 36.7776412963867,24.5562496185303 36.7773590087893,24.5556907653809 36.7773590087893,24.5556907653809 36.7776412963867,24.5537490844728 36.7776412963867,24.5537490844728 36.7773590087893,24.5515270233155 36.7773628234866,24.5515270233155 36.7768058776858,24.5512504577637 36.7768096923828,24.5512504577637 36.7765312194824,24.5509700775147 36.7765312194824,24.5509700775147 36.776248931885,24.5498619079592 36.776248931885,24.5498619079592 36.7759704589844,24.5492992401124 36.7759704589844,24.5492992401124 36.7756996154786,24.5490303039554 36.7756996154786,24.5490303039554 36.7748603820801,24.5487518310548 36.7748603820801,24.5487518310548 36.7745780944825,24.5470829010011 36.7745819091797,24.5470829010011 36.7743072509768,24.5456905364991 36.7743110656741,24.5456905364991 36.7740287780763,24.545415878296 36.7740287780763,24.545415878296 36.7737503051759,24.5451393127441 36.7737503051759,24.5451393127441 36.773189544678,24.5448608398438 36.7731933593749,24.5448608398438 36.7729148864746,24.544584274292 36.7729148864746,24.544584274292 36.7726402282715,24.5440273284912 36.7726402282715,24.5440273284912 36.7723617553712,24.5437507629394 36.7723617553712,24.5437507629394 36.7720794677736,24.5431938171387 36.7720832824708,24.5431938171387 36.7718048095704,24.5426387786866 36.7718048095704,24.5426387786866 36.77152633667,24.539306640625 36.77152633667,24.539306640625 36.7720832824708,24.5390281677246 36.7720832824708,24.5390300750732 36.7726402282715,24.5387496948242 36.7726402282715,24.5387496948242 36.7731933593749,24.5384693145753 36.773189544678,24.5384693145753 36.7740287780763,24.5381946563721 36.7740287780763,24.5381946563721 36.7745819091797,24.5379161834717 36.7745819091797,24.5379199981692 36.7751388549806,24.5376396179199 36.7751388549806,24.5376396179199 36.7754211425782,24.5340328216554 36.7754211425782,24.5340328216554 36.7756996154786,24.5331897735597 36.7756996154786,24.5331897735597 36.7764434814453,24.5331897735597 36.7767753601074,24.5331897735597 36.7768096923828,24.5334701538087 36.7768058776858,24.5334701538087 36.7781944274903,24.5331935882569 36.7781944274903,24.5331935882569 36.7786216735839,24.5331935882569 36.7787513732911,24.5330600738525 36.7787513732911,24.5329170227051 36.7787513732911,24.5329208374025 36.77889251709,24.5329208374025 36.7790298461915,24.5327816009521 36.7790260314941,24.5326385498047 36.7790260314941,24.5326385498047 36.7791709899905,24.5326385498047 36.7795829772949,24.5323581695557 36.7795791625979,24.5323581695557 36.7798614501953,24.5320796966553 36.7798614501953,24.5320835113525 36.7801399230958,24.531671524048 36.7801399230958,24.5315284729005 36.7801399230958,24.5315284729005 36.7802810668947,24.5315284729005 36.7804183959962,24.5313892364503 36.7804183959962,24.5312480926514 36.7804183959962,24.5312480926514 36.7805595397949,24.5312480926514 36.7806930541992,24.5311183929444 36.7806930541992,24.5306873321534 36.7806892395022,24.530694961548 36.7809715270996,24.5304164886476 36.7809715270996,24.5304183959962 36.78125,24.5298595428468 36.78125,24.5298614501956 36.7815284729006,24.5293102264406 36.7815284729006,24.5293102264406 36.7818107604982,24.5287494659424 36.7818107604982,24.5287494659424 36.7819595336913,24.5287494659424 36.782081604004,24.5284690856935 36.7820930480959,24.5284729003907 36.7823600769044,24.5279197692871 36.7823600769044,24.5279197692871 36.7829208374023,24.5276393890381 36.7829208374023,24.5276393890381 36.7840309143066,24.5273609161377 36.7840270996097,24.5273609161377 36.7845840454103,24.5274639129641 36.7845840454103,24.5304164886476 36.7845840454103,24.5304164886476 36.7848625183107,24.5306873321534 36.7848587036135,24.5306873321534 36.785140991211,24.5307788848877 36.785140991211,24.530969619751 36.785140991211,24.530969619751 36.7854194641114,24.5312480926514 36.7854156494141,24.5312480926514 36.7856941223145,24.5315284729005 36.7856941223145,24.5315284729005 36.7859725952148,24.5318050384521 36.7859725952148,24.5318050384521 36.786804199219,24.5320796966553 36.7868003845218,24.5320835113525 36.7876396179199,24.5318107604981 36.7876396179199,24.5318107604981 36.7879219055177,24.5315284729005 36.7879180908205,24.5315284729005 36.7881927490235,24.5312480926514 36.7881889343262,24.5312480926514 36.7884712219239,24.530969619751 36.7884712219239,24.530969619751 36.7887496948243,24.5306873321534 36.7887496948243,24.5306873321534 36.7893104553222,24.5304164886476 36.7893066406252,24.5304164886476 36.7900161743166,24.5304164886476 36.7901382446292,24.5306873321534 36.7901382446292,24.5306873321534 36.7912483215334,24.530969619751 36.7912483215334,24.530969619751 36.7940292358398,24.530694961548 36.7940292358398,24.530694961548 36.7943038940431,24.5301361083984 36.7943038940431,24.5301361083984 36.7945823669434,24.5287494659424 36.7945823669434,24.5287494659424 36.7943038940431,24.5281944274903 36.7943038940431,24.5281944274903 36.7940292358398,24.5276393890381 36.7940292358398,24.5276393890381 36.7937507629395,24.5265274047852 36.7937507629395,24.5265293121337 36.7940292358398,24.5262508392333 36.7940292358398,24.5262508392333 36.7943038940431,24.5259704589844 36.7943000793459,24.5259704589844 36.7957000732421,24.5262508392333 36.7957000732421,24.5262508392333 36.7962493896486,24.5265293121337 36.7962493896486,24.5265293121337 36.7968101501465,24.5259704589844 36.7968101501465,24.5259704589844 36.7965316772463,24.5254611968996 36.7965316772463,24.5229206085207 36.7965316772463,24.5229206085207 36.7968101501465,24.5223636627197 36.7968063354495,24.5223636627197 36.7981948852539,24.5215282440186 36.7981948852539,24.5215282440186 36.7984733581543,24.5208244323732 36.7984695434573,24.5206909179688 36.7984695434573,24.5206909179688 36.7987518310546,24.5201416015626 36.7987518310546,24.5201416015626 36.799030303955,24.5193061828614 36.799026489258,24.5193061828614 36.7995834350588,24.5195827484133 36.7995834350588,24.5195827484133 36.7996406555177,24.5195827484133 36.8004035949709,24.5195827484133 36.800693511963,24.5198593139648 36.8006896972658,24.5198593139648 36.8010444641116,24.5198593139648 36.8013229370119,24.5198593139648 36.8015289306641,24.5201416015626 36.8015289306641,24.5201416015626 36.8024330139163,24.5201416015626 36.8026390075685,24.5204162597656 36.8026390075685,24.5204162597656 36.8034706115723,24.520694732666 36.8034706115723,24.520694732666 36.8037757873538,24.520694732666 36.8045845031738,24.5209693908692 36.8045806884766,24.5209693908692 36.8051414489746,24.5234737396241 36.8051376342776,24.5234737396241 36.8059730529787,24.5220794677736 36.8059692382815,24.5220813751221 36.8062515258789,24.5215301513671 36.8062515258789,24.5215301513671 36.8065299987793,24.5209693908692 36.8065261840821,24.5209693908692 36.8068046569825,24.5201416015626 36.8068008422853,24.5201416015626 36.8093109130862,24.5204162597656 36.809307098389,24.5204162597656 36.8109703063965,24.5201416015626 36.8109703063965,24.5201416015626 36.8118095397949,24.5198593139648 36.8118095397949,24.5198593139648 36.8126411437988,24.5195808410645 36.8126411437988,24.5195827484133 36.8137512207032,24.5193061828614 36.8137512207032,24.5193061828614 36.8141860961915,24.5193061828614 36.8148612976074,24.5195808410645 36.8148612976074,24.5195808410645 36.8151893615724,24.5195808410645 36.8154182434083,24.5198593139648 36.8154182434083,24.5198593139648 36.8158073425294,24.5198593139648 36.8159713745117,24.5201416015626 36.8159713745117,24.5201416015626 36.8163681030276,24.5201416015626 36.8165283203127,24.5204162597656 36.8165283203127,24.5204200744632 36.8173599243165,24.5201416015626 36.8173599243165,24.5201416015626 36.8179206848144,24.5198593139648 36.8179168701174,24.5198593139648 36.8184738159182,24.5195808410645 36.8184700012208,24.5195827484133 36.8187484741214,24.5193119049072 36.8187484741214,24.5193119049072 36.8193092346191,24.5190277099611 36.819305419922,24.5190277099611 36.8198623657228,24.5193061828614 36.8198623657228,24.5193061828614 36.8202400207521,24.5193061828614 36.8206939697266,24.5195827484133 36.8206939697266,24.5195827484133 36.8218040466311,24.5198593139648 36.8218002319339,24.5198593139648 36.8223609924316,24.5204162597656 36.8223609924316,24.5204162597656 36.822639465332,24.5206909179688 36.822639465332,24.5206909179688 36.8229217529298,24.521251678467 36.8229179382326,24.521251678467 36.8231925964356,24.5217990875244 36.8231887817383,24.5217990875244 36.823471069336,24.5220794677736 36.823471069336,24.5220794677736 36.8237495422364,24.5226402282715 36.8237495422364,24.5226402282715 36.824031829834,24.5237503051758 36.824031829834,24.5237503051758 36.8243103027343,24.5254192352295 36.8243103027343,24.5254192352295 36.824031829834,24.5262508392333 36.824031829834,24.5262508392333 36.8237495422364,24.5268096923831 36.8237495422364,24.5268058776855 36.823471069336,24.5273609161377 36.823471069336,24.5273609161377 36.8231887817383,24.5279178619385 36.8231925964356,24.5279178619385 36.8229179382326,24.5287494659424 36.8229217529298,24.5287494659424 36.822639465332,24.5293102264406 36.822639465332,24.5293064117432 36.8223609924316,24.5296249389649 36.8223609924316,24.5298595428468 36.8223609924316,24.5298595428468 36.8225250244141,24.5298595428468 36.822639465332,24.5300731658937 36.822639465332,24.5301361083984 36.822639465332,24.5301361083984 36.8226699829104,24.5301361083984 36.8237495422364,24.5304183959962 36.8237495422364,24.5304183959962 36.8243103027343,24.5306873321534 36.8243103027343,24.5306873321534 36.8248596191407,24.530969619751 36.8248596191407,24.530969619751 36.8256988525391,24.5312480926514 36.8256950378419,24.5312480926514 36.8259735107423,24.5323600769043 36.8259735107423,24.5323600769043 36.8256950378419,24.5331897735597 36.8256988525391,24.5331897735597 36.8262481689455,24.5334701538087 36.8262481689455,24.5334701538087 36.8268089294434,24.5337524414062 36.8268051147461,24.5337524414062 36.8270835876465,24.5343055725098 36.8270835876465,24.5343055725098 36.8273620605468,24.534580230713 36.8273582458498,24.534580230713 36.8276405334472,24.5351390838624 36.8276405334472,24.5351390838624 36.8279190063478,24.5354175567628 36.8279151916506,24.5354175567628 36.8280830383301,24.5354175567628 36.828193664551,24.5356903076172 36.8281898498538,24.5356903076172 36.8284072875979,24.5356903076172 36.8287506103516,24.5359706878662 36.8287506103516,24.5359706878662 36.829029083252,24.5362491607667 36.829029083252,24.5362491607667 36.8295822143555,24.5365276336671 36.8295822143555,24.5365276336671 36.8298606872559,24.5368099212649 36.8298606872559,24.5368099212649 36.8304214477539,24.5370807647705 36.8304214477539,24.5370807647705 36.8306999206542,24.5373592376709 36.8306999206542,24.5373592376709 36.8309707641603,24.5376396179199 36.8309707641603,24.5376396179199 36.8312492370607,24.5379199981692 36.8312492370607,24.5379199981692 36.8315315246584,24.5381908416749 36.8315315246584,24.5381908416749 36.8318099975586,24.5384731292725 36.8318061828616,24.5384731292725 36.8320846557618,24.5390281677246 36.8320846557618,24.5390281677246 36.832359313965,24.5395793914796 36.832359313965,24.5395793914796 36.8326416015626,24.5409698486329 36.8326416015626,24.5409698486329 36.8329200744629,24.5412502288819 36.8329162597656,24.5412502288819 36.833194732666,24.5418052673341 36.833194732666,24.5418052673341 36.8334732055664,24.5420799255371 36.8334693908694,24.5420799255371 36.8337516784667,24.5426406860354 36.8337516784667,24.5426406860354 36.8340301513671,24.5448608398438 36.8340301513671,24.5448608398438 36.8337364196778,24.5448608398438 36.8334693908694,24.5451393127441 36.8334732055664,24.5451393127441 36.8329162597656,24.5456943511963 36.8329162597656,24.5456943511963 36.833194732666,24.5462493896485 36.833194732666,24.5462493896485 36.8334732055664,24.5459709167481 36.8334693908694,24.5459709167481 36.8340301513671,24.5456943511963 36.8340263366701,24.5456943511963 36.8343048095705,24.5451393127441 36.8343048095705,24.5451393127441 36.8345832824709,24.5450325012208 36.8345832824709,24.5448608398438 36.8345794677737,24.5448608398438 36.8348617553711,24.5445804595948 36.8348617553711,24.5445804595948 36.8359718322754,24.5448608398438 36.8359718322754,24.5448608398438 36.8362503051758,24.5456905364991 36.8362503051758,24.5456905364991 36.8365287780762,24.5459709167481 36.8365287780762,24.5459709167481 36.8368110656738,24.5462055206299 36.8368110656738,24.5465278625489 36.8368072509768,24.5465278625489 36.8370819091796,24.5468063354493 36.8370819091796,24.5468063354493 36.8373603820802,24.5487518310548 36.8373603820802,24.5487518310548 36.8381156921388,24.5487518310548 36.8384704589844,24.548469543457 36.8384704589844,24.5484733581544 36.8387489318849,24.547920227051 36.8387489318849,24.547920227051 36.8390312194825,24.5476417541504 36.8390312194825,24.5476417541504 36.8393096923829,24.5473594665528 36.8393058776855,24.5473594665528 36.8398628234863,24.5470829010011 36.8398628234863,24.5470829010011 36.8406944274905,24.5473594665528 36.8406944274905,24.5473594665528 36.8409729003909,24.5526390075684 36.8409729003909,24.5526409149172 36.841251373291,24.552360534668 36.841251373291,24.552360534668 36.8415298461914,24.5520858764648 36.8415260314943,24.5520858764648 36.842082977295,24.5518054962159 36.842082977295,24.5518054962159 36.8423614501954,24.5531959533692 36.8423614501954,24.5531902313232 36.8420791625978,24.5537490844728 36.842082977295,24.5537490844728 36.8418045043946,24.5540275573732 36.8418045043946,24.5540275573732 36.842082977295,24.5543079376221 36.842082977295,24.5543079376221 36.8423614501954,24.5545806884766 36.8423614501954,24.5545806884766 36.8426399230958,24.554859161377 36.8426399230958,24.554859161377 36.8445816040039,24.5559692382813 36.8445816040039,24.5559692382813 36.8448600769043,24.5568008422853 36.8448600769043,24.5568008422853 36.8451385498049,24.5570793151857 36.8451385498049,24.5570793151857 36.8456993103028,24.5573616027834 36.8456954956055,24.5573616027834 36.8459739685059,24.557638168335 36.8459701538087,24.557638168335 36.8465309143066,24.5579166412354 36.8465270996096,24.5579166412354 36.848472595215,24.5598583221436 36.848472595215,24.5598583221436 36.847637176514,24.5601425170899 36.8476409912109,24.5601425170899 36.846809387207,24.5604190826417 36.846809387207,24.5604190826417 36.8465309143066,24.5612525939943 36.8465309143066,24.5612525939943 36.8462486267092,24.5620803833008 36.8462486267092,24.5620803833008 36.8456993103028,24.5623626708984 36.8456993103028,24.5623626708984 36.8448600769043,24.5620803833008 36.8448600769043,24.5620803833008 36.84375,24.5618057250977 36.84375,24.5618057250977 36.8431930541992,24.562084197998 36.8431930541992,24.562084197998 36.8429183959962,24.5640296936035 36.8429183959962,24.5640277862549 36.8426399230958,24.5643062591553 36.8426399230958,24.5643062591553 36.8423614501954,24.5645847320557 36.8423614501954,24.5645809173583 36.8420791625978,24.5651397705079 36.842082977295,24.5651397705079 36.8418045043946,24.5659694671631 36.8418006896974,24.5659694671631 36.8423614501954,24.5662498474121 36.8423614501954,24.5662498474121 36.8426399230958,24.5677890777588 36.8426399230958,24.5679168701172 36.8426399230958,24.5679168701172 36.8429183959962,24.5695838928223 36.8429183959962,24.5695800781251 36.8423614501954,24.5698604583741 36.8423614501954,24.5698604583741 36.8415298461914,24.5701408386233 36.8415298461914,24.5701408386233 36.841251373291,24.5704174041749 36.841251373291,24.5704174041749 36.8409729003909,24.5706939697267 36.8409729003909,24.5706939697267 36.8404159545901,24.5709705352783 36.8404197692871,24.5709705352783 36.8401412963867,24.5715274810792 36.8401412963867,24.5715274810792 36.8393096923829,24.5718116760254 36.8393096923829,24.5718116760254 36.8381996154785,24.5720806121826 36.8381996154785,24.5720825195314 36.8373222351075,24.5720825195314 36.8370819091796,24.572359085083 36.8370819091796,24.572359085083 36.8365287780762,24.572639465332 36.8365287780762,24.572639465332 36.8362503051758,24.5729217529297 36.8362503051758,24.5729160308838 36.8359718322754,24.5731945037842 36.8359718322754,24.573190689087 36.8356895446779,24.5737495422363 36.8356933593751,24.5737495422363 36.8354148864747,24.5740280151367 36.8354148864747,24.5740280151367 36.8351402282715,24.5743064880371 36.8351402282715,24.5743064880371 36.8343048095705,24.5740280151367 36.8343048095705,24.5740280151367 36.8340263366701,24.5737495422363 36.8340301513671,24.5737495422363 36.8334693908694,24.5734710693359 36.8334732055664,24.5734710693359 36.833194732666,24.5737495422363 36.833194732666,24.5737495422363 36.8329162597656,24.5743103027345 36.8329200744629,24.5743103027345 36.832359313965,24.5740280151367 36.832359313965,24.5740280151367 36.8315277099612,24.5737495422363 36.8315315246584,24.5737495422363 36.8306999206542,24.5740318298341 36.8306999206542,24.5740318298341 36.8304214477539,24.5743103027345 36.8304214477539,24.5743103027345 36.8301391601563,24.5745811462403 36.8301391601563,24.5745792388917 36.8298606872559,24.5748596191407 36.8298606872559,24.5748596191407 36.8301391601563,24.5754203796388 36.8301391601563,24.5754203796388 36.8304214477539,24.5757007598878 36.8304214477539,24.5757007598878 36.8306999206542,24.575969696045 36.8306999206542,24.575969696045 36.8309707641603,24.5765285491944 36.8309707641603,24.5765285491944 36.8312492370607,24.5770797729492 36.8312492370607,24.5770797729492 36.8315315246584,24.5773601531982 36.8315315246584,24.5773601531982 36.8318099975586,24.5779170989991 36.8318061828616,24.5779170989991 36.8320846557618,24.5784702301025 36.8320808410645,24.5784702301025 36.832359313965,24.5793094635013 36.832359313965,24.5793094635013 36.8326416015626,24.5798606872559 36.8326416015626,24.5798606872559 36.8329200744629,24.5801391601562 36.8329162597656,24.5801391601562 36.833194732666,24.5809726715088 36.833194732666,24.5809726715088 36.8329162597656,24.5812492370606 36.8329200744629,24.5812492370606 36.832359313965,24.5815296173096 36.832359313965,24.581527709961 36.8318061828616,24.58180809021 36.8318099975586,24.58180809021 36.8312492370607,24.5820827484132 36.8312492370607,24.5820808410644 36.8306999206542,24.582359313965 36.8306999206542,24.582359313965 36.8304214477539,24.5831909179688 36.8304214477539,24.5831909179688 36.8301391601563,24.5851383209229 36.8301391601563,24.5851383209229 36.8304214477539,24.5859699249268 36.8304214477539,24.5859699249268 36.8306999206542,24.5879192352295 36.8306999206542,24.5879192352295 36.8304214477539,24.5881900787354 36.8304214477539,24.5881900787354 36.8301391601563,24.5885219573975 36.8301391601563,24.5887527465821 36.8301391601563,24.5887527465821 36.8304214477539,24.589309692383 36.8304214477539,24.589309692383 36.8306999206542,24.5895805358887 36.8306999206542,24.5895805358887 36.8309707641603,24.590139389038 36.8309707641603,24.590139389038 36.8312492370607,24.5904197692874 36.8312492370607,24.5904197692874 36.8315315246584,24.590139389038 36.8315315246584,24.590139389038 36.8318099975586,24.5898628234864 36.8318061828616,24.5898628234864 36.8320846557618,24.5895805358887 36.8320808410645,24.5895843505861 36.832359313965,24.589309692383 36.832359313965,24.589309692383 36.8326416015626,24.5890274047853 36.8326377868654,24.5890274047853 36.833194732666,24.5893058776857 36.833194732666,24.5893058776857 36.8334732055664,24.5895805358887 36.8334693908694,24.5895805358887 36.8337516784667,24.5898628234864 36.8337516784667,24.5898628234864 36.8340301513671,24.590139389038 36.8340301513671,24.590139389038 36.834873199463,24.590139389038 36.8359718322754,24.5904197692874 36.8359718322754,24.5904197692874 36.8368110656738,24.5906944274902 36.8368072509768,24.5906944274902 36.8370819091796,24.5909690856934 36.8370780944826,24.5909690856934 36.8373603820802,24.5934696197511 36.8373603820802,24.5934696197511 36.8370780944826,24.59375 36.8370819091796,24.59375 36.8365287780762,24.5940303802493 36.8365287780762,24.5940284729004 36.8359718322754,24.5943069458008 36.8359718322754,24.5943069458008 36.8351402282715,24.5945835113527 36.8351402282715,24.5945796966553 36.8345794677737,24.594861984253 36.8345832824709,24.594861984253 36.8343048095705,24.5954151153564 36.8343048095705,24.5954151153564 36.8340263366701,24.5956897735596 36.8340301513671,24.5956897735596 36.8337516784667,24.5962505340576 36.8337516784667,24.5962505340576 36.8334693908694,24.5965270996094 36.8334732055664,24.5965270996094 36.833194732666,24.5970840454102 36.833194732666,24.5970840454102 36.8329162597656,24.5973606109619 36.8329200744629,24.5973606109619 36.8326416015626,24.5987491607667 36.8326416015626,24.5987491607667 36.832359313965,24.6004219055176 36.832359313965,24.6004161834718 36.8320846557618,24.6006946563722 36.8320846557618,24.6006946563722 36.8318061828616,24.6009693145752 36.8318099975586,24.6009693145752 36.8312492370607,24.6012496948242 36.8312492370607,24.6012496948242 36.8309707641603,24.6015300750735 36.8309707641603,24.6015281677247 36.8306961059573,24.601802825928 36.8306999206542,24.601802825928 36.8304214477539,24.6020793914795 36.8304214477539,24.6020793914795 36.8298606872559,24.6023597717285 36.8298606872559,24.6023597717285 36.8295783996582,24.6026382446289 36.8295822143555,24.6026382446289 36.8293037414552,24.6029167175293 36.8293037414552,24.6029167175293 36.829029083252,24.6031951904297 36.829029083252,24.6031951904297 36.8287506103516,24.6034736633301 36.8287506103516,24.6034698486329 36.8284683227542,24.6037502288818 36.8284721374512,24.6037502288818 36.828193664551,24.6040287017822 36.828193664551,24.6040287017822 36.8279151916506,24.6043090820312 36.8279190063478,24.6043090820312 36.8276405334472,24.604585647583 36.8276405334472,24.6045799255372 36.8273582458498,24.6051387786866 36.8273620605468,24.6051387786866 36.8270835876465,24.6062507629397 36.8270835876465,24.6062507629397 36.8268051147461,24.6065292358399 36.8268089294434,24.6065292358399 36.8262481689455,24.6068058013917 36.8262481689455,24.6068058013917 36.8256950378419,24.6065292358399 36.8256988525391,24.6065292358399 36.8251419067385,24.6062507629397 36.8251419067385,24.6062507629397 36.8248596191407,24.6059703826904 36.8248596191407,24.6059703826904 36.8245811462403,24.6056957244874 36.8245849609377,24.6056957244874 36.8240280151369,24.6054191589355 36.824031829834,24.6054191589355 36.8231887817383,24.6051387786866 36.8231925964356,24.6051387786866 36.822639465332,24.6048603057862 36.822639465332,24.6048603057862 36.8223609924316,24.604305267334 36.8223609924316,24.604305267334 36.8220825195312,24.6040287017822 36.8220825195312,24.6040287017822 36.8218040466311,24.6028099060059 36.8218040466311,24.6020812988281 36.8218040466311,24.6020812988281 36.8220825195312,24.6015281677247 36.8220825195312,24.6015300750735 36.8223609924316,24.6012496948242 36.8223609924316,24.6012496948242 36.822639465332,24.6009693145752 36.822639465332,24.6009693145752 36.8229217529298,24.6012496948242 36.8229179382326,24.6012496948242 36.8231925964356,24.6015281677247 36.8231925964356,24.6015281677247 36.823471069336,24.601802825928 36.823471069336,24.601802825928 36.8237495422364,24.6020793914795 36.8237495422364,24.6020793914795 36.8243103027343,24.601942062378 36.8243103027343,24.601802825928 36.8243103027343,24.601802825928 36.8246116638184,24.6017951965332 36.8248596191407,24.6012496948242 36.8248596191407,24.6012496948242 36.8245811462403,24.6001415252686 36.8245849609377,24.6001415252686 36.8243064880373,24.5990314483643 36.8243103027343,24.5990314483643 36.824031829834,24.5987491607667 36.824031829834,24.5987491607667 36.8237495422364,24.5979175567627 36.8237495422364,24.5979175567627 36.823471069336,24.5976371765137 36.823471069336,24.5976371765137 36.8231887817383,24.5970840454102 36.8231925964356,24.5970840454102 36.8229179382326,24.5968074798585 36.8229217529298,24.5968074798585 36.822639465332,24.5965270996094 36.822639465332,24.5965270996094 36.8223609924316,24.5959701538086 36.8223609924316,24.5959701538086 36.8220787048343,24.5956935882568 36.8220825195312,24.5956935882568 36.8215293884278,24.5954151153564 36.8215293884278,24.5954151153564 36.8211135864258,24.5954151153564 36.8201370239258,24.5956897735596 36.8201408386231))"; +std::string const kimolos2 = "POLYGON((24.558609 36.775551,24.549721 36.774719,24.544441 36.775276,24.538055 36.779716,24.531666 36.787773,24.522221 36.804993,24.521111 36.812218,24.521111 36.823883,24.522778 36.827774,24.559166 36.848328,24.562496 36.84861,24.591942 36.833885,24.599163 36.823883,24.594997 36.807777,24.592499 36.798882,24.577774 36.781662,24.574718 36.779442,24.558609 36.775551))"; + +auto reverse = [](auto const& g) +{ + auto result = g; + std::reverse(result.begin(), result.end()); + return result; +}; + +auto simplify = [](auto const& g, auto const simplify_distance) +{ + std::decay_t result; + boost::geometry::simplify(g, result, simplify_distance); + return result; +}; + +#if defined(TEST_WITH_COMPARE) +template +double symmetric_hausdorff(const Geometry& geometry1, const Geometry& geometry2) +{ + namespace bg = boost::geometry; + return std::max(bg::discrete_hausdorff_distance(geometry1, geometry2), + bg::discrete_hausdorff_distance(geometry2, geometry1)); +} +#endif + +#if defined(TEST_WITH_GEOJSON) +struct geojson_visitor +{ + geojson_visitor() + { + m_stream_coll << std::setprecision(20); + m_stream_quad << std::setprecision(20); + } + + template + void visit_triangle(Ring const& ring, Proj const& p0, Proj const& p1, Proj const& p2, int, int) + // void visit_quadrilateral(T const& ring, I const& a, I const& b, I const& c, I const& d) + { + m_quad.feature(ring); + m_quad.add_property("role", 7); + m_quad.add_property("area", std::round(boost::geometry::area(ring))); + //m_quad.add_property("source", source); + m_quad.add_property("code", 1); + m_quad.add_property("size", ring.size()); + + { + std::ostringstream out; + out << p0.source_index + << " " << p1.source_index + << " " << p2.source_index; + m_quad.add_property("sources", out.str()); + } + } + template void visit_projections(int source, C const& collection) + { + std::size_t index = 0; + for (const auto& info : collection) + { + m_collection.feature(info.point); + m_collection.add_property("index", index++); + m_collection.add_property("source", source); + m_collection.add_property("is_projection", info.is_projection ? 1 : 0); + m_collection.add_property("distance", info.distance); + m_collection.add_property("source_index", info.source_index); + m_collection.add_property("segment_index", info.segment_index); + m_collection.add_property("sort_index", info.sort_index); + m_collection.add_property("offset", info.offset); + } + } + + std::ofstream m_stream_coll{"/tmp/collection.geojson"}; + std::ofstream m_stream_quad{"/tmp/quad.geojson"}; + boost::geometry::geojson_writer m_collection{m_stream_coll}; + boost::geometry::geojson_writer m_quad{m_stream_quad}; +}; + +template +void write_sources_as_geojson(std::string const& filename, P const& p, Q const& q) +{ + std::ofstream fout{filename}; + fout << std::setprecision(20); + + boost::geometry::geojson_writer writer(fout); + + writer.feature(p); + writer.add_property("name", "p"); + + writer.feature(q); + writer.add_property("name", "q"); + + for (const auto& pnt : p) + { + writer.feature(pnt); + writer.add_property("name", "p"); + } + for (const auto& pnt : q) + { + writer.feature(pnt); + writer.add_property("name", "q"); + } +} +#endif + + +} // anonymous namespace + +template +void test_ad(std::string const& case_id, Geometry const& p, Geometry const& q, T const expected) +{ + constexpr T test_epsilon = 0.0001; + auto const detected = boost::geometry::average_distance(p, q); + BOOST_CHECK_MESSAGE(std::abs(detected - expected) < test_epsilon, + "Algorithm: average_distance" + << " Case: " << case_id + << " Detected: " << detected + << " Expected: " << expected); +} + +template +void test_sim(std::string const& case_id, Geometry const& p, Geometry const& q, Result const expected, Visitor& visitor) +{ + auto const detected = boost::geometry::similarity(p, q, visitor); + BOOST_CHECK_MESSAGE(detected.is_reversed == expected.is_reversed, + "Algorithm: similarity" + << " Case: " << case_id + << " Detected: " << detected.is_reversed + << " Expected: " << expected.is_reversed); +} + +template +void test_average_distance_geographic() +{ + using point = bg::model::point>; + using linestring = boost::geometry::model::linestring; + + // Abbreviation, for readability + auto make_ls = [](std::string const& wkt) + { + return boost::geometry::from_wkt(wkt); + }; + + // Should be close + test_ad("geo_west", make_ls(west1), make_ls(west2), 0.44457); + test_ad("geo_east", make_ls(east1), make_ls(east2), 0.343435); + + // Should be far apart + test_ad("geo_west_east1", make_ls(west1), make_ls(east1), 423.5956); + test_ad("geo_west_east2", make_ls(west2), make_ls(east2), 419.2242); +} + +template +void test_average_distance_cartesian() +{ + using point = boost::geometry::model::d2::point_xy; + using linestring = boost::geometry::model::linestring; + + auto make_ls = [](std::string const& wkt) + { + return boost::geometry::from_wkt(wkt); + }; + + test_ad("simplex1", make_ls(simplex1), make_ls(simplex2), 0.1); + test_ad("simplex2", make_ls(simplex1), make_ls(simplex3), 0.1); + test_ad("simplex3", make_ls(simplex1), make_ls(simplex4), 0.114); + + // One meter is roughly 1.0e-5 degree. For readability and convenient omparison with the + // cases in test_average_distance_geographic, this scale is used for all test cases using lat-lon coordinates. + constexpr T scale = 1.0e-5; + + // Should be close + test_ad("west", make_ls(west1), make_ls(west2), 0.561308 * scale); + test_ad("east", make_ls(east1), make_ls(east2), 0.398684 * scale); + + // Should be far apart + test_ad("west_east1", make_ls(west1), make_ls(east1), 492.539 * scale); + test_ad("west_east2", make_ls(west2), make_ls(east2), 484.801 * scale); + + // Should be identical to close + test_ad("west_rev", make_ls(west1), reverse(make_ls(west2)), 0.561308 * scale); + test_ad("east_rev", make_ls(east1), reverse(make_ls(east2)), 0.398684 * scale); + + // Should become closer and closer + test_ad("west_simp1", make_ls(west2), simplify(make_ls(west2), 50.0 * scale), 6.14674 * scale); + test_ad("west_simp2", make_ls(west2), simplify(make_ls(west2), 10.0 * scale), 2.45124 * scale); + test_ad("west_simp3", make_ls(west2), simplify(make_ls(west2), 1.0 * scale), 0.0733346 * scale); + test_ad("west_simp4", make_ls(west2), simplify(make_ls(west2), 0.1 * scale), 0.0 * scale); + + test_ad("east_simp1", make_ls(east2), simplify(make_ls(east2), 50.0 * scale), 12.4301 * scale); + test_ad("east_simp2", make_ls(east2), simplify(make_ls(east2), 10.0 * scale), 0.753191 * scale); + test_ad("east_simp3", make_ls(east2), simplify(make_ls(east2), 1.0 * scale), 0.0511786 * scale); + test_ad("east_simp4", make_ls(east2), simplify(make_ls(east2), 0.1 * scale), 0.0 * scale); + + // Parts are not yet finished + test_ad("part1", make_ls(complete), make_ls(part1), 0.725765 * scale); + test_ad("part2", make_ls(complete), make_ls(part2), 19.9363 * scale); + + // Identical cases return 0.0 + test_ad("identical_west", make_ls(west1), make_ls(west1), 0.0); + test_ad("identical_east", make_ls(east1), make_ls(east1), 0.0); + test_ad("identical_part1", make_ls(part1), make_ls(part1), 0.0); + test_ad("identical_part2", make_ls(part2), make_ls(part2), 0.0); +} + +template +void test_average_distance_ring_cartesian() +{ + using point = bg::model::point>; + using linestring = boost::geometry::model::linestring; + using ring = boost::geometry::model::ring; + + auto const p = boost::geometry::from_wkt(kimolos1); + + // Read q as a line, remove its closing point, simplify it a bit + auto rq = boost::geometry::from_wkt(kimolos1); + rq.pop_back(); + linestring lq(rq.begin(), rq.end()); + auto const slq = simplify(lq, 250.0); + + // Convert back to a ring + ring q(slq.begin(), slq.end()); + boost::geometry::correct(q); + + std::cout << "Q: " << p.size() << " " << q.size() << std::endl; + +#if defined(TEST_WITH_COMPARE) + std::cout << "Frechet: " << boost::geometry::discrete_frechet_distance(lq, slq) + << " Hausdorff: " << symmetric_hausdorff(lq, slq) + << " Average: " << boost::geometry::average_distance(lq, slq) + << std::endl; +#endif + + + auto bestq = q; + +#if 0 + // For rotation + double mind = 0.0; + for (int i = 0; i + 1 < q.size(); i++) + { + auto pq = q; + std::rotate(pq.begin(), pq.begin() + i, pq.end()); + + // Correct, to re-add the closing point + boost::geometry::correct(pq); + boost::geometry::similarity_default_visitor dv; + auto const detected = boost::geometry::similarity(p, pq, dv); + std::cout << " i " << detected.distance; + if (i == 0 || detected.distance < mind) + { + bestq = pq; + mind = detected.distance; + std::cout << " ***"; + } + std::cout << std::endl; + } +#endif + + std::cout << std::fixed << std::setprecision(3); + +#if defined(TEST_WITH_GEOJSON) + geojson_visitor visitor; + + auto const detected = boost::geometry::similarity(p, bestq, visitor); + + std::cout << "RING: " << detected.distance << std::endl; + + write_sources_as_geojson("/tmp/rings.geojson", p, q); + + using polygon = boost::geometry::model::polygon; + using multi_polygon = boost::geometry::model::multi_polygon; + const double per = boost::geometry::perimeter(bestq); + if (false) + { + multi_polygon symed; + + boost::geometry::sym_difference(p, bestq, symed); + std::cout << "SYMED: " << boost::geometry::area(symed) / per << std::endl;; + + { + std::ofstream fout{"/tmp/symed.geojson"}; + fout << std::setprecision(20); + + boost::geometry::geojson_writer writer(fout); + + writer.feature(symed); + writer.add_property("name", "symed"); + } + } + else + { + multi_polygon a; + multi_polygon b; + boost::geometry::difference(p, bestq, a); + boost::geometry::difference(bestq, p, b); + + double sum_area = boost::geometry::area(a) + boost::geometry::area(b); + std::cout << "DIFFED: " << sum_area << " / " << per << " = " << sum_area / per << std::endl;; + + // double per = boost::geometry::perimeter(q); + // std::cout << "SYMED: " << boost::geometry::area(symed) / per << std::endl;; + { + std::ofstream fout{"/tmp/diff.geojson"}; + fout << std::setprecision(20); + + boost::geometry::geojson_writer writer(fout); + + auto explode = [&](const auto& mp, const std::string& name) + { + for (const auto& p : mp) + { + writer.feature(p); + writer.add_property("name", name); + writer.add_property("area", std::lround(boost::geometry::area(p))); + } + }; + explode(a, "a"); + explode(b, "b"); + } + } + +#endif + // test_ad("kimolos", make_ring(kimolos1), make_ring(kimolos2), 0.0869059); +} + + +template +void test_debug_case() +{ + using point = bg::model::point>; + using linestring = boost::geometry::model::linestring; + + // start 24.595689624 36.816810831 to 24.5940284729004 36.8140258789062 + // Full + // auto const p = boost::geometry::from_wkt("LINESTRING(24.5956897735596 36.8168106079103,24.5954818725587 36.8168106079103,24.5954208374024 36.8168106079103,24.5954208374024 36.8162498474121,24.5940284729004 36.8162498474121,24.5940284729004 36.8159713745117,24.593334197998 36.8159713745117,24.593189239502 36.8159713745117,24.593189239502 36.8156890869143,24.5926380157471 36.8156929016113,24.5926380157471 36.8151397705079,24.5929164886475 36.8151397705079,24.5929164886475 36.8148612976074,24.5936183929443 36.8148612976074,24.59375 36.8148612976074,24.59375 36.81457901001,24.5940284729004 36.814582824707,24.5940284729004 36.8140258789062)"); + // auto const q = boost::geometry::from_wkt("LINESTRING(24.59438129 36.81383378,24.59505437 36.81708132)"); + auto const p = boost::geometry::from_wkt("LINESTRING(24.593189239502 36.8156890869143,24.5926380157471 36.8156929016113,24.5926380157471 36.8151397705079,24.5929164886475 36.8151397705079,24.5929164886475 36.8148612976074,24.5936183929443 36.8148612976074,24.59375 36.8148612976074,24.59375 36.81457901001,24.5940284729004 36.814582824707,24.5940284729004 36.8140258789062)"); + auto const q = boost::geometry::from_wkt("LINESTRING(24.59438129 36.81383378,24.5948331 36.8160010)"); + + + +#if defined(TEST_WITH_COMPARE) + std::cout << "Frechet: " << boost::geometry::discrete_frechet_distance(p, q) + << " Hausdorff: " << symmetric_hausdorff(p, q) + << " Average: " << boost::geometry::average_distance(p, q) + << std::endl; +#endif + +#if defined(TEST_WITH_GEOJSON) + geojson_visitor visitor; + auto const detected = boost::geometry::similarity(p, q, visitor); + write_sources_as_geojson("/tmp/cornery.geojson", p, q); +#endif +} + +template +void test_similarity() +{ + using point = boost::geometry::model::d2::point_xy; + using linestring = boost::geometry::model::linestring; + + auto make_expectation = [](bool is_reversed) + { + boost::geometry::similarity_info result; + result.is_reversed = is_reversed; + return result; + }; + + auto const p = boost::geometry::from_wkt(west1); + auto const q = boost::geometry::from_wkt(west2); + + boost::geometry::similarity_default_visitor default_visitor; +#if defined(TEST_WITH_GEOJSON) + + // Write the features to a geojson file, as linestring and as point, + // and then, at visiting the algorithm, write debug-information. + geojson_visitor visitor; + write_sources_as_geojson("/tmp/west.geojson", p, q); +#else + auto& visitor = default_visitor; +#endif + + test_sim("west", p, q, make_expectation(false), visitor); + test_sim("west_rev1", p, reverse(q), make_expectation(true), default_visitor); + test_sim("west_rev2", reverse(p), q, make_expectation(true), default_visitor); + test_sim("west_both", reverse(p), reverse(q), make_expectation(false), default_visitor); +} + +int test_main(int, char* []) +{ + test_average_distance_ring_cartesian(); + // test_debug_case(); + // test_average_distance_cartesian(); + // test_average_distance_geographic(); + // test_similarity(); + + return 0; +} + + +// 2031 rm -Rf out ; ogr2ogr -f CSV out GRC_adm0.shp -lco GEOMETRY=AS_WKT -explodecollections +// 2037 nl -s "," out/GRC_adm0.csv > out/with_ids.csv +// 2038 vi out/with_ids.csv