Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does `almost_equals` function of Shapely treat the starting point and errors?

Here are polygons with the the same set of points but different starting points/rounding error but still the same direction.

poly1 = Polygon([(0,0),(0,1),(1,1),(1,0)])
poly2 = Polygon([(0,1),(1,1),(1,0),(0,0)])
poly3 = Polygon([(0,1),(1,1.00000001),(1,0),(0,0)])
poly4 = Polygon([(0,0),(0,1),(1,1.00000001),(1,0)])

Issue 1: poly1.almost_equals(poly2) returns False but poly1.equals(poly2) returns True. So equals can handle different starting point but almost_equals can not.

Issue 2: poly1.almost_equals(poly3) returns False but poly1.almost_equals(poly4) returns True. So almost_equals can handle rounding errors but still not different starting point.

Is this how the almost_equals function supposed to behave? I think Polygons with different starting points are still the same Polygon and should be treated so. Are there convenient way to solve this problem? I have a complicated custom solution but am wondering if such operation has been implemented in Shapely.

like image 697
ZL-Arctic Avatar asked Oct 15 '25 17:10

ZL-Arctic


1 Answers

Yes, this is an expected behavior. It is not clearly stated in the documentation but you can find it in the docstring of the function:

def almost_equals(self, other, decimal=6):
    """Returns True if geometries are equal at all coordinates to a
    specified decimal place

    Refers to approximate coordinate equality, which requires coordinates be
    approximately equal and in the same order for all components of a geometry.
    """
    return self.equals_exact(other, 0.5 * 10**(-decimal))

Currently, there is no any other function that you could use to solve your problem. There is an open issue on GitHub where the future of almost_equals is discussed. So, probably, soon a new convenient functionality will be introduced. And, meanwhile, you could use several workarounds:

  1. Calculate symmetric_difference of two polygons and compare the resulting area with some minimum threshold.
    E.g.:
    def almost_equals(polygon, other, threshold):
        # or (polygon ^ other).area < threshold
        return polygon.symmetric_difference(other).area < threshold
    
    almost_equals(poly1, poly3, 1e-6)  # True
    
  2. Normalize both polygons first and then use the almost_equals. To normalize them you could, for example, orient the vertices of the borders counter-clockwise and then order the vertices so that the first vertex of each polygon would be the lowest and the leftmost comparing to other vertices.
    E.g.:
    from shapely.geometry.polygon import orient
    
    
    def normalize(polygon):
    
        def normalize_ring(ring):
            coords = ring.coords[:-1]
            start_index = min(range(len(coords)), key=coords.__getitem__)
            return coords[start_index:] + coords[:start_index]
    
        polygon = orient(polygon)
        normalized_exterior = normalize_ring(polygon.exterior)
        normalized_interiors = list(map(normalize_ring, polygon.interiors))
        return Polygon(normalized_exterior, normalized_interiors)
    
    
    normalize(poly1).almost_equals(normalize(poly3))  # True
    
like image 165
Georgy Avatar answered Oct 18 '25 05:10

Georgy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!