s2geometry icon indicating copy to clipboard operation
s2geometry copied to clipboard

Usage: How to test for polygon being covered by another polygon with shared boundary?

Open JoelJaeschke opened this issue 4 months ago • 1 comments

Given two polygons, I would like to replicate what ST_Covers does using S2. While the case of one polygon being contained in the interior of another works (or at least yields the result I would expect), I have trouble with the case where two polygons share part of their boundary (compare script below):

#include <vector>
#include <iostream>

#include <s2/s2boolean_operation.h>
#include <s2/mutable_s2shape_index.h>
#include <s2/s2point.h>
#include <s2/s2polygon.h>
#include <s2/s2debug.h>
#include <s2/s2loop.h>

int main(int argc, char** argv) {
    // "parent" polygon, the one that should cover all the other polygons
    std::vector<S2Point> vertices_parent = {
            S2LatLng::FromDegrees(60, -118).ToPoint(),
            S2LatLng::FromDegrees(23, -118).ToPoint(),
            S2LatLng::FromDegrees(23, 34).ToPoint(),
            S2LatLng::FromDegrees(60, 34).ToPoint(),
    };
    std::unique_ptr<S2Loop> parent_loop = std::make_unique<S2Loop>(vertices_parent, S2Debug::DISABLE);
    const auto parent_poly = std::make_unique<S2Polygon>(std::move(parent_loop), S2Debug::DISABLE);

    MutableS2ShapeIndex parent_shape_index{MutableS2ShapeIndex::Options()};
    parent_shape_index.Add(std::make_unique<S2Polygon::Shape>(parent_poly.get()));

    // "child" polygon that shares a common boundary
    std::vector<S2Point> vertices_child = {
            S2LatLng::FromDegrees(45, -118).ToPoint(),
            S2LatLng::FromDegrees(23, -118).ToPoint(),
            S2LatLng::FromDegrees(23, 34).ToPoint(),
            S2LatLng::FromDegrees(45, 34).ToPoint(),
    };
    std::unique_ptr<S2Loop> child_loop = std::make_unique<S2Loop>(vertices_child, S2Debug::DISABLE);
    const auto child_poly = std::make_unique<S2Polygon>(std::move(child_loop), S2Debug::DISABLE);
    
    MutableS2ShapeIndex child_shape_index{MutableS2ShapeIndex::Options()};
    child_shape_index.Add(std::make_unique<S2Polygon::Shape>(child_poly.get()));

    // "child" polygon that is full contained in the interior
    std::vector<S2Point> vertices_child_int = {
            S2LatLng::FromDegrees(40, -117).ToPoint(),
            S2LatLng::FromDegrees(37, -117).ToPoint(),
            S2LatLng::FromDegrees(37, -114).ToPoint(),
            S2LatLng::FromDegrees(40, -114).ToPoint(),
    };
    std::unique_ptr<S2Loop> child_int_loop = std::make_unique<S2Loop>(vertices_child_int, S2Debug::DISABLE);
    const auto child_int_poly = std::make_unique<S2Polygon>(std::move(child_int_loop), S2Debug::DISABLE);
    
    MutableS2ShapeIndex child_int_shape_index{MutableS2ShapeIndex::Options()};
    child_int_shape_index.Add(std::make_unique<S2Polygon::Shape>(child_int_poly.get()));

    // test for containment
    S2BooleanOperation::Options closedOptions{};
    closedOptions.set_polygon_model(S2BooleanOperation::PolygonModel::CLOSED);
    closedOptions.set_polyline_model(S2BooleanOperation::PolylineModel::CLOSED);

    // containment in interior
    const auto int_contains_polygon_str = parent_poly->Contains(*child_int_poly) ? "true" : "false";
    const auto int_contains_boolop_str = S2BooleanOperation::Contains(parent_shape_index, child_int_shape_index, closedOptions) ? "true" : "false";

    std::cout << "Interior => contains (boolop): " << int_contains_boolop_str << ", contains (poly): " << int_contains_polygon_str << std::endl;

    // containment with shared boundary
    const auto contains_polygon_str = parent_poly->Contains(*child_poly) ? "true" : "false";
    const auto contains_boolop_str = S2BooleanOperation::Contains(parent_shape_index, child_shape_index, closedOptions) ? "true" : "false";

    std::cout << "Shared => contains (boolop): " << contains_boolop_str << ", contains (poly): " << contains_polygon_str << std::endl;

    return 0;
}

When I run this, it returns

Interior => contains (boolop): true, contains (poly): true
Shared => contains (boolop): false, contains (poly): false

which is not what I expect in the case of the shared boundary. Is this just a misunderstanding on my end and I am going about this the wrong way? Or do I have to use some combination of predicates to replicate what ST_Covers does? I would greatly appreciate any pointers!

JoelJaeschke avatar Oct 21 '24 20:10 JoelJaeschke