FAILURE MAP
← Case archive

FA-70721 / GIS polygon topology / Open access

WKT polygon and multipolygon ring structure: ring closure check · case 01

Unclosed rings are accepted.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Rings are checked for size but not for closure.

THE FAILURE

Rings are checked for size but not for closure.

Unsuccessful approach: Closure is compared on x only.

Case contract

Input: a WKT string. The keyword (case-insensitive) must be POLYGON or MULTIPOLYGON, else None; "<keyword> EMPTY" returns []. Otherwise parse the parenthesised body into polygons -> rings -> positions, keeping only x and y of every position (Z/M ordinates are dropped). Every ring must have at least 4 positions and be closed in x/y, else None. Return a list of polygons (a POLYGON gives a one-element list).

Why this case matters

WKT is the lingua franca between databases and GIS tools; losing a dimension or nesting level corrupts rings.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    s = x.strip()
    up = s.upper()
    words = up.replace('(', ' ( ').split()
    if not words or words[0] not in ('POLYGON', 'MULTIPOLYGON'):
        return None
    if len(words) == 2 and words[1] == 'EMPTY':
        return []
    if '(' not in s:
        return None
    body = s[s.index('('):]
    stack = [[]]
    cur = ''
    for ch in body:
        if ch == '(':
            stack.append([])
        elif ch in '),':
            if cur.strip():
                nums = [float(t) for t in cur.split()]
                stack[-1].append(nums[:2])
            cur = ''
            if ch == ')':
                done = stack.pop()
                stack[-1].append(done)
        else:
            cur += ch
    tree = stack[0][0]
    polys = [tree] if words[0] == 'POLYGON' else tree
    for poly in polys:
        for ring in poly:
            if len(ring) < 4:
                return None
    return polys
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', 'POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0], [0.0, 0.0]]]]), ('control #1', 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 2 4, 4 4, 2 2))', [[[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0], [0.0, 0.0]], [[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [2.0, 2.0]]]]), ('regression #2', 'POLYGON Z ((0 0 5, 4 0 5, 4 4 6, 0 0 5))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]), ('regression #3', 'POLYGON ZM ((0 0 5 1, 4 0 5 2, 4 4 6 3, 0 0 5 1))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]), ('regression #4', 'POLYGON M ((0 0 7, 4 0 8, 4 4 9, 0 0 7))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]), ('boundary #5', 'POLYGON EMPTY', []), ('boundary #6', 'polygon empty', []), ('boundary #10', 'POLYGON ((0 0, 4 0, 4 4, 0 1))', None)], [('regression #3', 'POLYGON ZM ((0 0 5 1, 4 0 5 2, 4 4 6 3, 0 0 5 1))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]), ('regression #4', 'POLYGON M ((0 0 7, 4 0 8, 4 4 9, 0 0 7))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]), ('boundary #5', 'POLYGON EMPTY', []), ('boundary #6', 'polygon empty', []), ('regression #7', 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((5 5, 6 5, 6 6, 5 5), (5.2 5.1, 5.8 5.8, 5.8 5.1, 5.2 5.1)))', [[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]], [[[5.0, 5.0], [6.0, 5.0], [6.0, 6.0], [5.0, 5.0]], [[5.2, 5.1], [5.8, 5.8], [5.8, 5.1], [5.2, 5.1]]]]), ('regression #8', 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))', [[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]]]), ('regression #9', 'multipolygon (((-1.5 -2, 3e2 0, 0 1e1, -1.5 -2)))', [[[[-1.5, -2.0], [300.0, 0.0], [0.0, 10.0], [-1.5, -2.0]]]]), ('boundary #10', 'POLYGON ((0 0, 4 0, 4 4, 0 1))', None)], [('boundary #6', 'polygon empty', []), ('regression #7', 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((5 5, 6 5, 6 6, 5 5), (5.2 5.1, 5.8 5.8, 5.8 5.1, 5.2 5.1)))', [[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]], [[[5.0, 5.0], [6.0, 5.0], [6.0, 6.0], [5.0, 5.0]], [[5.2, 5.1], [5.8, 5.8], [5.8, 5.1], [5.2, 5.1]]]]), ('regression #8', 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))', [[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]]]), ('regression #9', 'multipolygon (((-1.5 -2, 3e2 0, 0 1e1, -1.5 -2)))', [[[[-1.5, -2.0], [300.0, 0.0], [0.0, 10.0], [-1.5, -2.0]]]]), ('boundary #10', 'POLYGON ((0 0, 4 0, 4 4, 0 1))', None), ('boundary #11', 'POLYGON ((0 0, 4 0, 0 0))', None), ('control #12', 'Polygon((1 1,5 1,5 5,1 1))', [[[[1.0, 1.0], [5.0, 1.0], [5.0, 5.0], [1.0, 1.0]]]]), ('boundary #13', 'LINESTRING (0 0, 1 1)', None)], [('regression #9', 'multipolygon (((-1.5 -2, 3e2 0, 0 1e1, -1.5 -2)))', [[[[-1.5, -2.0], [300.0, 0.0], [0.0, 10.0], [-1.5, -2.0]]]]), ('boundary #10', 'POLYGON ((0 0, 4 0, 4 4, 0 1))', None), ('boundary #11', 'POLYGON ((0 0, 4 0, 0 0))', None), ('control #12', 'Polygon((1 1,5 1,5 5,1 1))', [[[[1.0, 1.0], [5.0, 1.0], [5.0, 5.0], [1.0, 1.0]]]]), ('boundary #13', 'LINESTRING (0 0, 1 1)', None), ('boundary #14', 'MULTIPOLYGON EMPTY', []), ('control #15', '  POLYGON ((0 0, -3 0, -3 -3, 0 0))  ', [[[[0.0, 0.0], [-3.0, 0.0], [-3.0, -3.0], [0.0, 0.0]]]]), ('regression #16', 'MULTIPOLYGON Z (((0 0 1, 2 0 1, 2 2 1, 0 0 1)), ((9 9 0, 8 9 0, 8 8 0, 9 9 0)))', [[[[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 0.0]]], [[[9.0, 9.0], [8.0, 9.0], [8.0, 8.0], [9.0, 9.0]]]])], [('control #0', 'POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0], [0.0, 0.0]]]]), ('control #1', 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 2 4, 4 4, 2 2))', [[[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0], [0.0, 0.0]], [[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [2.0, 2.0]]]]), ('boundary #10', 'POLYGON ((0 0, 4 0, 4 4, 0 1))', None), ('boundary #13', 'LINESTRING (0 0, 1 1)', None), ('boundary #14', 'MULTIPOLYGON EMPTY', []), ('control #15', '  POLYGON ((0 0, -3 0, -3 -3, 0 0))  ', [[[[0.0, 0.0], [-3.0, 0.0], [-3.0, -3.0], [0.0, 0.0]]]]), ('regression #16', 'MULTIPOLYGON Z (((0 0 1, 2 0 1, 2 2 1, 0 0 1)), ((9 9 0, 8 9 0, 8 8 0, 9 9 0)))', [[[[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 0.0]]], [[[9.0, 9.0], [8.0, 9.0], [8.0, 8.0], [9.0, 9.0]]]]), ('boundary #17', 'POLYGON ((0 0 1, 4 0 1, 4 4 1, 0 0 2))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]])]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
control #0[[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0], [0.0, 0.0]]]][[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0], [0.0, 0.0]]]]Passed
control #1[[[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0], [0.0, 0.0]], [[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [2.0, 2.0]]]][[[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0], [0.0, 0.0]], [[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [2.0, 2.0]]]]Passed
regression #2[[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]][[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]Passed
regression #3[[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]][[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]Passed
regression #4[[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]][[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]Passed
boundary #5[][]Passed
boundary #6[][]Passed
boundary #10[[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 1.0]]]]NoneFailed

SHA-256 / 9a4881bf2e43778324e1ead7d9be4f0be775d142866a0d8421e5d304d4d3aaa2

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
    s = x.strip()
    up = s.upper()
    words = up.replace('(', ' ( ').split()
    if not words or words[0] not in ('POLYGON', 'MULTIPOLYGON'):
        return None
    if len(words) == 2 and words[1] == 'EMPTY':
        return []
    if '(' not in s:
        return None
    body = s[s.index('('):]
    stack = [[]]
    cur = ''
    for ch in body:
        if ch == '(':
            stack.append([])
        elif ch in '),':
            if cur.strip():
                nums = [float(t) for t in cur.split()]
                stack[-1].append(nums[:2])
            cur = ''
            if ch == ')':
                done = stack.pop()
                stack[-1].append(done)
        else:
            cur += ch
    tree = stack[0][0]
    polys = [tree] if words[0] == 'POLYGON' else tree
    for poly in polys:
        for ring in poly:
            if len(ring) < 4 or ring[0][0] != ring[-1][0]:
                return None
    return polys
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', 'POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0], [0.0, 0.0]]]]), ('control #1', 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 2 4, 4 4, 2 2))', [[[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0], [0.0, 0.0]], [[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [2.0, 2.0]]]]), ('regression #2', 'POLYGON Z ((0 0 5, 4 0 5, 4 4 6, 0 0 5))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]), ('regression #3', 'POLYGON ZM ((0 0 5 1, 4 0 5 2, 4 4 6 3, 0 0 5 1))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]), ('regression #4', 'POLYGON M ((0 0 7, 4 0 8, 4 4 9, 0 0 7))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]), ('boundary #5', 'POLYGON EMPTY', []), ('boundary #6', 'polygon empty', []), ('boundary #10', 'POLYGON ((0 0, 4 0, 4 4, 0 1))', None)], [('regression #3', 'POLYGON ZM ((0 0 5 1, 4 0 5 2, 4 4 6 3, 0 0 5 1))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]), ('regression #4', 'POLYGON M ((0 0 7, 4 0 8, 4 4 9, 0 0 7))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]), ('boundary #5', 'POLYGON EMPTY', []), ('boundary #6', 'polygon empty', []), ('regression #7', 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((5 5, 6 5, 6 6, 5 5), (5.2 5.1, 5.8 5.8, 5.8 5.1, 5.2 5.1)))', [[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]], [[[5.0, 5.0], [6.0, 5.0], [6.0, 6.0], [5.0, 5.0]], [[5.2, 5.1], [5.8, 5.8], [5.8, 5.1], [5.2, 5.1]]]]), ('regression #8', 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))', [[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]]]), ('regression #9', 'multipolygon (((-1.5 -2, 3e2 0, 0 1e1, -1.5 -2)))', [[[[-1.5, -2.0], [300.0, 0.0], [0.0, 10.0], [-1.5, -2.0]]]]), ('boundary #10', 'POLYGON ((0 0, 4 0, 4 4, 0 1))', None)], [('boundary #6', 'polygon empty', []), ('regression #7', 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((5 5, 6 5, 6 6, 5 5), (5.2 5.1, 5.8 5.8, 5.8 5.1, 5.2 5.1)))', [[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]], [[[5.0, 5.0], [6.0, 5.0], [6.0, 6.0], [5.0, 5.0]], [[5.2, 5.1], [5.8, 5.8], [5.8, 5.1], [5.2, 5.1]]]]), ('regression #8', 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))', [[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]]]), ('regression #9', 'multipolygon (((-1.5 -2, 3e2 0, 0 1e1, -1.5 -2)))', [[[[-1.5, -2.0], [300.0, 0.0], [0.0, 10.0], [-1.5, -2.0]]]]), ('boundary #10', 'POLYGON ((0 0, 4 0, 4 4, 0 1))', None), ('boundary #11', 'POLYGON ((0 0, 4 0, 0 0))', None), ('control #12', 'Polygon((1 1,5 1,5 5,1 1))', [[[[1.0, 1.0], [5.0, 1.0], [5.0, 5.0], [1.0, 1.0]]]]), ('boundary #13', 'LINESTRING (0 0, 1 1)', None)], [('regression #9', 'multipolygon (((-1.5 -2, 3e2 0, 0 1e1, -1.5 -2)))', [[[[-1.5, -2.0], [300.0, 0.0], [0.0, 10.0], [-1.5, -2.0]]]]), ('boundary #10', 'POLYGON ((0 0, 4 0, 4 4, 0 1))', None), ('boundary #11', 'POLYGON ((0 0, 4 0, 0 0))', None), ('control #12', 'Polygon((1 1,5 1,5 5,1 1))', [[[[1.0, 1.0], [5.0, 1.0], [5.0, 5.0], [1.0, 1.0]]]]), ('boundary #13', 'LINESTRING (0 0, 1 1)', None), ('boundary #14', 'MULTIPOLYGON EMPTY', []), ('control #15', '  POLYGON ((0 0, -3 0, -3 -3, 0 0))  ', [[[[0.0, 0.0], [-3.0, 0.0], [-3.0, -3.0], [0.0, 0.0]]]]), ('regression #16', 'MULTIPOLYGON Z (((0 0 1, 2 0 1, 2 2 1, 0 0 1)), ((9 9 0, 8 9 0, 8 8 0, 9 9 0)))', [[[[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 0.0]]], [[[9.0, 9.0], [8.0, 9.0], [8.0, 8.0], [9.0, 9.0]]]])], [('control #0', 'POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0], [0.0, 0.0]]]]), ('control #1', 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 2 4, 4 4, 2 2))', [[[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0], [0.0, 0.0]], [[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [2.0, 2.0]]]]), ('boundary #10', 'POLYGON ((0 0, 4 0, 4 4, 0 1))', None), ('boundary #13', 'LINESTRING (0 0, 1 1)', None), ('boundary #14', 'MULTIPOLYGON EMPTY', []), ('control #15', '  POLYGON ((0 0, -3 0, -3 -3, 0 0))  ', [[[[0.0, 0.0], [-3.0, 0.0], [-3.0, -3.0], [0.0, 0.0]]]]), ('regression #16', 'MULTIPOLYGON Z (((0 0 1, 2 0 1, 2 2 1, 0 0 1)), ((9 9 0, 8 9 0, 8 8 0, 9 9 0)))', [[[[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 0.0]]], [[[9.0, 9.0], [8.0, 9.0], [8.0, 8.0], [9.0, 9.0]]]]), ('boundary #17', 'POLYGON ((0 0 1, 4 0 1, 4 4 1, 0 0 2))', [[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]])]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
control #0[[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0], [0.0, 0.0]]]][[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0], [0.0, 0.0]]]]Passed
control #1[[[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0], [0.0, 0.0]], [[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [2.0, 2.0]]]][[[[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0], [0.0, 0.0]], [[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [2.0, 2.0]]]]Passed
regression #2[[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]][[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]Passed
regression #3[[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]][[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]Passed
regression #4[[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]][[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 0.0]]]]Passed
boundary #5[][]Passed
boundary #6[][]Passed
boundary #10[[[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 1.0]]]]NoneFailed

SHA-256 / c2075d0f7828fe1d07aeb439457b65edff75dd10d3f559ddada4240641c97741

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 8 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

Verification & scope

Stipulated deterministic toy contract on a bounded input domain; results are rounded as stated and no conformance with any published standard or library is claimed. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:48:23.237651+00:00.

Case digest / 1fd5f81a586dd24814bcc9c8dba8a48d1b4c709e73713db6d9a8384034afc88f