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 7b191ca191 feat(Geometry): add EWKT and EWKB support
7b191ca191 is described below

commit 7b191ca1915bc7f238b3523219b5bbfc06c90891
Author: jsorel <[email protected]>
AuthorDate: Wed Sep 16 15:34:57 2026 +0200

    feat(Geometry): add EWKT and EWKB support
---
 .../org/apache/sis/geometries/adapter/Srid.java    |  92 +++++++++++
 .../sis/geometries/adapter/WellKnownBinary.java    | 182 +++++++++++++++++++--
 .../geometries/adapter/WellKnownBinaryParser.java  | 102 ++++++++++--
 .../sis/geometries/adapter/WellKnownText.java      |  96 ++++++++++-
 .../geometries/adapter/WellKnownTextParser.java    |  95 ++++++++++-
 .../geometries/adapter/WellKnownBinaryTest.java    | 161 +++++++++++++++++-
 .../sis/geometries/adapter/WellKnownTextTest.java  |  89 ++++++++++
 7 files changed, 768 insertions(+), 49 deletions(-)

diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Srid.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Srid.java
new file mode 100644
index 0000000000..e121324b3e
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Srid.java
@@ -0,0 +1,92 @@
+/*
+ * 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 org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.opengis.metadata.Identifier;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.util.FactoryException;
+
+
+/**
+ * Conversions between the spatial reference identifier of the extended 
geometry formats and a
+ * {@link CoordinateReferenceSystem}. A <abbr>SRID</abbr> is the numeric part 
of an <abbr>EPSG</abbr>
+ * code, and is what {@code EWKT} and {@code EWKB} carry in place of a full 
system definition.
+ *
+ * <p>The mapping is the one of the identifier the system already carries, 
never a search for an
+ * equivalent definition: {@link #of(CoordinateReferenceSystem)} reads the 
identifier and
+ * {@link #forCode(int)} builds the system the authority defines for that 
code. Neither method
+ * changes the axis order, so a system read back from a code is the one the 
<abbr>EPSG</abbr>
+ * authority defines, which for a geographic system is (<var>latitude</var>, 
<var>longitude</var>)
+ * and not the (<var>longitude</var>, <var>latitude</var>) order that some 
databases assume.</p>
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+final class Srid {
+    /**
+     * Value meaning that the coordinate reference system has no spatial 
reference identifier,
+     * which is how the extended formats say that they carry no system.
+     */
+    static final int UNDEFINED = 0;
+
+    /**
+     * Do not allow instantiation of this holder of static methods.
+     */
+    private Srid() {
+    }
+
+    /**
+     * Returns the spatial reference identifier of the given coordinate 
reference system, or
+     * {@link #UNDEFINED} if it has none. Only the identifiers the system 
already carries are
+     * examined; the <abbr>EPSG</abbr> geodetic dataset is not searched for an 
equivalent
+     * definition, so a system built from axes and a datum has no identifier 
whatever it describes.
+     *
+     * @param  crs  the system whose identifier to return, or {@code null}.
+     * @return the spatial reference identifier, or {@link #UNDEFINED} if none.
+     */
+    static int of(final CoordinateReferenceSystem crs) {
+        if (crs != null) {
+            final Identifier id = IdentifiedObjects.getIdentifier(crs, 
Citations.EPSG);
+            if (id != null) try {
+                return Integer.parseInt(id.getCode());
+            } catch (NumberFormatException e) {
+                // An EPSG code which is not a number cannot be written as a 
SRID. Fall through.
+            }
+        }
+        return UNDEFINED;
+    }
+
+    /**
+     * Returns the coordinate reference system which the <abbr>EPSG</abbr> 
authority defines for
+     * the given spatial reference identifier.
+     *
+     * @param  srid  the spatial reference identifier to resolve.
+     * @return the coordinate reference system of that identifier.
+     * @throws IllegalArgumentException if the identifier is unknown, or if 
the definitions
+     *         are not available.
+     */
+    static CoordinateReferenceSystem forCode(final int srid) {
+        try {
+            return CRS.forCode("EPSG:" + srid);
+        } catch (FactoryException e) {
+            throw new IllegalArgumentException("Cannot resolve the spatial 
reference identifier "
+                    + srid + " to a coordinate reference system: " + 
e.getMessage(), e);
+        }
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java
index 2e393f4730..ffe64a5365 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java
@@ -110,11 +110,12 @@ import 
org.opengis.referencing.crs.CoordinateReferenceSystem;
  *       {@code GeometryCollection}, and an empty point may not appear as a 
member of a
  *       {@code MultiPoint}. Every other type has a genuine empty form, a 
count of zero, which
  *       round-trips unchanged.</li>
- *   <li>The coordinate reference system is neither written nor read: the 
{@code SRID} field of the
- *       extended Well-Known Binary of some databases is not part of the 
standard, and neither are
- *       the high order type bits it uses for the dimension flags. Unless a 
system is given to
+ *   <li>In the {@link Flavor#OGC} flavor, the coordinate reference system is 
neither written nor
+ *       read: the {@code SRID} field is not part of the standard, and neither 
are the high order
+ *       type bits which carry it. Unless a system is given to
  *       {@link #decode(byte[], CoordinateReferenceSystem)}, decoded 
geometries use
- *       {@link 
org.apache.sis.geometries.Geometries#getUndefinedCRS(int)}.</li>
+ *       {@link org.apache.sis.geometries.Geometries#getUndefinedCRS(int)}. The
+ *       {@link Flavor#EWKB} flavor writes and reads that field.</li>
  * </ul>
  *
  * <h2>Thread safety</h2>
@@ -123,11 +124,64 @@ import 
org.opengis.referencing.crs.CoordinateReferenceSystem;
  * @author Johann Sorel (Geomatys)
  */
 public final class WellKnownBinary {
+    /**
+     * The dialects of Well-Known Binary which this class can be asked to read 
and write.
+     *
+     * @author Johann Sorel (Geomatys)
+     */
+    public enum Flavor {
+        /**
+         * The Well-Known Binary of <cite>OGC Simple Feature Access 
1.2.1</cite>, extended with the
+         * curved and surface-patch types of <cite>ISO 13249-3</cite>. This is 
the dialect the
+         * class javadoc describes, and the default. The dimension flags are 
the thousands of the
+         * type code, and there is no place for a spatial reference identifier.
+         */
+        OGC,
+
+        /**
+         * The <cite>Extended Well-Known Binary</cite> of PostGIS, which moves 
the dimension flags
+         * into the high order bits of the type code and adds a spatial 
reference identifier:
+         *
+         * <blockquote><pre>
+         * byte    byteOrder;
+         * uint32  type;           // geometry type, OR 0x80000000 for Z, 
0x40000000 for M, 0x20000000 for a SRID
+         * uint32  srid;           // present only if the 0x20000000 bit is set
+         * …                       // body, unchanged
+         * </pre></blockquote>
+         *
+         * <p>The identifier is written on the outermost geometry only, and 
only when the
+         * coordinate reference system carries an <abbr>EPSG</abbr> 
identifier. On reading, the
+         * system it names is used unless the caller passed one of their own to
+         * {@link #decode(byte[], CoordinateReferenceSystem)}.</p>
+         *
+         * <p>This dialect is a superset of {@link #OGC} on reading: the 
thousands of a type code
+         * are still understood, so a plain Well-Known Binary decodes 
unchanged. The converse does
+         * not hold — the {@code OGC} dialect rejects the high order bits.</p>
+         */
+        EWKB,
+
+        /**
+         * The <cite>Tiny Well-Known Binary</cite> of TWKB 1.0.
+         * Not implemented yet: {@link #encode encode(…)} and {@link #decode 
decode(…)} throw
+         * an {@link UnsupportedOperationException} for this flavor.
+         */
+        TWKB
+    }
+
     /**
      * Value of the byte order flag for each of the two orders.
      */
     static final byte XDR = 0, NDR = 1;
 
+    /**
+     * Bits which the {@link Flavor#EWKB} dialect sets in the high order of a 
type code, and the
+     * mask which isolates the base code from them.
+     */
+    static final int EWKB_Z = 0x80000000, 
+                     EWKB_M = 0x40000000, 
+                     EWKB_SRID = 0x20000000,
+                     EWKB_BASE_MASK = 0x1FFFFFFF;
+
     /**
      * Type codes of the geometries which have one, shared by the encoder and 
the parser.
      * They are held apart rather than declared on {@code WellKnownBinary} 
because a field
@@ -169,6 +223,11 @@ public final class WellKnownBinary {
         }
     }
 
+    /**
+     * The dialect to read and write. Never null.
+     */
+    private final Flavor flavor;
+
     /**
      * Byte order of the written geometries. Never null. Both orders are read 
whatever this is.
      */
@@ -179,7 +238,7 @@ public final class WellKnownBinary {
      * order of the byte sequences of the standard. Both orders are read.
      */
     public WellKnownBinary() {
-        byteOrder = ByteOrder.BIG_ENDIAN;
+        this(Flavor.OGC, ByteOrder.BIG_ENDIAN);
     }
 
     /**
@@ -189,10 +248,51 @@ public final class WellKnownBinary {
      * @param  byteOrder  order of the multi-byte values to write, not null.
      */
     public WellKnownBinary(final ByteOrder byteOrder) {
+        this(Flavor.OGC, byteOrder);
+    }
+
+    /**
+     * Creates a codec for the given dialect, writing geometries in big endian 
order.
+     *
+     * @param  flavor  the dialect to read and write, not null.
+     */
+    public WellKnownBinary(final Flavor flavor) {
+        this(flavor, ByteOrder.BIG_ENDIAN);
+    }
+
+    /**
+     * Creates a codec for the given dialect, writing geometries in the given 
byte order.
+     *
+     * @param  flavor     the dialect to read and write, not null.
+     * @param  byteOrder  order of the multi-byte values to write, not null.
+     */
+    public WellKnownBinary(final Flavor flavor, final ByteOrder byteOrder) {
+        ArgumentChecks.ensureNonNull("flavor", flavor);
         ArgumentChecks.ensureNonNull("byteOrder", byteOrder);
+        this.flavor = flavor;
         this.byteOrder = byteOrder;
     }
 
+    /**
+     * Returns the dialect this codec reads and writes.
+     *
+     * @return the dialect given to the constructor, or {@link Flavor#OGC} if 
none was.
+     */
+    public Flavor getFlavor() {
+        return flavor;
+    }
+
+    /**
+     * Verifies that the dialect of this codec is implemented.
+     *
+     * @throws UnsupportedOperationException if it is not.
+     */
+    private void ensureImplemented() {
+        if (flavor == Flavor.TWKB) {
+            throw new UnsupportedOperationException("The " + flavor + " flavor 
is not implemented yet.");
+        }
+    }
+
     /**
      * Returns the Well-Known Binary of the given geometry.
      *
@@ -204,7 +304,11 @@ public final class WellKnownBinary {
      */
     public byte[] encode(final Geometry geom) {
         ArgumentChecks.ensureNonNull("geom", geom);
+        ensureImplemented();
         final Output out = new Output(byteOrder);
+        if (flavor == Flavor.EWKB) {
+            out.setSrid(Srid.of(geom.getCoordinateReferenceSystem()));
+        }
         format(out, geom);
         return out.toArray();
     }
@@ -223,19 +327,24 @@ public final class WellKnownBinary {
 
     /**
      * Returns the geometry described by the given Well-Known Binary, in the 
given coordinate
-     * reference system. Well-Known Binary carries no system of its own.
+     * reference system. The {@link Flavor#OGC} Well-Known Binary carries no 
system of its own;
+     * an {@link Flavor#EWKB} one may carry a spatial reference identifier, 
which is used only
+     * when {@code crs} is null. The system which ends up being used, from 
either source, must
+     * have as many dimensions as the bytes have ordinates per position.
      *
      * @param  geom  the Well-Known Binary to decode, not null.
      * @param  crs   the coordinate reference system of the coordinates in the 
bytes, or
-     *               {@code null}.
+     *               {@code null} for the one the bytes name, if any.
      * @return the decoded geometry.
      * @throws IllegalArgumentException if the bytes are malformed, name a 
geometry type which is
-     *         not in the table of this class javadoc, or have a number of 
ordinates which
-     *         contradicts the dimension of {@code crs}.
+     *         not in the table of this class javadoc, carry a spatial 
reference identifier which
+     *         cannot be resolved, or have a number of ordinates which 
contradicts the dimension
+     *         of the coordinate reference system.
      */
     public Geometry decode(final byte[] geom, final CoordinateReferenceSystem 
crs) {
         ArgumentChecks.ensureNonNull("geom", geom);
-        return new WellKnownBinaryParser(geom, crs).parse();
+        ensureImplemented();
+        return new WellKnownBinaryParser(geom, crs, flavor).parse();
     }
 
     // ////////////////////////////////////////////////////////////////////////
@@ -426,7 +535,9 @@ public final class WellKnownBinary {
     }
 
     /**
-     * Writes the byte order flag and the type code of a geometry, the 
dimension flags included.
+     * Writes the byte order flag and the type code of a geometry, the 
dimension flags included,
+     * followed by the spatial reference identifier when the dialect carries 
one and this is the
+     * outermost geometry.
      *
      * @return whether the positions carry a measure, which the caller has to 
write as the
      *         ordinate following the position ones.
@@ -441,16 +552,33 @@ public final class WellKnownBinary {
         }
         final int dimension = crs.getCoordinateSystem().getDimension();
         final boolean hasM = hasMeasure(geometry);
-        final int flags;
+        final boolean hasZ;
         switch (dimension) {
-            case 2:  flags = hasM ? Codes.M_OFFSET : 0; break;
-            case 3:  flags = hasM ? Codes.Z_OFFSET + Codes.M_OFFSET : 
Codes.Z_OFFSET; break;
+            case 2:  hasZ = false; break;
+            case 3:  hasZ = true;  break;
             default: throw new IllegalArgumentException("Cannot write a " + 
geometry.getGeometryType()
                         + " in Well-Known Binary: its positions have " + 
dimension + " dimensions,"
                         + " but the format defines only 2 and 3.");
         }
+        /*
+         * The identifier is taken at most once per encoding, so only the 
outermost geometry
+         * carries it: every nested call gets Srid.UNDEFINED back.
+         */
+        final int srid = out.takeSrid();
+        int type = code;
+        if (flavor == Flavor.EWKB) {
+            if (hasZ) type |= EWKB_Z;
+            if (hasM) type |= EWKB_M;
+            if (srid != Srid.UNDEFINED) type |= EWKB_SRID;
+        } else {
+            if (hasZ) type += Codes.Z_OFFSET;
+            if (hasM) type += Codes.M_OFFSET;
+        }
         out.writeByteOrder();
-        out.writeInt(code + flags);
+        out.writeInt(type);
+        if (flavor == Flavor.EWKB && srid != Srid.UNDEFINED) {
+            out.writeInt(srid);
+        }
         return hasM;
     }
 
@@ -492,6 +620,13 @@ public final class WellKnownBinary {
          */
         private final boolean bigEndian;
 
+        /**
+         * The spatial reference identifier which the next header has to 
carry, or
+         * {@link Srid#UNDEFINED} if none has to. Held here rather than passed 
down the writing
+         * methods because only the outermost geometry carries it: {@link 
#takeSrid()} clears it.
+         */
+        private int srid;
+
         /**
          * Creates an initially empty output writing in the given byte order.
          */
@@ -499,6 +634,23 @@ public final class WellKnownBinary {
             bigEndian = (byteOrder == ByteOrder.BIG_ENDIAN);
         }
 
+        /**
+         * Sets the identifier which the first header written will carry.
+         */
+        void setSrid(final int srid) {
+            this.srid = srid;
+        }
+
+        /**
+         * Returns the identifier which the header being written has to carry, 
and clears it so
+         * that the nested geometries do not repeat it.
+         */
+        int takeSrid() {
+            final int value = srid;
+            srid = Srid.UNDEFINED;
+            return value;
+        }
+
         /**
          * Writes the flag which tells in which order the values after it are 
written.
          */
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinaryParser.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinaryParser.java
index 194ac06624..49cc8d46ed 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinaryParser.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinaryParser.java
@@ -71,6 +71,11 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
  * differ, so {@link #bigEndian} is re-read at the start of every element and 
applies until the next
  * one is read.
  *
+ * <h2>Spatial reference identifier</h2>
+ * The identifier belongs to the {@link WellKnownBinary.Flavor#EWKB} dialect 
alone, where a bit of
+ * the type code announces it. It describes the whole sequence rather than the 
element which
+ * carries it, so the outermost geometry is the one expected to hold it.
+ *
  * @author  Johann Sorel (Geomatys)
  */
 final class WellKnownBinaryParser {
@@ -91,11 +96,29 @@ final class WellKnownBinaryParser {
     private final byte[] data;
 
     /**
-     * The coordinate reference system given by the caller, or {@code null} 
for deriving an
-     * {@linkplain Geometries#getUndefinedCRS(int) undefined} one from the 
number of ordinates.
+     * The coordinate reference system given by the caller, or {@code null} 
for deriving one from
+     * the spatial reference identifier, or failing that from the number of 
ordinates.
      */
     private final CoordinateReferenceSystem userCRS;
 
+    /**
+     * The dialect being parsed, which decides how the dimension flags are 
read and whether a
+     * spatial reference identifier is allowed.
+     */
+    private final WellKnownBinary.Flavor flavor;
+
+    /**
+     * The spatial reference identifier the bytes carry, or {@link 
Srid#UNDEFINED} if they carry
+     * none. Always {@link Srid#UNDEFINED} in the {@code OGC} dialect, which 
has no field for it.
+     */
+    private int srid;
+
+    /**
+     * The coordinate reference system of {@link #srid}, resolved by the first 
call to
+     * {@link #crs()} which needs it. Null as long as it has not been resolved.
+     */
+    private CoordinateReferenceSystem sridCRS;
+
     /**
      * Index in {@link #data} of the next byte to read.
      */
@@ -119,12 +142,16 @@ final class WellKnownBinaryParser {
     /**
      * Creates a parser for the given bytes.
      *
-     * @param  data  the Well-Known Binary to parse.
-     * @param  crs   the coordinate reference system to give to the 
geometries, or {@code null}.
+     * @param  data    the Well-Known Binary to parse.
+     * @param  crs     the coordinate reference system to give to the 
geometries, or {@code null}.
+     * @param  flavor  the dialect to parse.
      */
-    WellKnownBinaryParser(final byte[] data, final CoordinateReferenceSystem 
crs) {
+    WellKnownBinaryParser(final byte[] data, final CoordinateReferenceSystem 
crs,
+                          final WellKnownBinary.Flavor flavor)
+    {
         this.data = data;
         this.userCRS = crs;
+        this.flavor = flavor;
     }
 
     /**
@@ -149,18 +176,34 @@ final class WellKnownBinaryParser {
      */
     private Geometry parseGeometry() {
         readByteOrder();
-        final int code = readInt();
-        if (code < 0) {
+        final int type = readInt();
+        final boolean extended = (flavor == WellKnownBinary.Flavor.EWKB);
+        int code = type;
+        int flags = 0;
+        if (extended) {
+            /*
+             * The high order bits are the dimension flags and the presence of 
an identifier.
+             * The base which remains may still carry the thousands of the OGC 
dialect, which
+             * this dialect understands as well, so that a plain Well-Known 
Binary decodes here.
+             */
+            if ((type & WellKnownBinary.EWKB_Z) != 0) flags |= FLAG_Z;
+            if ((type & WellKnownBinary.EWKB_M) != 0) flags |= FLAG_M;
+            code = type & WellKnownBinary.EWKB_BASE_MASK;
+        } else if (code < 0) {
             throw error("Type code " + Integer.toUnsignedString(code) + " is 
out of range."
-                    + " The extended Well-Known Binary of some databases, 
which puts the dimension"
-                    + " flags in the high order bits, is not supported");
+                    + " The extended Well-Known Binary, which puts the 
dimension flags in the"
+                    + " high order bits, is read by the " + 
WellKnownBinary.Flavor.EWKB + " flavor");
         }
-        final int flags = code / 1000;
-        if (flags > 3) {
+        final int thousands = code / 1000;
+        if (thousands > 3) {
             throw error("Type code " + code + " has no dimension flag: the 
thousands must be"
                     + " 0 for XY, 1 for Z, 2 for M or 3 for ZM");
         }
+        flags |= thousands;
         applyFlags(flags);
+        if (extended && (type & WellKnownBinary.EWKB_SRID) != 0) {
+            readSrid();
+        }
         switch (code % 1000) {
             case WellKnownBinary.Codes.POINT:               return 
parsePoint();
             case WellKnownBinary.Codes.LINESTRING:          return 
GeometryFactory.createLineString(readPointList());
@@ -181,6 +224,24 @@ final class WellKnownBinaryParser {
         }
     }
 
+    /**
+     * Reads the spatial reference identifier which follows a type code whose
+     * {@link WellKnownBinary#EWKB_SRID} bit is set.
+     *
+     * <p>Only the outermost geometry is expected to carry one, but a nested 
geometry repeating it
+     * is accepted as long as it repeats the same value: the whole sequence 
describes positions in
+     * a single system, so two different identifiers would contradict each 
other.</p>
+     */
+    private void readSrid() {
+        final int declared = readInt();
+        if (srid == Srid.UNDEFINED) {
+            srid = declared;
+        } else if (srid != declared) {
+            throw error("A nested geometry declares the spatial reference 
identifier " + declared
+                    + " while the enclosing one declares " + srid);
+        }
+    }
+
     /**
      * Parses the single coordinate tuple of a point.
      *
@@ -454,15 +515,26 @@ final class WellKnownBinaryParser {
      */
     private CoordinateReferenceSystem crs() {
         final int dimension = positionDimension();
-        if (userCRS == null) {
+        final CoordinateReferenceSystem declared;
+        final String source;
+        if (userCRS != null) {
+            declared = userCRS;
+            source = "The given coordinate reference system";
+        } else if (srid != Srid.UNDEFINED) {
+            if (sridCRS == null) {
+                sridCRS = Srid.forCode(srid);
+            }
+            declared = sridCRS;
+            source = "The coordinate reference system of SRID " + srid;
+        } else {
             return Geometries.getUndefinedCRS(dimension);
         }
-        final int actual = userCRS.getCoordinateSystem().getDimension();
+        final int actual = declared.getCoordinateSystem().getDimension();
         if (actual != dimension) {
-            throw error("The given coordinate reference system has " + actual 
+ " dimensions,"
+            throw error(source + " has " + actual + " dimensions,"
                     + " but the bytes have " + dimension + " ordinates per 
position");
         }
-        return userCRS;
+        return declared;
     }
 
     /**
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java
index 0fe8fde7ca..fedd8c6b5b 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java
@@ -108,10 +108,11 @@ import 
org.opengis.referencing.crs.CoordinateReferenceSystem;
  *       written back as {@code GEOMETRYCOLLECTION EMPTY}, and an empty point 
may not appear as a
  *       member of a {@code MULTIPOINT}. Every other type has a genuine empty 
form which
  *       round-trips unchanged.</li>
- *   <li>The coordinate reference system is neither written nor read: the 
{@code SRID=…;} prefix
- *       of the extended Well-Known Text of some databases is not part of the 
standard. Unless a
- *       system is given to {@link #decode(String, 
CoordinateReferenceSystem)}, decoded geometries
- *       use {@link 
org.apache.sis.geometries.Geometries#getUndefinedCRS(int)}.</li>
+ *   <li>In the {@link Flavor#OGC} flavor, the coordinate reference system is 
neither written nor
+ *       read: the {@code SRID=…;} prefix is not part of the standard. Unless 
a system is given to
+ *       {@link #decode(String, CoordinateReferenceSystem)}, decoded 
geometries use
+ *       {@link org.apache.sis.geometries.Geometries#getUndefinedCRS(int)}. The
+ *       {@link Flavor#EWKT} flavor writes and reads that prefix.</li>
  * </ul>
  *
  * <h2>Thread safety</h2>
@@ -121,6 +122,39 @@ import 
org.opengis.referencing.crs.CoordinateReferenceSystem;
  * @author Johann Sorel (Geomatys)
  */
 public final class WellKnownText {
+    /**
+     * The dialects of Well-Known Text which this class can be asked to read 
and write.
+     *
+     * @author Johann Sorel (Geomatys)
+     */
+    public enum Flavor {
+        /**
+         * The Well-Known Text of <cite>OGC Simple Feature Access 
1.2.1</cite>, extended with the
+         * curved and surface-patch types of <cite>ISO 13249-3</cite>. This is 
the dialect the
+         * class javadoc describes, and the default.
+         */
+        OGC,
+
+        /**
+         * The <cite>Extended Well-Known Text</cite> of PostGIS, which is the 
{@link #OGC} dialect
+         * preceded by an optional spatial reference identifier:
+         *
+         * <blockquote><pre>SRID=4326;POINT (1 2)</pre></blockquote>
+         *
+         * <p>The prefix is written when the coordinate reference system of 
the geometry carries an
+         * <abbr>EPSG</abbr> identifier, and omitted otherwise — an Extended 
Well-Known Text without
+         * the prefix is an ordinary Well-Known Text. On reading, the prefix 
is optional, and the
+         * system it names is used unless the caller passed one of their own to
+         * {@link #decode(String, CoordinateReferenceSystem)}.</p>
+         */
+        EWKT
+    }
+
+    /**
+     * The dialect to read and write. Never null.
+     */
+    private final Flavor flavor;
+
     /**
      * Format of the ordinates, or {@code null} for writing the shortest text 
which parses back
      * to the same {@code double}. Not thread-safe, hence the warning in the 
class javadoc.
@@ -133,6 +167,18 @@ public final class WellKnownText {
      * {@code 2}, not {@code 2.0}.
      */
     public WellKnownText() {
+        this(Flavor.OGC);
+    }
+
+    /**
+     * Creates a codec for the given dialect, writing every ordinate as the 
shortest decimal text
+     * which parses back to the same {@code double} value.
+     *
+     * @param  flavor  the dialect to read and write, not null.
+     */
+    public WellKnownText(final Flavor flavor) {
+        ArgumentChecks.ensureNonNull("flavor", flavor);
+        this.flavor = flavor;
         format = null;
     }
 
@@ -144,13 +190,35 @@ public final class WellKnownText {
      * @param  decimalPrecision  maximal number of digits after the decimal 
separator, 0 or more.
      */
     public WellKnownText(final int decimalPrecision) {
+        this(Flavor.OGC, decimalPrecision);
+    }
+
+    /**
+     * Creates a codec for the given dialect, rounding every ordinate to at 
most the given number
+     * of fraction digits as {@link #WellKnownText(int)} does.
+     *
+     * @param  flavor            the dialect to read and write, not null.
+     * @param  decimalPrecision  maximal number of digits after the decimal 
separator, 0 or more.
+     */
+    public WellKnownText(final Flavor flavor, final int decimalPrecision) {
+        ArgumentChecks.ensureNonNull("flavor", flavor);
         ArgumentChecks.ensurePositive("decimalPrecision", decimalPrecision);
+        this.flavor = flavor;
         format = NumberFormat.getNumberInstance(Locale.ROOT);
         format.setGroupingUsed(false);
         format.setMaximumFractionDigits(decimalPrecision);
         format.setRoundingMode(RoundingMode.HALF_UP);       // Not the 
HALF_EVEN default, which surprises.
     }
 
+    /**
+     * Returns the dialect this codec reads and writes.
+     *
+     * @return the dialect given to the constructor, or {@link Flavor#OGC} if 
none was.
+     */
+    public Flavor getFlavor() {
+        return flavor;
+    }
+
     /**
      * Returns the Well-Known Text of the given geometry.
      *
@@ -163,6 +231,12 @@ public final class WellKnownText {
     public String encode(final Geometry geom) {
         ArgumentChecks.ensureNonNull("geom", geom);
         final StringBuilder sb = new StringBuilder();
+        if (flavor == Flavor.EWKT) {
+            final int srid = Srid.of(geom.getCoordinateReferenceSystem());
+            if (srid != Srid.UNDEFINED) {
+                sb.append("SRID=").append(srid).append(';');
+            }
+        }
         format(sb, geom);
         return sb.toString();
     }
@@ -181,19 +255,23 @@ public final class WellKnownText {
 
     /**
      * Returns the geometry described by the given Well-Known Text, in the 
given coordinate
-     * reference system. Well-Known Text carries no system of its own.
+     * reference system. The {@link Flavor#OGC} Well-Known Text carries no 
system of its own;
+     * an {@link Flavor#EWKT} one may carry a spatial reference identifier, 
which is used only
+     * when {@code crs} is null. The system which ends up being used, from 
either source, must
+     * have as many dimensions as the text has ordinates per position.
      *
      * @param  geom  the Well-Known Text to decode, not null.
      * @param  crs   the coordinate reference system of the coordinates in the 
text, or
-     *               {@code null}.
+     *               {@code null} for the one the text names, if any.
      * @return the decoded geometry.
      * @throws IllegalArgumentException if the text is malformed, names a 
geometry type which is
-     *         not in the table of this class javadoc, or has a number of 
ordinates which
-     *         contradicts the dimension of {@code crs}.
+     *         not in the table of this class javadoc, names a spatial 
reference identifier which
+     *         cannot be resolved, or has a number of ordinates which 
contradicts the dimension
+     *         of the coordinate reference system.
      */
     public Geometry decode(final String geom, final CoordinateReferenceSystem 
crs) {
         ArgumentChecks.ensureNonNull("geom", geom);
-        return new WellKnownTextParser(geom, crs).parse();
+        return new WellKnownTextParser(geom, crs, flavor).parse();
     }
 
     // ////////////////////////////////////////////////////////////////////////
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownTextParser.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownTextParser.java
index 46c933054b..f3b1e9e96f 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownTextParser.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownTextParser.java
@@ -68,6 +68,11 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
  * later element contradicting them is an error, and if no element states them 
at all they are
  * inferred from the width of the first coordinate tuple.
  *
+ * <h2>Spatial reference identifier</h2>
+ * The optional {@code SRID=…;} prefix belongs to the {@link 
WellKnownText.Flavor#EWKT} dialect
+ * alone, and precedes the whole text rather than any single element. In the 
{@code OGC} dialect
+ * the {@code SRID} keyword is read as a geometry type name, and therefore 
rejected as unknown.
+ *
  * @author  Johann Sorel (Geomatys)
  */
 final class WellKnownTextParser {
@@ -93,11 +98,29 @@ final class WellKnownTextParser {
     private final String text;
 
     /**
-     * The coordinate reference system given by the caller, or {@code null} 
for deriving an
-     * {@linkplain Geometries#getUndefinedCRS(int) undefined} one from the 
number of ordinates.
+     * The coordinate reference system given by the caller, or {@code null} 
for deriving one from
+     * the {@code SRID=…;} prefix, or failing that from the number of 
ordinates.
      */
     private final CoordinateReferenceSystem userCRS;
 
+    /**
+     * The dialect being parsed, which decides whether a {@code SRID=…;} 
prefix is allowed.
+     */
+    private final WellKnownText.Flavor flavor;
+
+    /**
+     * The spatial reference identifier of the {@code SRID=…;} prefix, or 
{@link Srid#UNDEFINED}
+     * if the text has no such prefix. Always {@link Srid#UNDEFINED} in the 
{@code OGC} flavor,
+     * where the prefix is not part of the grammar.
+     */
+    private int srid;
+
+    /**
+     * The coordinate reference system of {@link #srid}, resolved by the first 
call to
+     * {@link #crs()} which needs it. Null as long as it has not been resolved.
+     */
+    private CoordinateReferenceSystem sridCRS;
+
     /**
      * Index in {@link #text} of the next character to read.
      */
@@ -117,12 +140,14 @@ final class WellKnownTextParser {
     /**
      * Creates a parser for the given text.
      *
-     * @param  text  the Well-Known Text to parse.
-     * @param  crs   the coordinate reference system to give to the 
geometries, or {@code null}.
+     * @param  text    the Well-Known Text to parse.
+     * @param  crs     the coordinate reference system to give to the 
geometries, or {@code null}.
+     * @param  flavor  the dialect to parse.
      */
-    WellKnownTextParser(final String text, final CoordinateReferenceSystem 
crs) {
+    WellKnownTextParser(final String text, final CoordinateReferenceSystem 
crs, final WellKnownText.Flavor flavor) {
         this.text = text;
         this.userCRS = crs;
+        this.flavor = flavor;
     }
 
     /**
@@ -131,6 +156,9 @@ final class WellKnownTextParser {
      * @throws IllegalArgumentException if the text is malformed or names an 
unsupported type.
      */
     Geometry parse() {
+        if (flavor == WellKnownText.Flavor.EWKT) {
+            readSrid();
+        }
         final Geometry geometry = parseGeometry();
         skipSpaces();
         if (pos < text.length()) {
@@ -139,6 +167,26 @@ final class WellKnownTextParser {
         return geometry;
     }
 
+    /**
+     * Consumes the {@code SRID=…;} prefix of an Extended Well-Known Text if 
there is one.
+     * The prefix is optional, so a text which does not start with the {@code 
SRID} keyword is
+     * left untouched; but a text which does start with it must carry a 
complete prefix.
+     */
+    private void readSrid() {
+        final int mark = pos;
+        if (!"SRID".equals(readWord())) {
+            pos = mark;
+            return;
+        }
+        if (!accept('=')) {
+            throw error("Expected '=' after the SRID keyword");
+        }
+        srid = readUnsignedInteger();
+        if (!accept(';')) {
+            throw error("Expected ';' after the spatial reference identifier");
+        }
+    }
+
     // ////////////////////////////////////////////////////////////////////////
     // Productions ////////////////////////////////////////////////////////////
     // ////////////////////////////////////////////////////////////////////////
@@ -494,15 +542,26 @@ final class WellKnownTextParser {
      */
     private CoordinateReferenceSystem crs() {
         final int dimension = positionDimension();
-        if (userCRS == null) {
+        final CoordinateReferenceSystem declared;
+        final String source;
+        if (userCRS != null) {
+            declared = userCRS;
+            source = "The given coordinate reference system";
+        } else if (srid != Srid.UNDEFINED) {
+            if (sridCRS == null) {
+                sridCRS = Srid.forCode(srid);
+            }
+            declared = sridCRS;
+            source = "The coordinate reference system of SRID " + srid;
+        } else {
             return Geometries.getUndefinedCRS(dimension);
         }
-        final int actual = userCRS.getCoordinateSystem().getDimension();
+        final int actual = declared.getCoordinateSystem().getDimension();
         if (actual != dimension) {
-            throw error("The given coordinate reference system has " + actual 
+ " dimensions,"
+            throw error(source + " has " + actual + " dimensions,"
                     + " but the text has " + dimension + " ordinates per 
position");
         }
-        return userCRS;
+        return declared;
     }
 
     /**
@@ -691,6 +750,24 @@ final class WellKnownTextParser {
         }
     }
 
+    /**
+     * Reads a sequence of decimal digits, which the grammar uses only for a 
spatial reference
+     * identifier. Unlike {@link #readNumber()} it accepts neither a sign, nor 
a fractional part,
+     * nor an exponent, since none of those can appear in an identifier.
+     */
+    private int readUnsignedInteger() {
+        skipSpaces();
+        final int start = pos;
+        while (pos < text.length() && isDigit(text.charAt(pos))) pos++;
+        final String token = text.substring(start, pos);
+        try {
+            return Integer.parseInt(token);
+        } catch (NumberFormatException e) {
+            pos = start;
+            throw error("\"" + token + "\" is not a spatial reference 
identifier");
+        }
+    }
+
     /**
      * Reads the next word, in upper case, or an empty string if the next 
character is not a
      * letter. Words are the keywords, the dimension flags and {@code EMPTY}.
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownBinaryTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownBinaryTest.java
index 175f4920eb..99c75693d9 100644
--- 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownBinaryTest.java
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownBinaryTest.java
@@ -41,6 +41,9 @@ import org.apache.sis.geometries.surface.TIN;
 import org.apache.sis.geometries.surface.Triangle;
 import org.apache.sis.maths.NDArrays;
 import org.apache.sis.maths.SampleSystem;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.IdentifiedObjects;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 // Test dependencies
@@ -258,7 +261,7 @@ public final class WellKnownBinaryTest {
          */
         final byte[] mixed = concat(bytes("00" + "00000007" + "00000001"), 
littleEndian);
         assertEquals("GEOMETRYCOLLECTION (POINT (1 2))", 
wkt.encode(wkb.decode(mixed)));
-        assertThrows(NullPointerException.class, () -> new 
WellKnownBinary(null));
+        assertThrows(NullPointerException.class, () -> new 
WellKnownBinary((ByteOrder) null));
     }
 
     /**
@@ -354,6 +357,162 @@ public final class WellKnownBinaryTest {
         assertThrows(IllegalArgumentException.class, () -> wkb.decode(data), 
hex(data));
     }
 
+    /**
+     * Bits of the ordinates used by the extended tests below.
+     */
+    private static final String ZERO = "0000000000000000", 
+                                ONE = "3FF0000000000000",
+                                TWO = "4000000000000000", 
+                                THREE = "4008000000000000", 
+                                FOUR = "4010000000000000";
+
+    /**
+     * Tests the spatial reference identifier which the {@link 
WellKnownBinary.Flavor#EWKB}
+     * dialect adds, and the high order bits which announce it.
+     */
+    @Test
+    public void testExtendedRoundTrip() {
+        final WellKnownBinary ewkb = new 
WellKnownBinary(WellKnownBinary.Flavor.EWKB);
+        assertEquals(WellKnownBinary.Flavor.EWKB, ewkb.getFlavor());
+        assertEquals(WellKnownBinary.Flavor.OGC,  wkb.getFlavor());
+        /*
+         * The 0x20000000 bit announces the identifier, which follows the type 
code.
+         * EPSG:4326 is 0x10E6.
+         */
+        final Geometry point = 
GeometryFactory.createPoint(CommonCRS.WGS84.geographic(), 1, 2);
+        final byte[] bytes = ewkb.encode(point);
+        assertEquals("00" + "20000001" + "000010E6" + ONE + TWO, hex(bytes));
+        /*
+         * Reading gives the geometry its system back, and writing it again 
gives the same bytes.
+         */
+        final Geometry back = ewkb.decode(bytes);
+        assertEquals("4326", IdentifiedObjects.getIdentifier(
+                back.getCoordinateReferenceSystem(), 
Citations.EPSG).getCode());
+        assertEquals("POINT (1 2)", wkt.encode(back));
+        assertArrayEquals(bytes, ewkb.encode(back));
+    }
+
+    /**
+     * Verifies that only the outermost geometry carries the identifier, the 
nested ones being
+     * written with a plain type code.
+     */
+    @Test
+    public void testExtendedIdentifierOnOutermostOnly() {
+        final WellKnownBinary ewkb = new 
WellKnownBinary(WellKnownBinary.Flavor.EWKB);
+        final CoordinateReferenceSystem crs = CommonCRS.WGS84.geographic();
+        final byte[] bytes = ewkb.encode(GeometryFactory.createMultiPoint(
+                GeometryFactory.createPoint(crs, 1, 2),
+                GeometryFactory.createPoint(crs, 3, 4)));
+        assertEquals("00" + "20000004" + "000010E6" + "00000002"
+                   + "00" + "00000001" + ONE   + TWO
+                   + "00" + "00000001" + THREE + FOUR, hex(bytes));
+        /*
+         * The identifier read on the outermost geometry reaches the members 
as well.
+         */
+        final MultiPoint<?> decoded = assertInstanceOf(MultiPoint.class, 
ewkb.decode(bytes));
+        assertEquals("MULTIPOINT ((1 2), (3 4))", wkt.encode(decoded));
+        assertEquals("4326", IdentifiedObjects.getIdentifier(
+                decoded.getGeometryN(0).getCoordinateReferenceSystem(), 
Citations.EPSG).getCode());
+    }
+
+    /**
+     * Tests the dimension flags of the extended dialect, which are high order 
bits rather than
+     * the thousands of the type code.
+     */
+    @Test
+    public void testExtendedDimensionFlags() {
+        final WellKnownBinary ewkb = new 
WellKnownBinary(WellKnownBinary.Flavor.EWKB);
+        assertEquals("00" + "80000001" + ONE + TWO + THREE,
+                     hex(ewkb.encode(wkt.decode("POINT Z (1 2 3)"))));
+        assertEquals("00" + "40000001" + ONE + TWO + THREE,
+                     hex(ewkb.encode(wkt.decode("POINT M (1 2 3)"))));
+        assertEquals("00" + "C0000001" + ONE + TWO + THREE + FOUR,
+                     hex(ewkb.encode(wkt.decode("POINT ZM (1 2 3 4)"))));
+        assertEquals("00" + "80000002" + "00000002" + ZERO + ZERO + ZERO + ONE 
+ ONE + ONE,
+                     hex(ewkb.encode(wkt.decode("LINESTRING Z (0 0 0, 1 1 
1)"))));
+        /*
+         * Every one of them reads back to the geometry it was written from.
+         */
+        for (final String text : new String[] {
+                "POINT Z (1 2 3)", "POINT M (1 2 3)", "POINT ZM (1 2 3 4)",
+                "LINESTRING Z (0 0 0, 1 1 1)", "POLYGON ZM ((0 0 0 5, 1 0 0 6, 
1 1 0 7, 0 0 0 5))",
+                "GEOMETRYCOLLECTION Z (POINT Z (1 2 3))"})
+        {
+            assertEquals(text, 
wkt.encode(ewkb.decode(ewkb.encode(wkt.decode(text)))), text);
+        }
+    }
+
+    /**
+     * Verifies that the extended dialect is a superset of the standard one on 
reading, and that
+     * the converse does not hold.
+     */
+    @Test
+    public void testExtendedReadsPlainBinary() {
+        final WellKnownBinary ewkb = new 
WellKnownBinary(WellKnownBinary.Flavor.EWKB);
+        for (final String text : new String[] {
+                "POINT (1 2)", "POINT Z (1 2 3)", "POINT ZM (1 2 3 4)",
+                "POLYGON Z ((0 0 0, 1 0 0, 1 1 0, 0 0 0))",
+                "GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 0, 1 1))"})
+        {
+            assertEquals(text, 
wkt.encode(ewkb.decode(wkb.encode(wkt.decode(text)))), text);
+        }
+        /*
+         * The standard dialect rejects the high order bits of the extended 
one.
+         */
+        assertMalformed(ewkb.encode(wkt.decode("POINT Z (1 2 3)")));
+        
assertMalformed(ewkb.encode(GeometryFactory.createPoint(CommonCRS.WGS84.geographic(),
 1, 2)));
+    }
+
+    /**
+     * Verifies that the system the bytes name yields to one the caller gives, 
and tests the
+     * rejection of the identifiers which cannot be used.
+     */
+    @Test
+    public void testExtendedPrecedenceAndErrors() {
+        final WellKnownBinary ewkb = new 
WellKnownBinary(WellKnownBinary.Flavor.EWKB);
+        final byte[] bytes = 
ewkb.encode(GeometryFactory.createPoint(CommonCRS.WGS84.geographic(), 1, 2));
+        final CoordinateReferenceSystem crs = Geometries.getUndefinedCRS(2);
+        assertSame(crs, ewkb.decode(bytes, 
crs).getCoordinateReferenceSystem());
+        /*
+         * EPSG:4326 is two dimensional, so a Z geometry carrying it 
contradicts it.
+         * 0xA0000001 is the point type with the Z bit and the identifier bit.
+         */
+        assertExtendedMalformed("00" + "A0000001" + "000010E6" + ONE + TWO + 
THREE);
+        /*
+         * An identifier which no authority defines. 999999 is 0xF423F.
+         */
+        assertExtendedMalformed("00" + "20000001" + "000F423F" + ONE + TWO);
+        /*
+         * Two geometries of the same sequence cannot name two different 
systems. 3857 is 0xF11.
+         */
+        assertExtendedMalformed("00" + "20000004" + "000010E6" + "00000001"
+                              + "00" + "20000001" + "00000F11" + ONE + TWO);
+    }
+
+    /**
+     * Verifies that the given hexadecimal bytes are rejected by the extended 
dialect.
+     */
+    private void assertExtendedMalformed(final String text) {
+        final WellKnownBinary ewkb = new 
WellKnownBinary(WellKnownBinary.Flavor.EWKB);
+        final byte[] data = bytes(text);
+        assertThrows(IllegalArgumentException.class, () -> ewkb.decode(data), 
text);
+    }
+
+    /**
+     * Verifies that the dialect which is not implemented yet says so instead 
of writing
+     * something else.
+     */
+    @Test
+    public void testUnimplementedFlavor() {
+        final WellKnownBinary twkb = new 
WellKnownBinary(WellKnownBinary.Flavor.TWKB);
+        assertEquals(WellKnownBinary.Flavor.TWKB, twkb.getFlavor());
+        final Geometry geometry = wkt.decode("POINT (1 2)");
+        final byte[] bytes = wkb.encode(geometry);
+        assertThrows(UnsupportedOperationException.class, () -> 
twkb.encode(geometry));
+        assertThrows(UnsupportedOperationException.class, () -> 
twkb.decode(bytes));
+        assertThrows(NullPointerException.class, () -> new 
WellKnownBinary((WellKnownBinary.Flavor) null));
+    }
+
     /**
      * Returns a copy of the given geometry with the type code of its 
outermost element replaced
      * by the given one, the dimension flags left untouched.
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownTextTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownTextTest.java
index 43f4f25ec8..0a3def45dc 100644
--- 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownTextTest.java
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/adapter/WellKnownTextTest.java
@@ -39,6 +39,8 @@ import org.apache.sis.geometries.surface.TIN;
 import org.apache.sis.geometries.surface.Triangle;
 import org.apache.sis.maths.NDArrays;
 import org.apache.sis.maths.SampleSystem;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.IdentifiedObjects;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 // Test dependencies
@@ -296,4 +298,91 @@ public final class WellKnownTextTest {
     private void assertMalformed(final String text) {
         assertThrows(IllegalArgumentException.class, () -> wkt.decode(text), 
text);
     }
+
+    /**
+     * Tests the {@code SRID=…;} prefix which the {@link 
WellKnownText.Flavor#EWKT} dialect adds.
+     */
+    @Test
+    public void testExtendedRoundTrip() {
+        final WellKnownText ewkt = new 
WellKnownText(WellKnownText.Flavor.EWKT);
+        assertEquals(WellKnownText.Flavor.EWKT, ewkt.getFlavor());
+        assertEquals(WellKnownText.Flavor.OGC,  wkt.getFlavor());
+        /*
+         * The prefix is read, kept in the coordinate reference system of the 
geometry,
+         * and written back.
+         */
+        final Geometry geometry = ewkt.decode("SRID=4326;POINT (1 2)");
+        assertEquals("4326", IdentifiedObjects.getIdentifier(
+                geometry.getCoordinateReferenceSystem(), 
Citations.EPSG).getCode());
+        assertEquals("SRID=4326;POINT (1 2)", ewkt.encode(geometry));
+        /*
+         * The prefix applies to the whole text, not to each member of a 
collection.
+         */
+        assertEquals("SRID=4326;GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 
0, 1 1))",
+                     ewkt.encode(ewkt.decode("SRID=4326;GEOMETRYCOLLECTION 
(POINT (1 2), LINESTRING (0 0, 1 1))")));
+        assertEquals("SRID=3857;MULTIPOINT ((1 2), (3 4))",
+                     ewkt.encode(ewkt.decode("SRID=3857;MULTIPOINT ((1 2), (3 
4))")));
+    }
+
+    /**
+     * Verifies that the prefix is optional on both sides: a geometry whose 
system has no
+     * <abbr>EPSG</abbr> identifier is written without it, and a text without 
it is read.
+     */
+    @Test
+    public void testExtendedWithoutSRID() {
+        final WellKnownText ewkt = new 
WellKnownText(WellKnownText.Flavor.EWKT);
+        assertEquals("POINT (1 2)", ewkt.encode(wkt.decode("POINT (1 2)")));
+        assertEquals("POINT (1 2)", ewkt.encode(ewkt.decode("POINT (1 2)")));
+        assertEquals("POINT Z (1 2 3)", ewkt.encode(ewkt.decode("POINT Z (1 2 
3)")));
+    }
+
+    /**
+     * Verifies that the prefix belongs to the extended dialect alone, and 
that the system it
+     * names yields to one the caller gives.
+     */
+    @Test
+    public void testExtendedPrecedence() {
+        final WellKnownText ewkt = new 
WellKnownText(WellKnownText.Flavor.EWKT);
+        /*
+         * The OGC dialect reads SRID as a geometry type name, and rejects it 
as unknown.
+         */
+        assertMalformed("SRID=4326;POINT (1 2)");
+        /*
+         * A system given by the caller wins over the one the text names.
+         */
+        final CoordinateReferenceSystem crs = Geometries.getUndefinedCRS(2);
+        assertSame(crs, ewkt.decode("SRID=4326;POINT (1 2)", 
crs).getCoordinateReferenceSystem());
+        /*
+         * The system the prefix names must have as many dimensions as the 
text has ordinates.
+         * EPSG:4326 is two dimensional, so a Z geometry contradicts it.
+         */
+        assertThrows(IllegalArgumentException.class, () -> 
ewkt.decode("SRID=4326;POINT Z (1 2 3)"));
+    }
+
+    /**
+     * Tests the rejection of malformed prefixes.
+     */
+    @Test
+    public void testExtendedMalformed() {
+        final WellKnownText ewkt = new 
WellKnownText(WellKnownText.Flavor.EWKT);
+        for (final String text : new String[] {
+                "SRID=;POINT (1 2)",            // No identifier.
+                "SRID 4326;POINT (1 2)",        // No '='.
+                "SRID=4326 POINT (1 2)",        // No ';'.
+                "SRID=-1;POINT (1 2)",          // Identifiers are unsigned.
+                "SRID=4326;",                   // No geometry.
+                "SRID=999999;POINT (1 2)"})     // Unknown identifier.
+        {
+            assertThrows(IllegalArgumentException.class, () -> 
ewkt.decode(text), text);
+        }
+    }
+
+    /**
+     * Verifies that a dialect is required when one is asked for.
+     */
+    @Test
+    public void testNullFlavor() {
+        assertThrows(NullPointerException.class, () -> new 
WellKnownText(null));
+        assertThrows(NullPointerException.class, () -> new WellKnownText(null, 
3));
+    }
 }

Reply via email to