@@ -454,7 +454,7 @@ public void testGeometryInvalidReason()
454454 // invalid geometries
455455 assertInvalidReason ("MULTIPOINT ((0 0), (0 1), (1 1), (0 1))" , "[MultiPoint] Repeated point: (0.0 1.0)" );
456456 assertInvalidReason ("LINESTRING (0 0, -1 0.5, 0 1, 1 1, 1 0, 0 1, 0 0)" , "[LineString] Self-intersection at or near: (0.0 1.0)" );
457- assertInvalidReason ("POLYGON ((0 0, 1 1, 0 1, 1 0, 0 0))" , "Error constructing Polygon: shell is empty but holes are not " );
457+ assertInvalidReason ("POLYGON ((0 0, 1 1, 0 1, 1 0, 0 0))" , "Self-intersection " );
458458 assertInvalidReason ("POLYGON ((0 0, 0 1, 0 1, 1 1, 1 0, 0 0), (2 2, 2 3, 3 3, 3 2, 2 2))" , "Hole lies outside shell" );
459459 assertInvalidReason ("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0), (2 2, 2 3, 3 3, 3 2, 2 2))" , "Hole lies outside shell" );
460460 assertInvalidReason ("POLYGON ((0 0, 0 1, 2 1, 1 1, 1 0, 0 0))" , "Ring Self-intersection" );
@@ -1372,6 +1372,124 @@ private void assertInvalidGeometryJson(String json)
13721372 assertInvalidFunction ("geometry_from_geojson('" + json + "')" , "Invalid GeoJSON:.*" );
13731373 }
13741374
1375+ @ Test
1376+ public void testDegeneratePolygons ()
1377+ {
1378+ // Single polygon with CCW orientation - should orient to CW
1379+ testDegeneratePolygonsFunc (
1380+ "POLYGON ((1 2, 3 4, 5 7, 1 2))" ,
1381+ "POLYGON ((1 2, 5 7, 3 4, 1 2))" );
1382+
1383+ // Single polygons with no area- should not reverse these
1384+ testDegeneratePolygonsFunc (
1385+ "POLYGON ((1 2, 5 6, 3 4, 1 2))" , "POLYGON ((1 2, 5 6, 3 4, 1 2))" );
1386+ testDegeneratePolygonsFunc (
1387+ "POLYGON ((1 2, 3 4, 5 6, 1 2))" , "POLYGON ((1 2, 3 4, 5 6, 1 2))" );
1388+
1389+ // Single polygons with interior rings- should canonicalize so any shells have
1390+ // CW orientation and holes have CCW orientation.
1391+ testDegeneratePolygonsFunc (
1392+ "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 3 7, 7 7, 7 3, 3 3))" ,
1393+ "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3))" );
1394+ testDegeneratePolygonsFunc (
1395+ "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3))" ,
1396+ "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3))" );
1397+
1398+ // Multipolygons where polygons after the first are CCW for shell or CW for
1399+ // hole. These should be correctly oriented after serde.
1400+
1401+ // First polygon has CW shell and CCW hole, second polygon has CCW
1402+ // shell and CCW hole -> second polygon shell should be reoriented
1403+ testDegeneratePolygonsFunc (
1404+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 20 0, 20 20, 0 20, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" ,
1405+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" );
1406+
1407+ // First polygon has CW shell and CW hole, second polygon has CCW
1408+ // shell and CCW hole -> first polygon hole and second polygon shell should be
1409+ // reoriented
1410+ testDegeneratePolygonsFunc (
1411+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 3 7, 7 7, 7 3, 3 3)), ((0 0, 20 0, 20 20, 0 20, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" ,
1412+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" );
1413+
1414+ // First polygon has CCW shell and CW hole, second polygon has CCW
1415+ // shell and CW hole -> both polygons should have shell and hole reoriented
1416+ testDegeneratePolygonsFunc (
1417+ "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (3 3, 3 7, 7 7, 7 3, 3 3)), ((0 0, 20 0, 20 20, 0 20, 0 0), (6 6, 6 14, 14 14, 14 6, 6 6)))" ,
1418+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" );
1419+
1420+ // First polygon has CCW shell and CCW hole, second polygon has CCW
1421+ // shell and CCW hole -> both polygons should have shells reoriented
1422+ testDegeneratePolygonsFunc (
1423+ "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 20 0, 20 20, 0 20, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" ,
1424+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" );
1425+
1426+ // First polygon has CW shell and CW hole, second polygon has CW
1427+ // shell and CW hole -> both polygons should have holes reoriented
1428+ testDegeneratePolygonsFunc (
1429+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 3 7, 7 7, 7 3, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 6 14, 14 14, 14 6, 6 6)))" ,
1430+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" );
1431+
1432+
1433+ // MultiPolygons with zero-area rings. These need to fail because our
1434+ // serialization format holds MultiPolygons as single vectors that rely on
1435+ // orientation for determining shell start points.
1436+
1437+ // Second polygon is zero area
1438+ testDegeneratePolygonsFuncInvalid ("MULTIPOLYGON (((1 1, 2 1, 2 2, 1 1)), ((1 1, 2 2, 3 3, 2 2, 1 1)))" );
1439+
1440+ // Single polygon with zero area
1441+ testDegeneratePolygonsFuncInvalid (
1442+ "MULTIPOLYGON (((5 10, 25 30, 15 20, 5 10)))" );
1443+ testDegeneratePolygonsFuncInvalid (
1444+ "MULTIPOLYGON (((1 1, 1 2, 1 3, 1 1)))" );
1445+
1446+ // First polygon has CW shell and CCW hole, second polygon has CW shell and
1447+ // zero-area hole
1448+ testDegeneratePolygonsFuncInvalid (
1449+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 9 9, 14 14, 6 6)))" );
1450+
1451+ // First polygon has CW shell and CCW hole, second polygon has zero-area shell
1452+ // and CCW hole
1453+ testDegeneratePolygonsFuncInvalid (
1454+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 0 20, 0 10, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" );
1455+
1456+ // First polygon has CW shell and CCW hole, second polygon has CW shell
1457+ // and zero-area hole
1458+ testDegeneratePolygonsFuncInvalid (
1459+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 9 9, 14 14, 9 9, 6 6)))" );
1460+
1461+
1462+ // First polygon has CW shell and CCW hole, second polygon has zero-area shell
1463+ // and zero-area hole
1464+ testDegeneratePolygonsFuncInvalid (
1465+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 0 20, 0 10, 0 0), (6 6, 9 9, 14 14, 6 6)))" );
1466+
1467+ // First polygon has zero-area shell and CCW hole, second polygon has CW shell
1468+ // and CCW hole
1469+ testDegeneratePolygonsFuncInvalid (
1470+ "MULTIPOLYGON (((0 0, 0 10, 0 15, 0 0), (3 3, 7 3, 7 7, 3 7, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6))))" );
1471+
1472+ // First polygon has CW shell and zero-area hole, second polygon has CW shell
1473+ // and CCW hole
1474+ testDegeneratePolygonsFuncInvalid (
1475+ "MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0), (3 3, 7 3, 9 3, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" );
1476+
1477+ // First polygon has zero-area shell and zero-area hole, second polygon has CW
1478+ // shell and CCW hole
1479+ testDegeneratePolygonsFuncInvalid (
1480+ "MULTIPOLYGON (((0 0, 0 10, 0 5, 0 10, 0 0), (3 3, 7 3, 9 3, 3 3)), ((0 0, 0 20, 20 20, 20 0, 0 0), (6 6, 14 6, 14 14, 6 14, 6 6)))" );
1481+ }
1482+
1483+ private void testDegeneratePolygonsFunc (String wkt , String expected )
1484+ {
1485+ assertFunction (format ("ST_ASText(ST_GeometryFromText('%s'))" , wkt ), VARCHAR , expected );
1486+ }
1487+
1488+ private void testDegeneratePolygonsFuncInvalid (String wkt )
1489+ {
1490+ assertInvalidFunction (format ("ST_ASText(ST_GeometryFromText('%s'))" , wkt ), "Input MultiPolygon contains one or more zero-area rings." );
1491+ }
1492+
13751493 @ Test
13761494 public void testGooglePolylineDecode ()
13771495 {
0 commit comments