This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 0a68f08237 feat(Geometry): add adapter from AWT Shape to SIS Geometry
0a68f08237 is described below

commit 0a68f08237c2a00ae77b3b7a9e6e0b9c1154088b
Author: jsorel <[email protected]>
AuthorDate: Wed Aug 26 16:56:48 2026 +0200

    feat(Geometry): add adapter from AWT Shape to SIS Geometry
---
 .../main/org/apache/sis/geometries/Empty.java      |   8 +
 .../main/org/apache/sis/geometries/Geometries.java |  20 +-
 .../org/apache/sis/geometries/GeometryFactory.java |  17 ++
 .../apache/sis/geometries/PolyhedralSurface.java   |  11 +
 .../org/apache/sis/geometries/PreparedTIN.java     |   2 +
 .../main/org/apache/sis/geometries/Sphere.java     |   5 +
 .../main/org/apache/sis/geometries/Surface.java    |   4 +-
 .../apache/sis/geometries/adapter/JTSAdapter.java  |   6 +-
 .../sis/geometries/adapter/ShapeConverter.java     | 331 +++++++++++++++++++++
 .../geometries/internal/shared/DefaultEmpty.java   |  59 ++++
 .../geometries/internal/shared/DefaultPolygon.java |   7 +
 .../internal/shared/DefaultRawMultiPoint.java      |  58 ++++
 .../geometries/operation/GeometryProcessor.java    |  60 +++-
 .../sis/geometries/adapter/ShapeConverterTest.java | 231 ++++++++++++++
 14 files changed, 797 insertions(+), 22 deletions(-)

diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java
index 99f92b0be2..997fdbf91d 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Empty.java
@@ -27,8 +27,16 @@ import org.opengis.annotation.UML;
 @UML(identifier="Empty", specification=ISO_19107) // section 6.4.10
 public interface Empty extends Geometry{
 
+    public static final String TYPE = "EMPTY";
+
     @Override
     default boolean isEmpty() {
         return true;
     }
+
+    @Override
+    public default String getGeometryType() {
+        return TYPE;
+    }
+
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java
index 30fc748a1b..14b3b34c7f 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java
@@ -28,11 +28,13 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.CoordinateSequence;
 import javax.measure.Unit;
 import org.apache.sis.geometries.adapter.JTSAdapter;
 import org.apache.sis.geometries.adapter.ShapeAdapter;
+import org.apache.sis.geometries.adapter.ShapeConverter;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.IdentifiedObject;
 import static org.opengis.referencing.IdentifiedObject.ALIAS_KEY;
@@ -76,6 +78,8 @@ import org.apache.sis.util.SimpleInternationalString;
  */
 public final class Geometries {
 
+    private static final org.locationtech.jts.geom.GeometryFactory JTS_FACTORY 
= new org.locationtech.jts.geom.GeometryFactory();
+
     private static final CoordinateReferenceSystem UNDEFINED_CRS_1D = 
createUndefined(1);
     private static final CoordinateReferenceSystem UNDEFINED_CRS_2D = 
createUndefined(2);
     private static final CoordinateReferenceSystem UNDEFINED_CRS_3D = 
createUndefined(3);
@@ -722,10 +726,11 @@ public final class Geometries {
      * View a geometry as a JTS geometry.
      *
      * @param copy if true create a copy of the point sequence, otherwise 
create a view
+     * @param gf JTS factory or null for default
      * @return JTS equivalent
      */
     public static org.locationtech.jts.geom.Geometry asJTS(Geometry geom, 
boolean copy, org.locationtech.jts.geom.GeometryFactory gf) {
-        return JTSAdapter.asJTS(geom, copy, gf);
+        return JTSAdapter.asJTS(geom, copy, gf == null ? JTS_FACTORY : gf);
     }
 
     /**
@@ -738,4 +743,17 @@ public final class Geometries {
         // Null value check in the invoked constructor.
         return new ShapeAdapter(geometry);
     }
+
+    /**
+     * Converts a Java2D shape to a SIS geometry. If the given shape is a view 
created by {@link #asShape(Geometry)},
+     * then the original geometry is returned. Otherwise a new geometry is 
created with a copy (not a view) of the
+     * shape coordinates.
+     *
+     * @param  shape     the Java2D shape to convert. Cannot be {@code null}.
+     * @param  flatness  the maximum distance that line segments are allowed 
to deviate from curves.
+     * @return SIS geometry with shape coordinates. Never null but can be 
empty.
+     */
+    public static Geometry fromAWT(final Shape shape, final double flatness) {
+        return ShapeConverter.create(Objects.requireNonNull(shape), flatness);
+    }
 }
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 5bdc74bfcf..73743486aa 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
@@ -28,6 +28,7 @@ import org.apache.sis.geometries.math.SampleSystem;
 import org.apache.sis.geometries.math.NDArrays;
 import org.apache.sis.geometries.math.Array;
 import org.apache.sis.geometries.internal.shared.ArraySequence;
+import org.apache.sis.geometries.internal.shared.DefaultEmpty;
 import org.apache.sis.geometries.internal.shared.DefaultGeometryCollection;
 import org.apache.sis.geometries.internal.shared.DefaultLineString;
 import org.apache.sis.geometries.internal.shared.DefaultLinearRing;
@@ -37,7 +38,9 @@ import 
org.apache.sis.geometries.internal.shared.DefaultMultiPolygon;
 import org.apache.sis.geometries.internal.shared.DefaultMultiSurface;
 import org.apache.sis.geometries.internal.shared.DefaultPoint;
 import org.apache.sis.geometries.internal.shared.DefaultPolygon;
+import org.apache.sis.geometries.internal.shared.DefaultRawMultiPoint;
 import org.apache.sis.geometries.internal.shared.DefaultTriangle;
+import org.apache.sis.geometries.math.DataType;
 import org.apache.sis.geometries.spirals.Clothoid;
 import org.apache.sis.geometry.wrapper.Capability;
 import org.apache.sis.geometry.wrapper.Dimensions;
@@ -58,6 +61,16 @@ public final class GeometryFactory extends 
org.apache.sis.geometry.wrapper.Geome
         super(GeometryLibrary.SIS, Geometry.class, Point.class);
     }
 
+    public static Empty createEmpty(CoordinateReferenceSystem crs) {
+        final AttributesType.Template attType = new AttributesType.Template();
+        attType.addOrReplaceAttribute(AttributesType.ATT_POSITION, 
SampleSystem.of(crs), DataType.DOUBLE);
+        return new DefaultEmpty(attType);
+    }
+
+    public static Empty createEmpty(AttributesType attType) {
+        return new DefaultEmpty(attType);
+    }
+
     public static Point createPoint(CoordinateReferenceSystem crs) {
         return new DefaultPoint(crs);
     }
@@ -94,6 +107,10 @@ public final class GeometryFactory extends 
org.apache.sis.geometry.wrapper.Geome
         return new DefaultMultiPoint(sequence);
     }
 
+    public static MultiPoint createMultiPoint(Point ... geometries) {
+        return new DefaultRawMultiPoint(geometries);
+    }
+
     public static MultiLineString createMultiLineString(LineString ... 
geometries) {
         return new DefaultMultiLineString(geometries);
     }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/PolyhedralSurface.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/PolyhedralSurface.java
index 170acdedc3..4c3b9adc0f 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/PolyhedralSurface.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/PolyhedralSurface.java
@@ -16,8 +16,12 @@
  */
 package org.apache.sis.geometries;
 
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
+import org.opengis.referencing.operation.TransformException;
 
 
 /**
@@ -73,6 +77,13 @@ public interface PolyhedralSurface<T extends Polygon> 
extends /*GeometryCollecti
      */
     T getPatchN(int n);
 
+    @Override
+    public default double getArea() {
+        try (Stream<Surface> stream = IntStream.range(0, 
getNumPatches()).mapToObj(this::getPatchN)) {
+            return stream.collect(Collectors.summingDouble(Surface::getArea));
+        }
+    }
+
     /**
      * Returns the collection of polygons in this surface that bounds the 
given polygon “p” for any polygon “p” in the surface.
      *
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/PreparedTIN.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/PreparedTIN.java
index 2ed3948a97..f213314df9 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/PreparedTIN.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/PreparedTIN.java
@@ -21,6 +21,8 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.ToDoubleFunction;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.locationtech.jts.index.quadtree.Quadtree;
 import org.opengis.coverage.CannotEvaluateException;
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Sphere.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Sphere.java
index e480e8998e..14f08876f7 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Sphere.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Sphere.java
@@ -87,6 +87,11 @@ public final class Sphere extends AbstractGeometry 
implements ParametricCurveSur
         return false;
     }
 
+    @Override
+    public double getArea() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
     @Override
     public void setCoordinateReferenceSystem(CoordinateReferenceSystem cs) 
throws IllegalArgumentException {
         if (cs.getCoordinateSystem().getDimension() != 
getCoordinateReferenceSystem().getCoordinateSystem().getDimension()) {
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java
index 11162926a9..aec63bdf93 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java
@@ -60,9 +60,7 @@ public interface Surface extends Orientable {
      * @return area of the surface.
      */
     @UML(identifier="area", specification=ISO_19107) // section 6.4.25.7
-    default double getArea() {
-        throw new UnsupportedOperationException();
-    }
+    double getArea();
 
     /**
      * The mathematical centroid for this Surface as a Point.
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/JTSAdapter.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/JTSAdapter.java
index 78e2462072..bed13ebfc0 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/JTSAdapter.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/JTSAdapter.java
@@ -88,11 +88,11 @@ public final class JTSAdapter {
             return GeometryFactory.createPolygon(exterior, interiors);
 
         } else if (jts instanceof org.locationtech.jts.geom.MultiPolygon cdt) {
-            final Surface[] geoms = new Surface[cdt.getNumGeometries()];
+            final Polygon[] geoms = new Polygon[cdt.getNumGeometries()];
             for (int i = 0; i < geoms.length; i++) {
-                geoms[i] = (Surface) fromJTS(cdt.getGeometryN(i), crs, copy);
+                geoms[i] = (Polygon) fromJTS(cdt.getGeometryN(i), crs, copy);
             }
-            return GeometryFactory.createMultiSurface(geoms);
+            return GeometryFactory.createMultiPolygon(geoms);
 
         } else if (jts instanceof org.locationtech.jts.geom.GeometryCollection 
cdt) {
             final Geometry[] geoms = new Geometry[cdt.getNumGeometries()];
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/ShapeConverter.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/ShapeConverter.java
new file mode 100644
index 0000000000..7546d296a1
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/ShapeConverter.java
@@ -0,0 +1,331 @@
+/*
+ * 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.adapter;
+
+import java.awt.Shape;
+import java.util.List;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.awt.geom.PathIterator;
+import java.awt.geom.IllegalPathStateException;
+import org.apache.sis.geometries.Geometries;
+import org.apache.sis.geometries.Geometry;
+import org.apache.sis.geometries.GeometryFactory;
+import org.apache.sis.geometries.LineString;
+import org.apache.sis.geometries.Point;
+import org.apache.sis.geometries.PointSequence;
+import org.apache.sis.geometries.Surface;
+import org.apache.sis.geometries.internal.shared.ArraySequence;
+import org.apache.sis.geometries.math.Array;
+import org.apache.sis.geometries.math.NDArrays;
+import org.apache.sis.geometries.math.SampleSystem;
+import org.apache.sis.referencing.internal.shared.AbstractShape;
+
+
+/**
+ * Converts a Java2D {@link Shape} to a SIS {@link Geometry}.
+ * Two subclasses exist depending on whether the geometries will store
+ * coordinates as {@code float} or {@code double} floating point numbers.
+ *
+ * @author  Johann Sorel (Puzzle-GIS, Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public abstract class ShapeConverter {
+
+    private static final int DIMENSION = 2;
+
+    /**
+     * Initial number of coordinate values that the buffer can hold.
+     * The buffer capacity will be expanded as needed.
+     */
+    private static final int INITIAL_CAPACITY = 64;
+
+    /**
+     * Bit mask of the kind of geometric objects created.
+     * Used for detecting if all objects are of the same type.
+     *
+     * @see #geometryType
+     */
+    private static final int POINT = 1, LINESTRING = 2, POLYGON = 4;
+
+    /**
+     * All geometries that are component of a multi-geometries.
+     * The above masks tell if the geometry can be built as a multi-line 
strings or multi-points.
+     */
+    private final List<Geometry> geometries;
+
+    /**
+     * Iterator over the coordinates of the Java2D shape to convert to a SIS 
geometry.
+     */
+    protected final PathIterator iterator;
+
+    /**
+     * Number of values in the {@code float[]} or {@code double[]} array 
stored by sub-class.
+     */
+    protected int length;
+
+    /**
+     * Bitmask combination of the type of all geometries built.
+     * This is a combination of {@link #POINT}, {@link #LINESTRING} and/or 
{@link #POLYGON}.
+     */
+    private int geometryType;
+
+    /**
+     * Creates a new converter from Java2D shape to SIS geometry.
+     *
+     * @param  iterator  iterator over the coordinates of the Java2D shape to 
convert to a SIS geometry.
+     */
+    ShapeConverter(final PathIterator iterator) {
+        this.iterator   = iterator;
+        this.geometries = new ArrayList<>();
+    }
+
+    /**
+     * Converts a Java2D Shape to a SIS geometry.
+     * Coordinates are copies; this is not a view.
+     *
+     * @param  shape     the Java2D shape to convert. Cannot be {@code null}.
+     * @param  flatness  the maximum distance that line segments are allowed 
to deviate from curves.
+     * @return SIS geometry with shape coordinates. Never null but can be 
empty.
+     */
+    public static Geometry create(final Shape shape, final double flatness) {
+        if (shape instanceof ShapeAdapter) {
+            return ((ShapeAdapter) shape).geometry;
+        }
+        final PathIterator iterator = shape.getPathIterator(null, flatness);
+        final ShapeConverter converter;
+        if (AbstractShape.isFloat(shape)) {
+            converter = new ShapeConverter.Float(iterator);
+        } else {
+            converter = new ShapeConverter.Double(iterator);
+        }
+        return converter.build();
+    }
+
+    /**
+     * A converter of Java2D {@link Shape} to a SIS {@link Geometry}
+     * storing coordinates as {@code double} values.
+     */
+    private static final class Double extends ShapeConverter {
+        /** A temporary array for the transfer of coordinate values. */
+        private final double[] vertex;
+
+        /** Coordinate of current geometry. The number of valid values is 
{@link #length}. */
+        private double[] buffer;
+
+        /** Creates a new converter for the given path iterator. */
+        Double(final PathIterator iterator) {
+            super(iterator);
+            vertex = new double[6];
+            buffer = new double[INITIAL_CAPACITY];
+        }
+
+        /** Delegates to {@link PathIterator#currentSegment(double[])}. */
+        @Override int currentSegment() {
+            return iterator.currentSegment(vertex);
+        }
+
+        /** Stores the single point obtained by the last call to {@link 
#currentSegment()}. */
+        @Override void addPoint() {
+            addPoint(vertex);
+        }
+
+        /** Implementation of {@link #addPoint()} shared with {@link 
#toSequence(boolean)}. */
+        private void addPoint(final double[] source) {
+            if (length >= buffer.length) {
+                buffer = Arrays.copyOf(buffer, length * 2);
+            }
+            System.arraycopy(source, 0, buffer, length, DIMENSION);
+            length += DIMENSION;
+        }
+
+        /** Returns a copy of current coordinate values as a SIS coordinate 
sequence. */
+        @Override PointSequence toSequence(final boolean close) {
+            if (close && !Arrays.equals(buffer, 0, 2, buffer, length - 2, 
length)) {
+                addPoint(buffer);
+            }
+
+            final Array array = NDArrays.of(SampleSystem.cartesian(2), 
Arrays.copyOf(buffer, length));
+            return new ArraySequence(array);
+        }
+    }
+
+    /**
+     * A converter of Java2D {@link Shape} to a SIS {@link Geometry}
+     * storing coordinates as {@code float} values.
+     */
+    private static final class Float extends ShapeConverter {
+        /** A temporary array for the transfer of coordinate values. */
+        private final float[] vertex;
+
+        /** Coordinate of current geometry. The number of valid values is 
{@link #length}. */
+        private float[] buffer;
+
+        /** Creates a new converter for the given path iterator. */
+        Float(final PathIterator iterator) {
+            super(iterator);
+            vertex = new float[6];
+            buffer = new float[INITIAL_CAPACITY];
+        }
+
+        /** Delegates to {@link PathIterator#currentSegment(float[])}. */
+        @Override int currentSegment() {
+            return iterator.currentSegment(vertex);
+        }
+
+        /** Stores the single point obtained by the last call to {@link 
#currentSegment()}. */
+        @Override void addPoint() {
+            addPoint(vertex);
+        }
+
+        /** Implementation of {@link #addPoint()} shared with {@link 
#toSequence(boolean)}. */
+        private void addPoint(final float[] source) {
+            if (length >= buffer.length) {
+                buffer = Arrays.copyOf(buffer, length * 2);
+            }
+            System.arraycopy(source, 0, buffer, length, DIMENSION);
+            length += DIMENSION;
+        }
+
+        /** Returns a copy of current coordinate values as a SIS coordinate 
sequence. */
+        @Override PointSequence toSequence(final boolean close) {
+            if (close && !Arrays.equals(buffer, 0, 2, buffer, length - 2, 
length)) {
+                addPoint(buffer);
+            }
+            final Array array = NDArrays.of(SampleSystem.cartesian(2), 
Arrays.copyOf(buffer, length));
+            return new ArraySequence(array);
+        }
+    }
+
+    /**
+     * Returns the coordinates and type of the current path segment in the 
iteration.
+     * This method delegate to one of the two {@code 
PathIterator.currentSegment(…)}
+     * methods, depending on the precision of floating-point values.
+     */
+    abstract int currentSegment();
+
+    /**
+     * Stores the single point obtained by the last call to {@link 
#currentSegment()}.
+     * As a consequence, {@link #length} is increased by {@value #DIMENSION}.
+     */
+    abstract void addPoint();
+
+    /**
+     * Returns a copy of current coordinate values as a SIS coordinate 
sequence.
+     * The number of values to copy in a new array is {@link #length}.
+     *
+     * @param  close  whether to ensure that the first point is repeated as 
the last point.
+     * @return a SIS coordinate sequence containing a copy of current 
coordinate values.
+     */
+    abstract PointSequence toSequence(boolean close);
+
+    /**
+     * Iterates over all coordinates given by the {@link #iterator} and stores 
them in a SIS geometry.
+     * The path shall contain only straight lines. Curves are not supported 
yet.
+     * The geometry will be constrained to two-dimensional coordinate tuples.
+     */
+    private Geometry build() {
+        while (!iterator.isDone()) {
+            switch (currentSegment()) {
+                case PathIterator.SEG_MOVETO: {
+                    flush(false);
+                    addPoint();
+                    break;
+                }
+                case PathIterator.SEG_LINETO: {
+                    if (length == 0) {
+                        throw new IllegalPathStateException("LINETO without 
previous MOVETO.");
+                    }
+                    addPoint();
+                    break;
+                }
+                case PathIterator.SEG_CLOSE: {
+                    flush(true);
+                    break;
+                }
+                default: {
+                    throw new IllegalPathStateException("Must contain only 
flat segments.");
+                }
+            }
+            iterator.next();
+        }
+        flush(false);
+        final int count = geometries.size();
+        if (count == 1) {
+            return geometries.get(0);
+        }
+
+        switch (geometryType) {
+            case 0:          return 
GeometryFactory.INSTANCE.createEmpty(Geometries.getUndefinedCRS(DIMENSION));
+            default:         return 
GeometryFactory.INSTANCE.createGeometryCollection(geometries.toArray(Geometry[]::new));
+            case POINT:      return 
GeometryFactory.INSTANCE.createMultiPoint(geometries.toArray(Point[]::new));
+            case LINESTRING: return 
GeometryFactory.INSTANCE.createMultiLineString(geometries.toArray(LineString[]::new));
+            case POLYGON:    break;
+        }
+        /*
+         * Java2D shapes and SIS geometries differ in their way to fill 
interior.
+         * Java2D fills the resulting contour based on visual winding rules.
+         * SIS has a system where outer shell and holes are clearly separated.
+         * We would need to draw contours as Java2D for computing SIS 
equivalent,
+         * but it would require a lot of work. In the meantime, the 
SymDifference
+         * operation is what behave the most like EVEN_ODD or NON_ZERO winding 
rules.
+         */
+        // Sort by area, bigger geometries are the outter rings.
+        geometries.sort((Geometry o1, Geometry o2) -> {
+                double area1 = (o1 instanceof Surface s) ? s.getArea() : 0.0;
+                double area2 = (o2 instanceof Surface s) ? s.getArea() : 0.0;
+                return java.lang.Double.compare(area2, area1);
+            });
+
+        Geometry result = geometries.get(0);
+        for (int i=1; i<count; i++) {
+            Geometry other = geometries.get(i);
+            if (result.intersects(other)) {
+                result = result.symDifference(other);   // Ring is a hole.
+            } else {
+                result = result.union(other);           // Ring is a separate 
polygon.
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Copies current coordinates in a new SIS geometry,
+     * then resets {@link #length} to 0 in preparation for the next geometry.
+     *
+     * @param  isRing  whether the geometry should be a closed polygon.
+     */
+    private void flush(final boolean isRing) {
+        if (length != 0) {
+            Geometry geometry;
+            if (length == DIMENSION) {
+                geometry = 
GeometryFactory.INSTANCE.createPoint(toSequence(false));
+                geometryType |= POINT;
+            } else {
+                if (isRing) {
+                    geometry = 
GeometryFactory.INSTANCE.createPolygon(GeometryFactory.INSTANCE.createLinearRing(toSequence(true)),
 null);
+                    geometryType |= POLYGON;
+                } else {
+                    geometry = 
GeometryFactory.INSTANCE.createLineString(toSequence(false));
+                    geometryType |= LINESTRING;
+                }
+            }
+            geometries.add(geometry);
+            length = 0;
+        }
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEmpty.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEmpty.java
new file mode 100644
index 0000000000..5308308ba3
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEmpty.java
@@ -0,0 +1,59 @@
+/*
+ * 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.internal.shared;
+
+import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.Empty;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class DefaultEmpty extends AbstractGeometry implements Empty {
+
+    private final AttributesType attType;
+
+    public DefaultEmpty(AttributesType attType) {
+        this.attType = attType;
+    }
+
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return 
attType.getAttributeSystem(AttributesType.ATT_POSITION).getCoordinateReferenceSystem();
+    }
+
+    @Override
+    public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) 
throws IllegalArgumentException {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public AttributesType getAttributesType() {
+        return attType;
+    }
+
+    @Override
+    public Envelope getEnvelope() {
+        final GeneralEnvelope env = new 
GeneralEnvelope(getCoordinateReferenceSystem());
+        env.setToNaN();
+        return env;
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolygon.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolygon.java
index b2cad90d70..9e7826fc0a 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolygon.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolygon.java
@@ -24,6 +24,7 @@ import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.Geometries;
 import org.apache.sis.geometries.LinearRing;
 import org.apache.sis.geometries.Polygon;
+import org.locationtech.jts.geom.GeometryFactory;
 
 
 /**
@@ -82,4 +83,10 @@ public class DefaultPolygon extends AbstractGeometry 
implements Polygon {
         return getExteriorRing().getEnvelope();
     }
 
+    @Override
+    public double getArea() {
+        //TODO : fallback on JTS until implemented
+        return Geometries.asJTS(this, false, new GeometryFactory()).getArea();
+    }
+
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultRawMultiPoint.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultRawMultiPoint.java
new file mode 100644
index 0000000000..ad9d28f720
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultRawMultiPoint.java
@@ -0,0 +1,58 @@
+/*
+ * 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.internal.shared;
+
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.geometries.MultiPoint;
+import org.apache.sis.geometries.Point;
+
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class DefaultRawMultiPoint extends AbstractGeometry implements 
MultiPoint<Point> {
+
+    private final Point[] geometries;
+
+    public DefaultRawMultiPoint(Point[] geometries) {
+        this.geometries = geometries;
+    }
+
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return geometries[0].getCoordinateReferenceSystem();
+    }
+
+    @Override
+    public void setCoordinateReferenceSystem(CoordinateReferenceSystem cs) 
throws IllegalArgumentException {
+        for (Point c : geometries) {
+            c.setCoordinateReferenceSystem(cs);
+        }
+    }
+
+    @Override
+    public int getNumGeometries() {
+        return geometries.length;
+    }
+
+    @Override
+    public Point getGeometryN(int n) {
+        return geometries[n];
+    }
+
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
index 360becd4f0..721d5c97c9 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
@@ -25,6 +25,7 @@ import java.util.function.Consumer;
 import java.util.function.Function;
 import javax.measure.quantity.Length;
 import org.apache.sis.geometries.AttributesType;
+import org.apache.sis.geometries.Geometries;
 import org.apache.sis.geometries.Geometry;
 import org.apache.sis.geometries.GeometryCollection;
 import org.apache.sis.geometries.LineString;
@@ -75,7 +76,9 @@ public final class GeometryProcessor {
      * but it should be near the resolution of the coordinates used.
      */
     public Geometry buffer(Geometry geom, double distance) throws 
OperationException {
-        throw new UnsupportedOperationException();
+
+        //TODO : fallback on JTS until implemented, this loss the attributes !
+        return Geometries.fromJTS(jts(geom).buffer(distance), true);
     }
 
     @UML(identifier="buffer", specification=ISO_19107) // section 6.4.4.24 and 
6.4.8.3
@@ -91,7 +94,9 @@ public final class GeometryProcessor {
      */
     //@UML(identifier="3DconvexHull", specification=ISO_19107) // section 6.4.9
     public Geometry convexHull(Geometry geom) throws OperationException {
-        throw new UnsupportedOperationException();
+
+        //TODO : fallback on JTS until implemented, this loss the attributes !
+        return Geometries.fromJTS(jts(geom).convexHull(), true);
     }
 
     /**
@@ -100,7 +105,9 @@ public final class GeometryProcessor {
     @UML(identifier="difference", specification=ISO_19107) // section 6.4.4.30 
and 6.4.8.5
     //@UML(identifier="3Ddifference", specification=ISO_19107) // section 6.4.9
     public Geometry difference(Geometry geom1, Geometry geom2) throws 
OperationException {
-        throw new UnsupportedOperationException();
+
+        //TODO : fallback on JTS until implemented, this loss the attributes !
+        return Geometries.fromJTS(jts(geom1).difference(jts(geom2)), true);
     }
 
     /**
@@ -140,7 +147,8 @@ public final class GeometryProcessor {
             }
         }
 
-        throw new UnsupportedOperationException();
+        //TODO : fallback on JTS until implemented, this loss the attributes !
+        return Geometries.fromJTS(jts(geom1).intersection(jts(geom2)), true);
     }
 
     /**
@@ -150,7 +158,9 @@ public final class GeometryProcessor {
     @UML(identifier="symDifference", specification=ISO_19107) // section 
6.4.4.30 and 6.4.8.6
     //@UML(identifier="3DsymDifference", specification=ISO_19107) // section 
6.4.9
     public Geometry symDifference(Geometry geom1, Geometry geom2) throws 
OperationException {
-        throw new UnsupportedOperationException();
+
+        //TODO : fallback on JTS until implemented, this loss the attributes !
+        return Geometries.fromJTS(jts(geom1).symDifference(jts(geom2)), true);
     }
 
     /**
@@ -159,7 +169,9 @@ public final class GeometryProcessor {
     @UML(identifier="union", specification=ISO_19107) // section 6.4.4.30 and 
6.4.8.7
     //@UML(identifier="3Dunion", specification=ISO_19107) // section 6.4.9
     public Geometry union(Geometry geom1, Geometry geom2) throws 
OperationException {
-        throw new UnsupportedOperationException();
+
+        //TODO : fallback on JTS until implemented, this loss the attributes !
+        return Geometries.fromJTS(jts(geom1).union(jts(geom2)), true);
     }
 
     @UML(identifier="contains", specification=ISO_19107) // section 6.4.4.30 ?
@@ -178,7 +190,9 @@ public final class GeometryProcessor {
                 return Contains.contains(polygon, pt);
             }
         }
-        throw new UnsupportedOperationException();
+
+        //TODO : fallback on JTS until implemented
+        return jts(geom1).contains(jts(geom2));
     }
 
     /**
@@ -187,7 +201,8 @@ public final class GeometryProcessor {
     @UML(identifier="crosses", specification=ISO_19107) // section 6.4.8.8
     //@UML(identifier="3Dcrosses", specification=ISO_19107) // section 6.4.9
     public boolean crosses(Geometry geom1, Geometry geom2) throws 
OperationException {
-        throw new UnsupportedOperationException();
+        //TODO : fallback on JTS until implemented
+        return jts(geom1).crosses(jts(geom2));
     }
 
     /**
@@ -196,7 +211,8 @@ public final class GeometryProcessor {
     @UML(identifier="disjoint", specification=ISO_19107) // section 6.4.8.8
     //@UML(identifier="3Ddisjoint", specification=ISO_19107) // section 6.4.9
     public boolean disjoint(Geometry geom1, Geometry geom2) throws 
OperationException {
-        throw new UnsupportedOperationException();
+        //TODO : fallback on JTS until implemented
+        return jts(geom1).disjoint(jts(geom2));
     }
 
     /**
@@ -205,7 +221,8 @@ public final class GeometryProcessor {
     @UML(identifier="equals", specification=ISO_19107) // section 6.4.8.8, 
6.4.4.30
     //@UML(identifier="3Dequals", specification=ISO_19107) // section 6.4.9
     public boolean equal(Geometry geom1, Geometry geom2) throws 
OperationException {
-        throw new UnsupportedOperationException();
+        //TODO : fallback on JTS until implemented
+        return jts(geom1).equals(jts(geom2));
     }
 
     /**
@@ -214,7 +231,8 @@ public final class GeometryProcessor {
     @UML(identifier="intersects", specification=ISO_19107) // section 6.4.8.8, 
6.4.4.30
     //@UML(identifier="3Dintersects", specification=ISO_19107) // section 6.4.9
     public boolean intersects(Geometry geom1, Geometry geom2) throws 
OperationException {
-        throw new UnsupportedOperationException();
+        //TODO : fallback on JTS until implemented
+        return jts(geom1).intersects(jts(geom2));
     }
 
     /**
@@ -243,7 +261,8 @@ public final class GeometryProcessor {
     @UML(identifier="overlaps", specification=ISO_19107) // section 6.4.8.8
     //@UML(identifier="3Doverlaps", specification=ISO_19107) // section 6.4.9
     public boolean overlaps(Geometry geom1, Geometry geom2) throws 
OperationException {
-        throw new UnsupportedOperationException();
+        //TODO : fallback on JTS until implemented
+        return jts(geom1).overlaps(jts(geom2));
     }
 
     /**
@@ -261,7 +280,8 @@ public final class GeometryProcessor {
     @UML(identifier="relate", specification=ISO_19107) // section 6.4.8.8
     //@UML(identifier="3Drelate", specification=ISO_19107) // section 6.4.9
     public boolean relate(Geometry geom1, Geometry geom2, String matrix) 
throws OperationException {
-        throw new UnsupportedOperationException();
+        //TODO : fallback on JTS until implemented
+        return jts(geom1).relate(jts(geom2), matrix);
     }
 
     /**
@@ -270,7 +290,8 @@ public final class GeometryProcessor {
     @UML(identifier="touches", specification=ISO_19107) // section 6.4.8.8
     //@UML(identifier="3Dtouches", specification=ISO_19107) // section 6.4.9
     public boolean touches(Geometry geom1, Geometry geom2) throws 
OperationException {
-        throw new UnsupportedOperationException();
+        //TODO : fallback on JTS until implemented
+        return jts(geom1).touches(jts(geom2));
     }
 
     /**
@@ -279,7 +300,9 @@ public final class GeometryProcessor {
     @UML(identifier="within", specification=ISO_19107) // section 6.4.8.8
     //@UML(identifier="3Dwithin", specification=ISO_19107) // section 6.4.9
     public boolean within(Geometry geom1, Geometry geom2) throws 
OperationException {
-        throw new UnsupportedOperationException();
+
+        //TODO : fallback on JTS until implemented
+        return jts(geom1).within(jts(geom2));
     }
 
     @UML(identifier="withinDistance", specification=ISO_19107) // section 
6.4.8.8
@@ -458,4 +481,11 @@ public final class GeometryProcessor {
         }
         return sep;
     }
+
+    /**
+     * TODO fallback on JTS until we implemetend all methods.
+     */
+    private static org.locationtech.jts.geom.Geometry jts(Geometry geom) {
+        return Geometries.asJTS(geom, false, null);
+    }
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/ShapeConverterTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/ShapeConverterTest.java
new file mode 100644
index 0000000000..d495285fb0
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/ShapeConverterTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.adapter;
+
+import java.util.Arrays;
+import java.awt.Shape;
+import java.awt.Graphics2D;
+import java.awt.Font;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.Area;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.sis.geometries.Empty;
+import org.apache.sis.geometries.Geometry;
+import org.apache.sis.geometries.GeometryCollection;
+import org.apache.sis.geometries.LineString;
+import org.apache.sis.geometries.LinearRing;
+import org.apache.sis.geometries.MultiPolygon;
+import org.apache.sis.geometries.Point;
+import org.apache.sis.geometries.PointSequence;
+import org.apache.sis.geometries.Polygon;
+import org.apache.sis.geometries.math.Tuple;
+import org.apache.sis.geometries.math.Vector2D;
+
+// Test dependencies
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import org.opengis.geometry.Envelope;
+
+
+/**
+ * Tests {@link ShapeConverter}.
+ *
+ * @author  Johann Sorel (Puzzle-GIS, Geomatys)
+ */
+public final class ShapeConverterTest {
+
+    /**
+     * Creates a new test case.
+     */
+    public ShapeConverterTest() {
+    }
+
+    /**
+     * Verifies that the given geometry is an instance of the expected class
+     * and contains the expected coordinate values.
+     *
+     * @param shape     the Java2D shape to convert with {@link 
ShapeConverter}.
+     * @param type      expected class of the actual geometry.
+     * @param expected  expected coordinates of the actual geometry.
+     */
+    private static void assertCoordinatesEqual(final Shape shape, final 
Class<?> type, final Tuple<?>... expected) {
+        assertCoordinatesEqual(ShapeConverter.create(shape, 0.0001), type, 
expected);
+    }
+
+    /**
+     * Verifies that the given geometry is an instance of the expected class
+     * and contains the expected coordinate values.
+     *
+     * @param geometry  the SIS geometry to test.
+     * @param type      expected class of the actual geometry.
+     * @param expected  expected coordinates of the actual geometry.
+     */
+    private static void assertCoordinatesEqual(final Geometry geometry, final 
Class<?> type, final Tuple<?>... expected) {
+        assertInstanceOf(type, geometry, "Geometry class");
+        assertArrayEquals(expected, getCoordinates(geometry), "Coordinates");
+    }
+
+    private static Tuple[] getCoordinates(Geometry geom) {
+        final List<Tuple> lst = new ArrayList();
+        getCoordinates(geom, lst);
+        return lst.toArray(Tuple[]::new);
+    }
+
+    private static void getCoordinates(Geometry geom, List<Tuple> lst) {
+        if (geom instanceof Empty) {
+            //nothing
+        } else if (geom instanceof Point pt) {
+            lst.add(new Vector2D.Double(pt.getPosition().toArrayDouble()));
+        } else if (geom instanceof LineString ls) {
+            PointSequence ps = ls.getPoints();
+            for (int i = 0, n = ps.size(); i < n; i++) {
+                lst.add(new 
Vector2D.Double(ps.getPosition(i).toArrayDouble()));
+            }
+        } else if (geom instanceof Polygon pl) {
+            getCoordinates(pl.getExteriorRing(), lst);
+            for (int i = 0, n = pl.getNumInteriorRing(); i < n; i++) {
+                getCoordinates(pl.getInteriorRingN(i), lst);
+            }
+        } else if (geom instanceof GeometryCollection col) {
+            for (int i = 0, n = col.getNumGeometries(); i < n; i++) {
+                getCoordinates(col.getGeometryN(i), lst);
+            }
+        } else {
+            throw new UnsupportedOperationException("Unsuported geometry type 
" + geom);
+        }
+    }
+
+    /**
+     * Tests {@link ShapeConverter} with a point.
+     */
+    @Test
+    public void testPoint() {
+        final var shape = new GeneralPath();
+        shape.moveTo(10, 20);
+        assertCoordinatesEqual(shape, Point.class,
+                new Vector2D.Double(10, 20));
+    }
+
+    /**
+     * Tests {@link ShapeConverter} with a line.
+     */
+    @Test
+    public void testLine() {
+        final var shape = new Line2D.Double(1, 2, 3, 4);
+        assertCoordinatesEqual(shape, LineString.class,
+                new Vector2D.Double(1, 2),
+                new Vector2D.Double(3, 4));
+    }
+
+    /**
+     * Tests {@link ShapeConverter} with a rectangle.
+     */
+    @Test
+    public void testRectangle() {
+        final var shape = new Rectangle2D.Double(1, 2, 10, 20);
+        assertCoordinatesEqual(shape, Polygon.class,
+                new Vector2D.Double( 1,  2),
+                new Vector2D.Double(11,  2),
+                new Vector2D.Double(11, 22),
+                new Vector2D.Double( 1, 22),
+                new Vector2D.Double( 1,  2));
+    }
+
+    /**
+     * Tests {@link ShapeConverter} with a rectangle with a hole shape.
+     */
+    @Test
+    public void testRectangleWithHole() {
+        final var contour = new Rectangle2D.Double(1, 2, 10, 20);
+        final var hole    = new Rectangle2D.Double(5, 6,  2,  3);
+        final var shape   = new Area(contour);
+        shape.subtract(new Area(hole));
+
+        final Geometry geometry = ShapeConverter.create(shape, 0.0001);
+        final Polygon polygon = assertInstanceOf(Polygon.class, geometry);
+        assertEquals(1, polygon.getNumInteriorRing());
+
+        assertCoordinatesEqual(polygon.getExteriorRing(), LinearRing.class,
+                new Vector2D.Double(1,   2),
+                new Vector2D.Double(1,  22),
+                new Vector2D.Double(11, 22),
+                new Vector2D.Double(11,  2),
+                new Vector2D.Double(1,   2));
+
+        assertCoordinatesEqual(polygon.getInteriorRingN(0), LinearRing.class,
+                new Vector2D.Double(7, 6),
+                new Vector2D.Double(7, 9),
+                new Vector2D.Double(5, 9),
+                new Vector2D.Double(5, 6),
+                new Vector2D.Double(7, 6));
+    }
+
+    /**
+     * Tests {@link ShapeConverter} with the shape of an arbitrary text.
+     * We use that as an easy way to create relatively complex shapes.
+     * The arbitrary text is "Labi": 4 letters, 5 polygons (because "i" is made
+     * of 2 detached polygons), with 2 polygons ("a" and "b") having a hole.
+     */
+    @Test
+    public void testText() {
+        final Shape shape;
+        final Graphics2D handler = new BufferedImage(1, 1, 
BufferedImage.TYPE_INT_ARGB).createGraphics();
+        try {
+            final FontRenderContext fontRenderContext = 
handler.getFontRenderContext();
+            final Font font = new Font("Monospaced", Font.PLAIN, 12);
+            final GlyphVector glyphs = 
font.createGlyphVector(fontRenderContext, "Labi");
+            shape = glyphs.getOutline();
+        } finally {
+            handler.dispose();
+        }
+        final Geometry geometry = ShapeConverter.create(shape, 0.1);
+        final MultiPolygon mp = assertInstanceOf(MultiPolygon.class, geometry);
+        /*
+         * The "Labi" text contains 4 characters but `i` is split in two 
ploygons,
+         * for a total of 5 polygons. Two letters ("a" and "b") are polyogns 
whith
+         * a hole inside them.
+         */
+        assertEquals(5, mp.getNumGeometries());
+        final var parts = new Geometry[mp.getNumGeometries()];
+        Arrays.setAll(parts, mp::getGeometryN);
+        Arrays.sort(parts, (Geometry o1, Geometry o2) ->                // 
Sort on X
+                Double.compare(o1.getEnvelope().getMinimum(0),
+                               o2.getEnvelope().getMinimum(0)));
+
+        for (int i=0; i < parts.length; i++) {
+            final String message = "Glyph #" + i;
+            final Geometry glyph = parts[i];
+            final Polygon polygon = assertInstanceOf(Polygon.class, glyph, 
message);
+            assertEquals((i == 1 || i == 2) ? 1 : 0, 
polygon.getNumInteriorRing(), message);  // Expect a hole in `a` and `b`.
+        }
+        /*
+         * Compare the bounding boxes.
+         */
+        final Rectangle2D bounds2D = shape.getBounds2D();
+        final Envelope env = geometry.getEnvelope();
+        assertEquals(bounds2D.getMinX(), env.getMinimum(0));
+        assertEquals(bounds2D.getMaxX(), env.getMaximum(0));
+        assertEquals(bounds2D.getMinY(), env.getMinimum(1));
+        assertEquals(bounds2D.getMaxY(), env.getMaximum(1));
+    }
+}

Reply via email to