Add CurvePolygon geometry type interface
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/CurvePolygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/CurvePolygon.java
new file mode 100644
index 0000000..a026e5e
--- /dev/null
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/CurvePolygon.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.geometries;
+
+import java.util.List;
+import static org.opengis.annotation.Specification.ISO_19107;
+import org.opengis.annotation.UML;
+import org.opengis.geometry.coordinate.GriddedSurface;
+import org.apache.sis.geometries.privy.AbstractGeometry;
+
+
+/**
+ * A curve polygon is a surface where each ring is a closed line string, circular string, or compound curve.
+ * The first ring is the exterior boundary and, all other rings are interior boundaries.
+ *
+ * @todo is Polygon a subclass of CurvePolygon ?
+ * ISO-19107 use the name Polygon for CurvePolygon
+ * OGC Features and Geometries JSON separates them
+ * In practice most geometry library have only polygon with straight lines
+ *
+ * @author Johann Sorel (Geomatys)
+ * @see https://docs.ogc.org/DRAFTS/21-045r1.html#curve_polygon
+ */
+@UML(identifier="Polygon", specification=ISO_19107) // section 8.1.2
+public interface CurvePolygon extends Surface {
+
+    public static final String TYPE = "CURVEPOLYGON";
+
+    @Override
+    public default String getGeometryType() {
+        return TYPE;
+    }
+
+    @Override
+    public default AttributesType getAttributesType() {
+        return getExteriorRing().getAttributesType();
+    }
+
+    @UML(identifier="rings", specification=ISO_19107) // section 8.1 figure 28
+    List<Curve> getInteriorRings();
+
+    /**
+     * Returns the exterior ring of this Polygon.
+     *
+     * @see OGC Simple Feature Access 1.2.1 - 6.1.11.2
+     * @return exterior ring of this Polygon.
+     */
+    @UML(identifier="exteriorRing", specification=ISO_19107) // section 8.1 figure 28
+    Curve getExteriorRing();
+
+    /**
+     * Returns the number of interior rings in this Polygon.
+     *
+     * @see OGC Simple Feature Access 1.2.1 - 6.1.11.2
+     * @return number of interior rings in this Polygon.
+     */
+    @UML(identifier="numInteriorRing", specification=ISO_19107) // section 8.1 figure 28
+    default int getNumInteriorRing() {
+        return getInteriorRings().size();
+    }
+
+    /**
+     * Returns the Nth interior ring for this Polygon as a LineString.
+     *
+     * @see OGC Simple Feature Access 1.2.1 - 6.1.11.2
+     * @param n ring index
+     * @return interior ring for this Polygon.
+     */
+    @UML(identifier="interiorRingN", specification=ISO_19107) // section 8.1 figure 28
+    default Curve getInteriorRingN(int n) {
+        return getInteriorRings().get(n);
+    }
+
+    @UML(identifier="spanningSurface", specification=ISO_19107) // section 8.1.2.3
+    default GriddedSurface getSpanningSurface() {
+        //TODO
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    default String asText() {
+        final StringBuilder sb = new StringBuilder("POLYGON ((");
+        AbstractGeometry.toText(sb,  getExteriorRing().asLine(null, null).getPoints());
+        sb.append(')');
+        for (int i = 0, n = getNumInteriorRing(); i < n; i++) {
+            if (i != 0) sb.append(',');
+            sb.append('(');
+            AbstractGeometry.toText(sb, getInteriorRingN(i).asLine(null, null).getPoints());
+            sb.append(')');
+        }
+        sb.append(')');
+        return sb.toString();
+    }
+
+}
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java
index 4585ced..628413d 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java
@@ -130,6 +130,7 @@
             case CLOTHOID : return Clothoid.class;
             case COMPOUNDCURVE : return CompoundCurve.class;
             case CURVE : return Curve.class;
+            case CURVEPOLYGON : return CurvePolygon.class;
             case GEOMETRY : return Geometry.class;
             case GEOMETRYCOLLECTION : return GeometryCollection.class;
             case LINESTRING : return LineString.class;
@@ -147,7 +148,6 @@
             //todo
             case BREPSOLID :
             case COMPOUNDSURFACE :
-            case CURVEPOLYGON :
             case ELLIPTICALCURVE :
             case GEODESICSTRING :
             case NURBSCURVE :
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/MultiPolygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/MultiPolygon.java
index 8c39f09..dfadb5e 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/MultiPolygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/MultiPolygon.java
@@ -55,7 +55,7 @@
             for (int i = 0, n = polygon.getNumInteriorRing(); i < n; i++) {
                 if (i != 0) sb.append(',');
                 sb.append('(');
-                AbstractGeometry.toText(sb, polygon.getInteriorRingN(i).asLine(null, null).getPoints());
+                AbstractGeometry.toText(sb, polygon.getInteriorRingN(i).getPoints());
                 sb.append(')');
             }
             sb.append(')');
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Polygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Polygon.java
index fa1f1cf..7ae7832 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Polygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Polygon.java
@@ -42,7 +42,6 @@
  * f) The exterior of a Polygon with 1 or more holes is not connected. Each hole defines a connected component of the exterior.
  *
  * @author Johann Sorel (Geomatys)
- * @see https://docs.ogc.org/DRAFTS/21-045r1.html#curve_polygon
  */
 @UML(identifier="Polygon", specification=ISO_19107) // section 8.1.2
 public interface Polygon extends Surface {
@@ -60,7 +59,7 @@
     }
 
     @UML(identifier="rings", specification=ISO_19107) // section 8.1 figure 28
-    List<Curve> getInteriorRings();
+    List<LinearRing> getInteriorRings();
 
     /**
      * Returns the exterior ring of this Polygon.
@@ -69,7 +68,7 @@
      * @return exterior ring of this Polygon.
      */
     @UML(identifier="exteriorRing", specification=ISO_19107) // section 8.1 figure 28
-    Curve getExteriorRing();
+    LinearRing getExteriorRing();
 
     /**
      * Returns the number of interior rings in this Polygon.
@@ -90,7 +89,7 @@
      * @return interior ring for this Polygon.
      */
     @UML(identifier="interiorRingN", specification=ISO_19107) // section 8.1 figure 28
-    default Curve getInteriorRingN(int n) {
+    default LinearRing getInteriorRingN(int n) {
         return getInteriorRings().get(n);
     }
 
@@ -108,7 +107,7 @@
         for (int i = 0, n = getNumInteriorRing(); i < n; i++) {
             if (i != 0) sb.append(',');
             sb.append('(');
-            AbstractGeometry.toText(sb, getInteriorRingN(i).asLine(null, null).getPoints());
+            AbstractGeometry.toText(sb, getInteriorRingN(i).getPoints());
             sb.append(')');
         }
         sb.append(')');
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Triangle.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Triangle.java
index fabf57e..a5345f6 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Triangle.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Triangle.java
@@ -56,7 +56,7 @@
     LinearRing getExteriorRing();
 
     @Override
-    default List<Curve> getInteriorRings() {
+    default List<LinearRing> getInteriorRings() {
         return Collections.emptyList();
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/privy/DefaultPolygon.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/privy/DefaultPolygon.java
index 0d875e2..b16e83e 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/privy/DefaultPolygon.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/privy/DefaultPolygon.java
@@ -34,8 +34,8 @@
  */
 public class DefaultPolygon extends AbstractGeometry implements Polygon {
 
-    protected final Curve exterior;
-    protected final List<Curve> interiors;
+    protected final LinearRing exterior;
+    protected final List<LinearRing> interiors;
 
     public DefaultPolygon(LinearRing exterior) {
         this(exterior, null);
@@ -72,12 +72,12 @@
     }
 
     @Override
-    public Curve getExteriorRing() {
+    public LinearRing getExteriorRing() {
         return exterior;
     }
 
     @Override
-    public List<Curve> getInteriorRings() {
+    public List<LinearRing> getInteriorRings() {
         return interiors;
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/processor/spatialedition/Transform.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/processor/spatialedition/Transform.java
index 43bc6e2..521e92f 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/processor/spatialedition/Transform.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/processor/spatialedition/Transform.java
@@ -119,14 +119,14 @@
         public void process(org.apache.sis.geometries.operation.spatialedition.Transform operation) throws OperationException {
             final org.apache.sis.geometries.Polygon p = (org.apache.sis.geometries.Polygon) operation.geometry;
 
-            Curve exterior = (Curve) GeometryOperations.SpatialEdition.transform(p.getExteriorRing(), operation.crs, operation.transform);
+            org.apache.sis.geometries.LinearRing exterior = (org.apache.sis.geometries.LinearRing) GeometryOperations.SpatialEdition.transform(p.getExteriorRing(), operation.crs, operation.transform);
 
-            final List<Curve> interiors = new ArrayList<>(p.getInteriorRings());
+            final List<org.apache.sis.geometries.LinearRing> interiors = new ArrayList<>(p.getInteriorRings());
             for (int i = 0, n = interiors.size(); i < n; i++) {
-                interiors.set(i, (Curve) GeometryOperations.SpatialEdition.transform(interiors.get(i), operation.crs, operation.transform));
+                interiors.set(i, (org.apache.sis.geometries.LinearRing) GeometryOperations.SpatialEdition.transform(interiors.get(i), operation.crs, operation.transform));
             }
 
-            operation.result = GeometryFactory.createPolygon((org.apache.sis.geometries.LinearRing) exterior, (List) interiors);
+            operation.result = GeometryFactory.createPolygon(exterior, interiors);
         }
     }
 
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/processor/spatialrelations2d/Contains.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/processor/spatialrelations2d/Contains.java
index e5427ba..b0cac29 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/processor/spatialrelations2d/Contains.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/processor/spatialrelations2d/Contains.java
@@ -110,7 +110,7 @@
 
 
             { //check exterior
-                final TupleArray coords = asLineString(polygon.getExteriorRing()).getPoints().getAttributeArray(AttributesType.ATT_POSITION);
+                final TupleArray coords = polygon.getExteriorRing().getPoints().getAttributeArray(AttributesType.ATT_POSITION);
                 if (!contains(coords, candidate.getPosition())) {
                     //point is outside the exterior ring
                     operation.result = false;
@@ -120,7 +120,7 @@
 
             { //check holes
                 for (int i = 0, n = polygon.getNumInteriorRing(); i < n; i++) {
-                    final LineString hole = asLineString(polygon.getInteriorRingN(i));
+                    final LineString hole = polygon.getInteriorRingN(i);
                     final TupleArray coords = hole.getPoints().getAttributeArray(AttributesType.ATT_POSITION);
                     if (contains(coords, candidate.getPosition())) {
                         //point is within a hole
@@ -135,11 +135,4 @@
         }
     }
 
-    private static LineString asLineString(Curve curve) {
-        if (curve instanceof LineString ls) {
-            return ls;
-        }
-        throw new OperationException("Curve type not supported");
-    }
-
 }
diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/triangulate/EarClipping.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/triangulate/EarClipping.java
index 9170630..01a6138 100644
--- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/triangulate/EarClipping.java
+++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/triangulate/EarClipping.java
@@ -110,12 +110,12 @@
 
         final SimplePolygon part = new SimplePolygon();
         //copy collection to avoid modifications
-        part.outter = (LineString) ((Polygon)geometry).getExteriorRing();
+        part.outter = geometry.getExteriorRing();
 
-        final int nbHole = ((Polygon)geometry).getNumInteriorRing();
+        final int nbHole = geometry.getNumInteriorRing();
 
         for(int i=0;i<nbHole;i++){
-            final LineString inner = (LineString) ((Polygon)geometry).getInteriorRingN(i);
+            final LineString inner = geometry.getInteriorRingN(i);
             part.inners.add(inner);
         }
 
@@ -157,7 +157,7 @@
             //we must find the minimum x coordinate in the inner loop
             final List<Tuple> loop = part.inners.get(i).getPoints().getAttributeArray(AttributesType.ATT_POSITION).stream(false).toList();
             int index = 0;
-            Tuple min = (Tuple) loop.get(index);
+            Tuple min = loop.get(index);
             for(int k=1,p=loop.size();k<p;k++){
                 Tuple candidate = (Tuple) loop.get(1);
                 if (candidate.get(0) < min.get(0)) {
@@ -192,9 +192,9 @@
         }
 
         //remove any neighor points overlaping
-        Tuple t = (Tuple) borderCoords.get(0);
+        Tuple t = borderCoords.get(0);
         for(int i=1,n=borderCoords.size();i<n;i++){
-            Tuple candidate = (Tuple) borderCoords.get(i);
+            Tuple candidate = borderCoords.get(i);
             if(candidate.equals(t)){
                 borderCoords.remove(i);
                 i--;
@@ -210,7 +210,7 @@
 
         nbCoords = borderCoords.size();
         coordType = new int[nbCoords];
-        coords = borderCoords.toArray(new Tuple[0]);
+        coords = borderCoords.toArray(Tuple[]::new);
 
         //flip coordinates if not clockwise
         if(!clockwise){