You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2024/01/14 21:12:12 UTC

(sis) branch geoapi-4.0 updated: Better management of change of axis order: - Share cache, for avoiding to redo the same operation. - Make possible to get back the original definition. - Always sort time dimension last in "normalized" CS.

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

desruisseaux 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 2a816a05e5 Better management of change of axis order: - Share cache, for avoiding to redo the same operation. - Make possible to get back the original definition. - Always sort time dimension last in "normalized" CS.
2a816a05e5 is described below

commit 2a816a05e50503c04669614ec3fcee6f34c45e49
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sun Jan 14 21:46:03 2024 +0100

    Better management of change of axis order:
    - Share cache, for avoiding to redo the same operation.
    - Make possible to get back the original definition.
    - Always sort time dimension last in "normalized" CS.
---
 .../apache/sis/coverage/grid/GridOrientation.java  |   2 +-
 .../apache/sis/referencing/crs/AbstractCRS.java    | 116 +++++++------
 .../sis/referencing/crs/AbstractDerivedCRS.java    |  16 +-
 .../sis/referencing/crs/DefaultCompoundCRS.java    | 126 +++++++-------
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  73 +++++---
 .../sis/referencing/crs/DefaultEngineeringCRS.java |  22 ++-
 .../sis/referencing/crs/DefaultGeocentricCRS.java  |  29 ++--
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  20 ++-
 .../sis/referencing/crs/DefaultGeographicCRS.java  |  25 ++-
 .../sis/referencing/crs/DefaultImageCRS.java       |  19 ++-
 .../sis/referencing/crs/DefaultParametricCRS.java  |  21 ++-
 .../sis/referencing/crs/DefaultProjectedCRS.java   |  17 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |  20 ++-
 .../sis/referencing/crs/DefaultVerticalCRS.java    |  17 +-
 .../apache/sis/referencing/crs/package-info.java   |   2 +-
 .../org/apache/sis/referencing/cs/AbstractCS.java  | 185 +++++++++++++--------
 .../apache/sis/referencing/cs/AxesConvention.java  |  14 +-
 .../apache/sis/referencing/cs/DefaultAffineCS.java |  33 ++--
 .../sis/referencing/cs/DefaultCartesianCS.java     |  71 +++++---
 .../sis/referencing/cs/DefaultCompoundCS.java      |  44 ++++-
 .../sis/referencing/cs/DefaultCylindricalCS.java   |  36 ++--
 .../sis/referencing/cs/DefaultEllipsoidalCS.java   |  43 ++---
 .../apache/sis/referencing/cs/DefaultLinearCS.java |  34 ++--
 .../sis/referencing/cs/DefaultParametricCS.java    |  34 ++--
 .../apache/sis/referencing/cs/DefaultPolarCS.java  |  34 ++--
 .../sis/referencing/cs/DefaultSphericalCS.java     |  41 ++---
 .../apache/sis/referencing/cs/DefaultTimeCS.java   |  34 ++--
 .../sis/referencing/cs/DefaultUserDefinedCS.java   |  34 ++--
 .../sis/referencing/cs/DefaultVerticalCS.java      |  34 ++--
 .../org/apache/sis/referencing/cs/Normalizer.java  |  16 +-
 .../org/apache/sis/referencing/cs/SubTypes.java    |  31 ++++
 .../apache/sis/referencing/cs/package-info.java    |   2 +-
 .../sis/referencing/util/ReferencingUtilities.java |  28 ++++
 .../sis/referencing/cs/DefaultPolarCSTest.java     |   4 +-
 34 files changed, 828 insertions(+), 449 deletions(-)

diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
index fef4d1925c..4ef84ef025 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
@@ -231,7 +231,7 @@ public final class GridOrientation implements Serializable {
         if (variant == crsVariant) {
             return this;
         }
-        if (variant == AxesConvention.NORMALIZED) {
+        if (variant == AxesConvention.NORMALIZED || variant == AxesConvention.ORIGINAL) {
             throw new IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedArgumentValue_1, variant));
         }
         return new GridOrientation(flippedAxes, variant, canReorderGridAxis);
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
index 70292de25b..0dab42d7cc 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractCRS.java
@@ -18,7 +18,6 @@ package org.apache.sis.referencing.crs;
 
 import java.util.Map;
 import java.util.EnumMap;
-import java.util.HashMap;
 import java.util.Objects;
 import java.util.ConcurrentModificationException;
 import jakarta.xml.bind.annotation.XmlType;
@@ -33,8 +32,6 @@ import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.GeneralDerivedCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.referencing.AbstractReferenceSystem;
-import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.referencing.util.ReferencingUtilities;
 import org.apache.sis.metadata.internal.ImplementationHelper;
 import org.apache.sis.referencing.cs.AxesConvention;
@@ -45,6 +42,7 @@ import org.apache.sis.io.wkt.Formatter;
 import static org.apache.sis.util.Utilities.deepEquals;
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 import static org.apache.sis.referencing.util.WKTUtilities.toFormattable;
+import org.apache.sis.util.resources.Errors;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.metadata.Identifier;
@@ -82,7 +80,7 @@ import org.opengis.metadata.Identifier;
  * without synchronization.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see AbstractCS
  * @see org.apache.sis.referencing.datum.AbstractDatum
@@ -105,7 +103,7 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -7433284548909530047L;
+    private static final long serialVersionUID = -4925108294894867598L;
 
     /**
      * The coordinate system.
@@ -120,11 +118,22 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe
 
     /**
      * Other coordinate reference systems computed from this CRS by the application of an axes convention.
-     * Created only when first needed.
+     * This map is shared by all instances derived from the same original {@code AbstractCRS} instance.
+     * It is serialized in order to preserve metadata about the original instance.
+     * All accesses to this map shall be synchronized on {@code forConvention}.
      *
      * @see #forConvention(AxesConvention)
      */
-    private transient Map<AxesConvention,AbstractCRS> forConvention;
+    final EnumMap<AxesConvention,AbstractCRS> forConvention;
+
+    /**
+     * Creates the value to assign to the {@link #forConvention} map by constructors.
+     */
+    private EnumMap<AxesConvention,AbstractCRS> forConvention() {
+        var m = new EnumMap<AxesConvention,AbstractCRS>(AxesConvention.class);
+        m.put(AxesConvention.ORIGINAL, this);
+        return m;
+    }
 
     /**
      * Creates a coordinate reference system from the given properties and coordinate system.
@@ -164,10 +173,27 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe
      * @param properties  the properties to be given to the coordinate reference system.
      * @param cs          the coordinate system.
      */
+    @SuppressWarnings("this-escape")
     public AbstractCRS(final Map<String,?> properties, final CoordinateSystem cs) {
         super(properties);
         ensureNonNull("cs", cs);
         coordinateSystem = cs;
+        forConvention = forConvention();
+    }
+
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     *
+     * @param original  the original coordinate system from which to derive a new one.
+     * @param id        new identifier for this CRS, or {@code null} if none.
+     * @param cs        coordinate system with new axis order or units of measurement.
+     *
+     * @see #createSameType(CoordinateSystem)
+     */
+    AbstractCRS(final AbstractCRS original, final Identifier id, final CoordinateSystem cs) {
+        super(ReferencingUtilities.getPropertiesWithoutIdentifiers(original, (id == null) ? null : Map.of(IDENTIFIERS_KEY, id)));
+        coordinateSystem = cs;
+        forConvention = original.forConvention;
     }
 
     /**
@@ -181,9 +207,11 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe
      *
      * @see #castOrCopy(CoordinateReferenceSystem)
      */
+    @SuppressWarnings("this-escape")
     protected AbstractCRS(final CoordinateReferenceSystem crs) {
         super(crs);
         coordinateSystem = crs.getCoordinateSystem();
+        forConvention = forConvention();
     }
 
     /**
@@ -277,14 +305,6 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe
         return null;
     }
 
-    /**
-     * Returns the existing CRS for the given convention, or {@code null} if none.
-     */
-    final AbstractCRS getCached(final AxesConvention convention) {
-        assert Thread.holdsLock(this);
-        return (forConvention != null) ? forConvention.get(convention) : null;
-    }
-
     /**
      * Sets the CRS for the given axes convention.
      *
@@ -292,15 +312,11 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe
      * @return the cached CRS. May be different than the given {@code crs} if an existing instance has been found.
      */
     final AbstractCRS setCached(final AxesConvention convention, AbstractCRS crs) {
-        assert Thread.holdsLock(this);
-        if (forConvention == null) {
-            forConvention = new EnumMap<>(AxesConvention.class);
-        } else if (crs != this) {
-            for (final AbstractCRS existing : forConvention.values()) {
-                if (crs.equals(existing)) {
-                    crs = existing;
-                    break;
-                }
+        assert Thread.holdsLock(forConvention);
+        for (final AbstractCRS existing : forConvention.values()) {
+            if (crs.equals(existing, ComparisonMode.IGNORE_METADATA)) {
+                crs = existing;
+                break;
             }
         }
         if (forConvention.put(convention, crs) != null) {
@@ -318,42 +334,39 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe
      *
      * @see AbstractCS#forConvention(AxesConvention)
      */
-    public synchronized AbstractCRS forConvention(final AxesConvention convention) {
+    public AbstractCRS forConvention(final AxesConvention convention) {
         ensureNonNull("convention", convention);
-        AbstractCRS crs = getCached(convention);
-        if (crs == null) {
-            final AbstractCS cs = AbstractCS.castOrCopy(coordinateSystem);
-            final AbstractCS candidate = cs.forConvention(convention);
-            if (candidate == cs) {
-                crs = this;
-            } else {
-                /*
-                 * Copy properties (scope, domain of validity) except the identifier (e.g. "EPSG:4326")
-                 * because the modified CRS is no longer conform to the authoritative definition.
-                 * If name contains a namespace (e.g. "EPSG"), remove that namespace for the same reason.
-                 * For example, "EPSG:WGS 84" will become simply "WGS 84".
-                 */
-                Map<String,?> properties = IdentifiedObjects.getProperties(this, IDENTIFIERS_KEY);
-                Identifier name = getName();
-                if (name.getCodeSpace() != null || name.getAuthority() != null) {
-                    name = new NamedIdentifier(null, name.getCode());
-                    final Map<String,Object> copy = new HashMap<>(properties);
-                    copy.put(NAME_KEY, name);
-                    properties = copy;
+        synchronized (forConvention) {
+            AbstractCRS crs = forConvention.get(convention);
+            if (crs == null) {
+                final AbstractCRS original = forConvention.get(AxesConvention.ORIGINAL);
+                final AbstractCS cs = AbstractCS.castOrCopy(original.coordinateSystem);
+                final AbstractCS candidate = cs.forConvention(convention);
+                if (candidate.equals(cs, ComparisonMode.IGNORE_METADATA)) {
+                    crs = original;
+                } else if (original != this && candidate.equals(coordinateSystem, ComparisonMode.IGNORE_METADATA)) {
+                    crs = this;
+                } else try {
+                    crs = createSameType(candidate);
+                } catch (ClassCastException e) {
+                    throw new IllegalArgumentException(Errors.format(Errors.Keys.CanNotCompute_1, convention), e);
                 }
-                crs = createSameType(properties, candidate);
+                crs = setCached(convention, crs);
             }
-            crs = setCached(convention, crs);
+            return crs;
         }
-        return crs;
     }
 
     /**
      * Returns a coordinate reference system of the same type as this CRS but with different axes.
      * This method shall be overridden by all {@code AbstractCRS} subclasses in this package.
+     *
+     * @param  cs  the coordinate system with new axes.
+     * @return new CRS of the same type and datum than this CRS, but with the given axes.
+     * @throws ClassCastException if the type of the given coordinate system is invalid.
      */
-    AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        return new AbstractCRS(properties, cs);
+    AbstractCRS createSameType(final CoordinateSystem cs) {
+        return new AbstractCRS(this, null, cs);
     }
 
     /**
@@ -525,9 +538,10 @@ public class AbstractCRS extends AbstractReferenceSystem implements CoordinateRe
      */
     AbstractCRS() {
         super(org.apache.sis.referencing.util.NilReferencingObject.INSTANCE);
+        forConvention = forConvention();
         /*
          * The coordinate system is mandatory for SIS working. We do not verify its presence here
-         * because the verification would have to be done in an 'afterMarshal(…)' method and throwing
+         * because the verification would have to be done in an `afterMarshal(…)` method and throwing
          * an exception in that method causes the whole unmarshalling to fail. But the SC_CRS adapter
          * does some verifications.
          */
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
index fb5a177d56..83c3e4942f 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractDerivedCRS.java
@@ -47,9 +47,7 @@ import static org.apache.sis.util.Utilities.deepEquals;
 
 
 /**
- * A coordinate reference system that is defined by its coordinate
- * {@linkplain org.apache.sis.referencing.operation.DefaultConversion conversion} from another CRS
- * (not by a {@linkplain org.apache.sis.referencing.datum.AbstractDatum datum}).
+ * A coordinate reference system that is defined by its coordinate conversion from another CRS.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  *
@@ -108,6 +106,16 @@ abstract class AbstractDerivedCRS<C extends Conversion> extends AbstractCRS impl
         conversionFromBase = createConversionFromBase(properties, baseCRS, conversion);
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     */
+    AbstractDerivedCRS(final AbstractDerivedCRS<C> original, final CoordinateSystem derivedCS) {
+        super(original, null, derivedCS);
+        final Conversion conversion = original.conversionFromBase;
+        conversionFromBase = createConversionFromBase(null, (SingleCRS) conversion.getSourceCRS(), conversion);
+    }
+
     /**
      * For {@link DefaultDerivedCRS#DefaultDerivedCRS(Map, SingleCRS, CoordinateReferenceSystem, OperationMethod,
      * MathTransform, CoordinateSystem)} constructor only (<strong>not legal for {@code ProjectedCRS}</strong>).
@@ -150,7 +158,7 @@ abstract class AbstractDerivedCRS<C extends Conversion> extends AbstractCRS impl
      * (through {@link DefaultConversion}) the {@link #getCoordinateSystem()} method on {@code this}.
      * Consequently, this method shall be invoked only after the construction of this {@code AbstractDerivedCRS}
      * instance is advanced enough for allowing the {@code getCoordinateSystem()} method to execute.
-     * Subclasses may consider to make the {@code getCoordinateSystem()} method final for better guarantees.</p>
+     * Subclasses should make their {@code getCoordinateSystem()} method final for better guarantees.</p>
      */
     private C createConversionFromBase(final Map<String,?> properties, final SingleCRS baseCRS, final Conversion conversion) {
         MathTransformFactory factory = null;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
index 8e0d0f126e..d3b4d4945b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
@@ -37,7 +37,6 @@ import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.apache.sis.referencing.AbstractReferenceSystem;
-import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.DefaultCompoundCS;
 import org.apache.sis.referencing.util.WKTKeywords;
@@ -112,7 +111,7 @@ import org.opengis.referencing.crs.ParametricCRS;
  * SIS factories and static constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createCompoundCRS(String)
  *
@@ -131,7 +130,8 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
      * May be the same reference as {@link #singles}.
      *
      * <p><b>Consider this field as final!</b>
-     * This field is modified only at construction and unmarshalling time by {@link #setComponents(List)}</p>
+     * This field is set at construction time by {@link #setComponents(Map, List)} and at
+     * unmarshalling time by {@link #setXMLComponents(CoordinateReferenceSystem[])}.</p>
      */
     @SuppressWarnings("serial")     // Most SIS implementations are serializable.
     private List<? extends CoordinateReferenceSystem> components;
@@ -187,15 +187,16 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
      */
     public DefaultCompoundCRS(final Map<String,?> properties, final CoordinateReferenceSystem... components) {
         super(properties, createCoordinateSystem(properties, components));
-        setComponents(Arrays.asList(components));
-        /*
-         * 'singles' is computed by the above method call. Now verify that we do not have an ellipsoidal
-         * height with a geographic or projected CRS (see https://issues.apache.org/jira/browse/SIS-303).
-         * Note that this is already be done if the given array does not contain nested CompoundCRS.
-         */
-        if (singles != this.components) {
-            verify(properties, singles.toArray(SingleCRS[]::new));
-        }
+        setComponents(properties, Arrays.asList(components));
+    }
+
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #forConvention(AxesConvention)} method only.
+     */
+    private DefaultCompoundCRS(final DefaultCompoundCRS original, final CoordinateReferenceSystem[] components) {
+        super(original, null, createCoordinateSystem(null, components));
+        setComponents(null, Arrays.asList(components));
     }
 
     /**
@@ -276,7 +277,7 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
             this.components = that.components;
             this.singles    = that.singles;
         } else {
-            setComponents(crs.getComponents());
+            setComponents(null, crs.getComponents());
         }
     }
 
@@ -337,13 +338,21 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
      * Computes the {@link #components} and {@link #singles} fields from the given CRS list.
      * If the two lists have the same content, then the two fields will reference the same list.
      *
+     * @param  properties  properties of the compound CRS to construct, or {@code null} if unknown.
+     * @param  elements    components to set for the CRS.
+     *
      * @see #getComponents()
      */
-    private void setComponents(final List<? extends CoordinateReferenceSystem> crs) {
-        if (setSingleComponents(crs)) {
+    private void setComponents(final Map<String,?> properties, final List<? extends CoordinateReferenceSystem> elements) {
+        if (setSingleComponents(elements)) {
             components = singles;                           // Shares the same list.
         } else {
-            components = UnmodifiableArrayList.wrap(crs.toArray(CoordinateReferenceSystem[]::new));
+            components = UnmodifiableArrayList.wrap(elements.toArray(CoordinateReferenceSystem[]::new));
+            /*
+             * Verify that we do not have an ellipsoidal height with a geographic or projected CRS.
+             * https://issues.apache.org/jira/browse/SIS-303
+             */
+            verify(properties, singles.toArray(SingleCRS[]::new));
         }
     }
 
@@ -372,9 +381,9 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
      *
      * @see #getSingleComponents()
      */
-    private boolean setSingleComponents(final List<? extends CoordinateReferenceSystem> crs) {
-        final List<SingleCRS> flattened = new ArrayList<>(crs.size());
-        final boolean identical = ReferencingUtilities.getSingleComponents(crs, flattened);
+    private boolean setSingleComponents(final List<? extends CoordinateReferenceSystem> elements) {
+        final List<SingleCRS> flattened = new ArrayList<>(elements.size());
+        final boolean identical = ReferencingUtilities.getSingleComponents(elements, flattened);
         singles = UnmodifiableArrayList.wrap(flattened.toArray(SingleCRS[]::new));
         return identical;
     }
@@ -389,6 +398,7 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
     @SuppressWarnings("unchecked")
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final List<? extends CoordinateReferenceSystem> components = this.components;
         if (components instanceof CheckedContainer<?>) {
             final Class<?> type = ((CheckedContainer<?>) components).getElementType();
@@ -471,49 +481,51 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
     }
 
     /**
-     * {@inheritDoc}
-     *
-     * <p>If the given convention is {@link AxesConvention#DISPLAY_ORIENTED} or
-     * {@link AxesConvention#NORMALIZED}, then this method will also reorder the components
-     * with horizontal CRS (geodetic or projected) first, then vertical CRS, then temporal CRS.</p>
+     * Returns a compound CRS equivalent to this one but with axes rearranged according the given convention.
+     * This method first reorders the axes of each individual CRS {@linkplain #getComponents() component}.
+     * Then, if the given convention is {@link AxesConvention#DISPLAY_ORIENTED} or {@link AxesConvention#NORMALIZED},
+     * this method will also reorder the components with horizontal CRS (geodetic or projected) first,
+     * then vertical CRS, then temporal CRS.
      *
      * @return {@inheritDoc}
      */
     @Override
-    public synchronized DefaultCompoundCRS forConvention(final AxesConvention convention) {
+    public DefaultCompoundCRS forConvention(final AxesConvention convention) {
         ArgumentChecks.ensureNonNull("convention", convention);
-        DefaultCompoundCRS crs = (DefaultCompoundCRS) getCached(convention);
-        if (crs == null) {
-            crs = this;
-            boolean changed = false;
-            final boolean reorderCRS = convention.ordinal() <= AxesConvention.DISPLAY_ORIENTED.ordinal();
-            final List<? extends CoordinateReferenceSystem> components = reorderCRS ? singles : this.components;
-            final CoordinateReferenceSystem[] newComponents = new CoordinateReferenceSystem[components.size()];
-            for (int i=0; i<newComponents.length; i++) {
-                CoordinateReferenceSystem component = components.get(i);
-                AbstractCRS m = castOrCopy(component);
-                if (m != (m = m.forConvention(convention))) {
-                    component = m;
-                    changed = true;
+        synchronized (forConvention) {
+            DefaultCompoundCRS crs = (DefaultCompoundCRS) forConvention.get(convention);
+            if (crs == null) {
+                crs = (DefaultCompoundCRS) forConvention.get(AxesConvention.ORIGINAL);
+                boolean changed = false;
+                final boolean reorderCRS = convention.ordinal() <= AxesConvention.DISPLAY_ORIENTED.ordinal();
+                final List<? extends CoordinateReferenceSystem> elements = reorderCRS ? crs.singles : crs.components;
+                final CoordinateReferenceSystem[] newComponents = new CoordinateReferenceSystem[elements.size()];
+                for (int i=0; i<newComponents.length; i++) {
+                    CoordinateReferenceSystem component = elements.get(i);
+                    AbstractCRS m = castOrCopy(component);
+                    if (m != (m = m.forConvention(convention))) {
+                        component = m;
+                        changed = true;
+                    }
+                    newComponents[i] = component;
                 }
-                newComponents[i] = component;
-            }
-            if (changed) {
-                if (reorderCRS) {
-                    Arrays.sort(newComponents, SubTypes.BY_TYPE);   // This array typically has less than 4 elements.
+                if (changed) {
+                    if (reorderCRS) {
+                        Arrays.sort(newComponents, SubTypes.BY_TYPE);   // This array typically has less than 4 elements.
+                    }
+                    crs = new DefaultCompoundCRS(crs, newComponents);
                 }
-                crs = new DefaultCompoundCRS(IdentifiedObjects.getProperties(this, IDENTIFIERS_KEY), newComponents);
+                crs = (DefaultCompoundCRS) setCached(convention, crs);
             }
-            crs = (DefaultCompoundCRS) setCached(convention, crs);
+            return crs;
         }
-        return crs;
     }
 
     /**
      * Should never be invoked since we override {@link AbstractCRS#forConvention(AxesConvention)}.
      */
     @Override
-    final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
+    final AbstractCRS createSameType(final CoordinateSystem cs) {
         throw new AssertionError();
     }
 
@@ -577,19 +589,19 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
     protected String formatTo(final Formatter formatter) {
         WKTUtilities.appendName(this, formatter, null);
         final Convention convention = formatter.getConvention();
-        final List<? extends CoordinateReferenceSystem> crs;
+        final List<? extends CoordinateReferenceSystem> elements;
         final boolean isStandardCompliant;
         final boolean isWKT1 = convention.majorVersion() == 1;
         if (isWKT1 || convention == Convention.INTERNAL) {
-            crs = getComponents();
+            elements = getComponents();
             isStandardCompliant = true;                     // WKT 1 does not put any restriction.
         } else {
-            crs = getSingleComponents();
-            isStandardCompliant = isStandardCompliant(crs);
+            elements = getSingleComponents();
+            isStandardCompliant = isStandardCompliant(elements);
         }
-        for (final CoordinateReferenceSystem element : crs) {
+        for (final CoordinateReferenceSystem component : elements) {
             formatter.newLine();
-            formatter.append(WKTUtilities.toFormattable(element));
+            formatter.append(WKTUtilities.toFormattable(component));
         }
         formatter.newLine();                                // For writing the ID[…] element on its own line.
         if (!isStandardCompliant) {
@@ -624,7 +636,7 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
         singles    = List.of();
         /*
          * At least one component CRS is mandatory for SIS working. We do not verify their presence here
-         * because the verification would have to be done in an 'afterMarshal(…)' method and throwing an
+         * because the verification would have to be done in an `afterMarshal(…)` method and throwing an
          * exception in that method causes the whole unmarshalling to fail.  But the SC_CRS adapter does
          * some verifications (indirectly, by testing for coordinate system existence).
          */
@@ -652,8 +664,8 @@ public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
     /**
      * Invoked by JAXB for setting the components of this compound CRS.
      */
-    private void setXMLComponents(final CoordinateReferenceSystem[] crs) {
-        components = setSingleComponents(Arrays.asList(crs)) ? singles : UnmodifiableArrayList.wrap(crs);
-        setCoordinateSystem("coordinateSystem", createCoordinateSystem(null, crs));
+    private void setXMLComponents(final CoordinateReferenceSystem[] elements) {
+        components = setSingleComponents(Arrays.asList(elements)) ? singles : UnmodifiableArrayList.wrap(elements);
+        setCoordinateSystem("coordinateSystem", createCoordinateSystem(null, elements));
     }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
index feb4007d2e..1252bb3b01 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
@@ -94,7 +94,7 @@ import org.opengis.referencing.cs.ParametricCS;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createDerivedCRS(String)
  *
@@ -171,6 +171,14 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
         super(properties, baseCRS, conversion, derivedCS);
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     */
+    DefaultDerivedCRS(final DefaultDerivedCRS original, final CoordinateSystem derivedCS) {
+        super(original, derivedCS);
+    }
+
     /**
      * Creates a derived CRS from a math transform. The given {@code MathTransform} shall transform coordinate
      * values specifically from the {@code baseCRS} to {@code this} CRS (optionally with an interpolation CRS);
@@ -468,11 +476,14 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
 
     /**
      * Returns a coordinate reference system of the same type as this CRS but with different axes.
+     *
+     * @param  cs  the coordinate system with new axes.
+     * @return new CRS of the same type and datum than this CRS, but with the given axes.
+     * @throws ClassCastException if the type of the given coordinate system is invalid.
      */
     @Override
-    AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem derivedCS) {
-        final Conversion conversionFromBase = super.getConversionFromBase();
-        return new DefaultDerivedCRS(properties, (SingleCRS) conversionFromBase.getSourceCRS(), conversionFromBase, derivedCS);
+    AbstractCRS createSameType(final CoordinateSystem derivedCS) {
+        return new DefaultDerivedCRS(this, derivedCS);
     }
 
     /**
@@ -627,6 +638,11 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
             super(other);
         }
 
+        /** Creates a new CRS derived from the specified one, but with different axis order or unit. */
+        private Geodetic(final Geodetic original, final CoordinateSystem derivedCS) {
+            super(original, derivedCS);
+        }
+
         /** Creates a new geodetic CRS from the given properties. */
         Geodetic(Map<String,?> properties, GeodeticCRS baseCRS, Conversion conversion, CoordinateSystem derivedCS) {
             super(properties, baseCRS, conversion, derivedCS);
@@ -645,10 +661,8 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
         }
 
         /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
-        @Override AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem derivedCS) {
-            final Conversion conversionFromBase = getConversionFromBase();
-            return new Geodetic(properties, (GeodeticCRS) conversionFromBase.getSourceCRS(),
-                    conversionFromBase, derivedCS);
+        @Override AbstractCRS createSameType(final CoordinateSystem derivedCS) {
+            return new Geodetic(this, derivedCS);
         }
 
         /** Returns the WKT keyword for this derived CRS type. */
@@ -675,6 +689,11 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
             super(other);
         }
 
+        /** Creates a new CRS derived from the specified one, but with different axis order or unit. */
+        private Vertical(final Vertical original, final VerticalCS derivedCS) {
+            super(original, derivedCS);
+        }
+
         /** Creates a new vertical CRS from the given properties. */
         Vertical(Map<String,?> properties, VerticalCRS baseCRS, Conversion conversion, VerticalCS derivedCS) {
             super(properties, baseCRS, conversion, derivedCS);
@@ -698,10 +717,8 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
         }
 
         /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
-        @Override AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem derivedCS) {
-            final Conversion conversionFromBase = getConversionFromBase();
-            return new Vertical(properties, (VerticalCRS) conversionFromBase.getSourceCRS(),
-                    conversionFromBase, (VerticalCS) derivedCS);
+        @Override AbstractCRS createSameType(final CoordinateSystem derivedCS) {
+            return new Vertical(this, (VerticalCS) derivedCS);
         }
 
         /** Returns the WKT keyword for this derived CRS type. */
@@ -728,6 +745,11 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
             super(other);
         }
 
+        /** Creates a new CRS derived from the specified one, but with different axis order or unit. */
+        private Temporal(final Temporal original, final TimeCS derivedCS) {
+            super(original, derivedCS);
+        }
+
         /** Creates a new temporal CRS from the given properties. */
         Temporal(Map<String,?> properties, TemporalCRS baseCRS, Conversion conversion, TimeCS derivedCS) {
             super(properties, baseCRS, conversion, derivedCS);
@@ -751,10 +773,8 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
         }
 
         /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
-        @Override AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem derivedCS) {
-            final Conversion conversionFromBase = getConversionFromBase();
-            return new Temporal(properties, (TemporalCRS) conversionFromBase.getSourceCRS(),
-                    conversionFromBase, (TimeCS) derivedCS);
+        @Override AbstractCRS createSameType(final CoordinateSystem derivedCS) {
+            return new Temporal(this, (TimeCS) derivedCS);
         }
 
         /** Returns the WKT keyword for this derived CRS type. */
@@ -781,6 +801,11 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
             super(other);
         }
 
+        /** Creates a new CRS derived from the specified one, but with different axis order or unit. */
+        private Parametric(final Parametric original, final ParametricCS derivedCS) {
+            super(original, derivedCS);
+        }
+
         /** Creates a new parametric CRS from the given properties. */
         Parametric(Map<String,?> properties, ParametricCRS baseCRS, Conversion conversion, ParametricCS derivedCS) {
             super(properties, baseCRS, conversion, derivedCS);
@@ -804,10 +829,8 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
         }
 
         /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
-        @Override AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem derivedCS) {
-            final Conversion conversionFromBase = getConversionFromBase();
-            return new Parametric(properties, (ParametricCRS) conversionFromBase.getSourceCRS(),
-                    conversionFromBase, (ParametricCS) derivedCS);
+        @Override AbstractCRS createSameType(final CoordinateSystem derivedCS) {
+            return new Parametric(this, (ParametricCS) derivedCS);
         }
 
         /** Returns the WKT keyword for this derived CRS type. */
@@ -837,6 +860,11 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
             super(other);
         }
 
+        /** Creates a new CRS derived from the specified one, but with different axis order or unit. */
+        private Engineering(final Engineering original, final CoordinateSystem derivedCS) {
+            super(original, derivedCS);
+        }
+
         /** Creates a new engineering CRS from the given properties. */
         Engineering(Map<String,?> properties, EngineeringCRS baseCRS, Conversion conversion, CoordinateSystem derivedCS) {
             super(properties, baseCRS, conversion, derivedCS);
@@ -855,9 +883,8 @@ public class DefaultDerivedCRS extends AbstractDerivedCRS<Conversion> implements
         }
 
         /** Returns a coordinate reference system of the same type as this CRS but with different axes. */
-        @Override AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem derivedCS) {
-            final Conversion conversionFromBase = getConversionFromBase();
-            return new Engineering(properties, (EngineeringCRS) conversionFromBase.getSourceCRS(), conversionFromBase, derivedCS);
+        @Override AbstractCRS createSameType(final CoordinateSystem derivedCS) {
+            return new Engineering(this, derivedCS);
         }
 
         /** Returns the WKT keyword for this derived CRS type. */
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
index fdd1f87bad..3467f5b3d9 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
@@ -60,7 +60,7 @@ import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
  * in the javadoc, this condition holds if all components were created using only SIS factories and static constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.datum.DefaultEngineeringDatum
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createEngineeringCRS(String)
@@ -138,14 +138,23 @@ public class DefaultEngineeringCRS extends AbstractCRS implements EngineeringCRS
      * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createEngineeringCRS(Map, EngineeringDatum, CoordinateSystem)
      */
     public DefaultEngineeringCRS(final Map<String,?> properties,
-                                 final EngineeringDatum   datum,
-                                 final CoordinateSystem      cs)
+                                 final EngineeringDatum datum,
+                                 final CoordinateSystem cs)
     {
         super(properties, cs);
         ensureNonNull("datum", datum);
         this.datum = datum;
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     */
+    private DefaultEngineeringCRS(final DefaultEngineeringCRS original, final CoordinateSystem cs) {
+        super(original, null, cs);
+        datum = original.datum;
+    }
+
     /**
      * Constructs a new coordinate reference system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -216,10 +225,13 @@ public class DefaultEngineeringCRS extends AbstractCRS implements EngineeringCRS
 
     /**
      * Returns a coordinate reference system of the same type as this CRS but with different axes.
+     *
+     * @param  cs  the coordinate system with new axes.
+     * @return new CRS of the same type and datum than this CRS, but with the given axes.
      */
     @Override
-    final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        return new DefaultEngineeringCRS(properties, datum, cs);
+    final AbstractCRS createSameType(final CoordinateSystem cs) {
+        return new DefaultEngineeringCRS(this, cs);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
index 45d8f08ef0..afa8eacab4 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
@@ -69,7 +69,7 @@ import org.apache.sis.referencing.cs.AxesConvention;
  * in the javadoc, this condition holds if all components were created using only SIS factories and static constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createGeocentricCRS(String)
  *
@@ -82,17 +82,6 @@ public class DefaultGeocentricCRS extends DefaultGeodeticCRS implements Geocentr
      */
     private static final long serialVersionUID = 6784642848287659827L;
 
-    /**
-     * For {@link #createSameType(Map, CoordinateSystem)} usage only.
-     * This constructor does not verify the coordinate system type.
-     */
-    private DefaultGeocentricCRS(final Map<String,?>    properties,
-                                 final GeodeticDatum    datum,
-                                 final CoordinateSystem cs)
-    {
-        super(properties, datum, cs);
-    }
-
     /**
      * Creates a coordinate reference system from the given properties, datum and coordinate system.
      * The properties given in argument follow the same rules as for the
@@ -159,6 +148,15 @@ public class DefaultGeocentricCRS extends DefaultGeodeticCRS implements Geocentr
         super(properties, datum, cs);
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     * This constructor does not verify the coordinate system type.
+     */
+    private DefaultGeocentricCRS(final DefaultGeocentricCRS original, final CoordinateSystem cs) {
+        super(original, null, cs);
+    }
+
     /**
      * Constructs a new coordinate reference system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -228,10 +226,13 @@ public class DefaultGeocentricCRS extends DefaultGeodeticCRS implements Geocentr
 
     /**
      * Returns a coordinate reference system of the same type as this CRS but with different axes.
+     *
+     * @param  cs  the coordinate system with new axes.
+     * @return new CRS of the same type and datum than this CRS, but with the given axes.
      */
     @Override
-    final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        return new DefaultGeocentricCRS(properties, super.getDatum(), cs);
+    final AbstractCRS createSameType(final CoordinateSystem cs) {
+        return new DefaultGeocentricCRS(this, cs);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
index 858ebe70b4..d87eac98bf 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
@@ -44,6 +44,9 @@ import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.measure.Units;
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.metadata.Identifier;
+
 
 /**
  * A 2- or 3-dimensional coordinate reference system based on a geodetic datum.
@@ -104,6 +107,15 @@ class DefaultGeodeticCRS extends AbstractCRS implements GeodeticCRS { // If made
         this.datum = datum;
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     */
+    DefaultGeodeticCRS(final DefaultGeodeticCRS original, final Identifier id, final CoordinateSystem cs) {
+        super(original, id, cs);
+        datum = original.datum;
+    }
+
     /**
      * Constructs a new coordinate reference system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -149,10 +161,14 @@ class DefaultGeodeticCRS extends AbstractCRS implements GeodeticCRS { // If made
     /**
      * Returns a coordinate reference system of the same type as this CRS but with different axes.
      * This method shall be overridden by all {@code DefaultGeodeticCRS} subclasses in this package.
+     *
+     * @param  cs  the coordinate system with new axes.
+     * @return new CRS of the same type and datum than this CRS, but with the given axes.
+     * @throws ClassCastException if the type of the given coordinate system is invalid.
      */
     @Override
-    AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        return new DefaultGeodeticCRS(properties, datum, cs);
+    AbstractCRS createSameType(final CoordinateSystem cs) {
+        return new DefaultGeodeticCRS(this, null, cs);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
index 111f571eeb..0d3d037918 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
@@ -17,7 +17,6 @@
 package org.apache.sis.referencing.crs;
 
 import java.util.Map;
-import java.util.HashMap;
 import java.util.Arrays;
 import jakarta.xml.bind.annotation.XmlTransient;
 import org.opengis.referencing.datum.GeodeticDatum;
@@ -85,7 +84,7 @@ import org.opengis.metadata.Identifier;
  * in the javadoc, this condition holds if all components were created using only SIS factories and static constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createGeographicCRS(String)
  *
@@ -157,6 +156,14 @@ public class DefaultGeographicCRS extends DefaultGeodeticCRS implements Geograph
         super(properties, datum, cs);
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     */
+    private DefaultGeographicCRS(final DefaultGeographicCRS original, final Identifier id, final EllipsoidalCS cs) {
+        super(original, id, cs);
+    }
+
     /**
      * Constructs a new coordinate reference system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -242,9 +249,14 @@ public class DefaultGeographicCRS extends DefaultGeodeticCRS implements Geograph
      * EPSG:4269 or EPSG:4326, then this method magically add the CRS:27, CRS:83 or CRS:84 identifier.
      * Without this special case, the normal behavior would be no identifier. The expected behavior is
      * that {@code CommonCRS.WGS84.normalizedGeographic()} returns a CRS having the "CRS:84" identifier.
+     *
+     * @param  cs  the coordinate system with new axes.
+     * @return new CRS of the same type and datum than this CRS, but with the given axes.
+     * @throws ClassCastException if the type of the given coordinate system is invalid.
      */
     @Override
-    final AbstractCRS createSameType(Map<String,?> properties, final CoordinateSystem cs) {
+    final AbstractCRS createSameType(final CoordinateSystem cs) {
+        Identifier id = null;
         final CoordinateSystemAxis axis = cs.getAxis(0);
         if (axis.getMinimumValue() == Longitude.MIN_VALUE &&
             axis.getMaximumValue() == Longitude.MAX_VALUE)    // For excluding the AxesConvention.POSITIVE_RANGE case.
@@ -253,16 +265,15 @@ public class DefaultGeographicCRS extends DefaultGeodeticCRS implements Geograph
                 if (EPSG.equals(identifier.getCodeSpace())) try {
                     final int i = Arrays.binarySearch(EPSG_CODES, Short.parseShort(identifier.getCode()));
                     if (i >= 0) {
-                        final Map<String,Object> c = new HashMap<>(properties);
-                        c.put(IDENTIFIERS_KEY, new ImmutableIdentifier(Citations.WMS, CRS, Short.toString(CRS_CODES[i])));
-                        properties = c;
+                        id = new ImmutableIdentifier(Citations.WMS, CRS, Short.toString(CRS_CODES[i]));
+                        break;
                     }
                 } catch (NumberFormatException e) {
                     // Okay to igore, because it is not the purpose of this method to disallow non-numeric codes.
                 }
             }
         }
-        return new DefaultGeographicCRS(properties, super.getDatum(), (EllipsoidalCS) cs);
+        return new DefaultGeographicCRS(this, id, (EllipsoidalCS) cs);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java
index e7c8162843..4d880b390f 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultImageCRS.java
@@ -51,7 +51,7 @@ import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
  * in the javadoc, this condition holds if all components were created using only SIS factories and static constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.datum.DefaultImageDatum
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createImageCRS(String)
@@ -131,6 +131,15 @@ public class DefaultImageCRS extends AbstractCRS implements ImageCRS {
         this.datum = datum;
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     */
+    private DefaultImageCRS(final DefaultImageCRS original, final AffineCS cs) {
+        super(original, null, cs);
+        datum = original.datum;
+    }
+
     /**
      * Constructs a new coordinate reference system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -211,10 +220,14 @@ public class DefaultImageCRS extends AbstractCRS implements ImageCRS {
 
     /**
      * Returns a coordinate reference system of the same type as this CRS but with different axes.
+     *
+     * @param  cs  the coordinate system with new axes.
+     * @return new CRS of the same type and datum than this CRS, but with the given axes.
+     * @throws ClassCastException if the type of the given coordinate system is invalid.
      */
     @Override
-    final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        return new DefaultImageCRS(properties, datum, (AffineCS) cs);
+    final AbstractCRS createSameType(final CoordinateSystem cs) {
+        return new DefaultImageCRS(this, (AffineCS) cs);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
index 13b595e8d3..c53b99bd4a 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultParametricCRS.java
@@ -51,7 +51,7 @@ import org.opengis.referencing.datum.ParametricDatum;
  * in the javadoc, this condition holds if all components were created using only SIS factories and static constants.
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.datum.DefaultParametricDatum
  * @see org.apache.sis.referencing.cs.DefaultParametricCS
@@ -124,13 +124,22 @@ public class DefaultParametricCRS extends AbstractCRS implements ParametricCRS {
      */
     public DefaultParametricCRS(final Map<String,?> properties,
                                 final ParametricDatum datum,
-                                final ParametricCS    cs)
+                                final ParametricCS cs)
     {
         super(properties, cs);
         ensureNonNull("datum", datum);
         this.datum = datum;
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     */
+    private DefaultParametricCRS(final DefaultParametricCRS original, final ParametricCS cs) {
+        super(original, null, cs);
+        datum = original.datum;
+    }
+
     /**
      * Constructs a new coordinate reference system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -212,10 +221,14 @@ public class DefaultParametricCRS extends AbstractCRS implements ParametricCRS {
 
     /**
      * Returns a coordinate reference system of the same type as this CRS but with different axes.
+     *
+     * @param  cs  the coordinate system with new axes.
+     * @return new CRS of the same type and datum than this CRS, but with the given axes.
+     * @throws ClassCastException if the type of the given coordinate system is invalid.
      */
     @Override
-    final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        return new DefaultParametricCRS(properties, datum, (ParametricCS) cs);
+    final AbstractCRS createSameType(final CoordinateSystem cs) {
+        return new DefaultParametricCRS(this, (ParametricCS) cs);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
index fe05bf66ff..870c17a9ae 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
@@ -59,7 +59,7 @@ import static org.apache.sis.referencing.util.WKTUtilities.toFormattable;
  * in the javadoc, this condition holds if all components were created using only SIS factories and static constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createProjectedCRS(String)
  *
@@ -135,6 +135,14 @@ public class DefaultProjectedCRS extends AbstractDerivedCRS<Projection> implemen
         super(properties, baseCRS, conversion, derivedCS);
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     */
+    private DefaultProjectedCRS(final DefaultProjectedCRS original, final CartesianCS derivedCS) {
+        super(original, derivedCS);
+    }
+
     /**
      * Constructs a new coordinate reference system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -261,11 +269,12 @@ public class DefaultProjectedCRS extends AbstractDerivedCRS<Projection> implemen
 
     /**
      * Returns a coordinate reference system of the same type as this CRS but with different axes.
+     *
+     * @throws ClassCastException if the type of the given coordinate system is invalid.
      */
     @Override
-    final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        final Projection conversion = super.getConversionFromBase();
-        return new DefaultProjectedCRS(properties, conversion.getSourceCRS(), conversion, (CartesianCS) cs);
+    final AbstractCRS createSameType(final CoordinateSystem cs) {
+        return new DefaultProjectedCRS(this, (CartesianCS) cs);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
index cbc9d8c614..3be96cf380 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
@@ -65,7 +65,7 @@ import static org.apache.sis.util.internal.StandardDateFormat.MILLIS_PER_SECOND;
  * in the javadoc, this condition holds if all components were created using only SIS factories and static constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.datum.DefaultTemporalDatum
  * @see org.apache.sis.referencing.cs.DefaultTimeCS
@@ -152,6 +152,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS {
      *
      * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createTemporalCRS(Map, TemporalDatum, TimeCS)
      */
+    @SuppressWarnings("this-escape")
     public DefaultTemporalCRS(final Map<String,?> properties,
                               final TemporalDatum datum,
                               final TimeCS        cs)
@@ -162,6 +163,16 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS {
         initializeConverter();
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     */
+    private DefaultTemporalCRS(final DefaultTemporalCRS original, final TimeCS cs) {
+        super(original, null, cs);
+        datum = original.datum;
+        initializeConverter();
+    }
+
     /**
      * Constructs a new coordinate reference system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -173,6 +184,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS {
      *
      * @see #castOrCopy(TemporalCRS)
      */
+    @SuppressWarnings("this-escape")
     protected DefaultTemporalCRS(final TemporalCRS crs) {
         super(crs);
         datum = crs.getDatum();
@@ -296,10 +308,12 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS {
 
     /**
      * Returns a coordinate reference system of the same type as this CRS but with different axes.
+     *
+     * @throws ClassCastException if the type of the given coordinate system is invalid.
      */
     @Override
-    final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        return new DefaultTemporalCRS(properties, datum, (TimeCS) cs);
+    final AbstractCRS createSameType(final CoordinateSystem cs) {
+        return new DefaultTemporalCRS(this, (TimeCS) cs);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
index e19f5bb66d..5554f4e16c 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
@@ -49,7 +49,7 @@ import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
  * in the javadoc, this condition holds if all components were created using only SIS factories and static constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.datum.DefaultVerticalDatum
  * @see org.apache.sis.referencing.cs.DefaultVerticalCS
@@ -129,6 +129,15 @@ public class DefaultVerticalCRS extends AbstractCRS implements VerticalCRS {
         this.datum = datum;
     }
 
+    /**
+     * Creates a new CRS derived from the specified one, but with different axis order or unit.
+     * This is for implementing the {@link #createSameType(CoordinateSystem)} method only.
+     */
+    private DefaultVerticalCRS(final DefaultVerticalCRS original, final VerticalCS cs) {
+        super(original, null, cs);
+        datum = original.datum;
+    }
+
     /**
      * Constructs a new coordinate reference system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -210,10 +219,12 @@ public class DefaultVerticalCRS extends AbstractCRS implements VerticalCRS {
 
     /**
      * Returns a coordinate reference system of the same type as this CRS but with different axes.
+     *
+     * @throws ClassCastException if the type of the given coordinate system is invalid.
      */
     @Override
-    final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        return new DefaultVerticalCRS(properties, datum, (VerticalCS) cs);
+    final AbstractCRS createSameType(final CoordinateSystem cs) {
+        return new DefaultVerticalCRS(this, (VerticalCS) cs);
     }
 
     /**
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/package-info.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/package-info.java
index ddd2d99cf2..d71a18efa6 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/package-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/package-info.java
@@ -73,7 +73,7 @@
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Cédric Briançon (Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   0.4
  */
 @XmlSchema(location = "http://schemas.opengis.net/gml/3.2.1/coordinateReferenceSystems.xsd",
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java
index d93d6d76f9..dd4d8d06ca 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java
@@ -19,6 +19,7 @@ package org.apache.sis.referencing.cs;
 import java.util.Map;
 import java.util.EnumMap;
 import java.util.Arrays;
+import java.util.ConcurrentModificationException;
 import java.util.logging.Logger;
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlElement;
@@ -42,6 +43,7 @@ import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.util.WKTUtilities;
 import org.apache.sis.referencing.util.AxisDirections;
 import org.apache.sis.referencing.util.WKTKeywords;
+import org.apache.sis.referencing.util.ReferencingUtilities;
 import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.system.Modules;
 import org.apache.sis.util.Utilities;
@@ -51,7 +53,6 @@ import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.io.wkt.ElementKind;
 import org.apache.sis.io.wkt.Formatter;
-import org.apache.sis.measure.Angle;
 import static org.apache.sis.util.ArgumentChecks.*;
 
 
@@ -75,7 +76,7 @@ import static org.apache.sis.util.ArgumentChecks.*;
  * objects and passed between threads without synchronization.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see DefaultCoordinateSystemAxis
  * @see org.apache.sis.referencing.crs.AbstractCRS
@@ -106,7 +107,7 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 6757665252533744744L;
+    private static final long serialVersionUID = 3394376886951478970L;
 
     /**
      * Return value for {@link #validateAxis(AxisDirection, Unit)}
@@ -126,11 +127,22 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
 
     /**
      * Other coordinate systems derived from this coordinate systems for other axes conventions.
-     * Created only when first needed.
+     * This map is shared by all instances derived from the same original {@code AbstractCRS} instance.
+     * It is serialized in order to preserve metadata about the original instance.
+     * All accesses to this map shall be synchronized on {@code forConvention}.
      *
      * @see #forConvention(AxesConvention)
      */
-    private transient Map<AxesConvention,AbstractCS> derived;
+    final EnumMap<AxesConvention,AbstractCS> forConvention;
+
+    /**
+     * Creates the value to assign to the {@link #forConvention} map by constructors.
+     */
+    private EnumMap<AxesConvention,AbstractCS> forConvention() {
+        var m = new EnumMap<AxesConvention,AbstractCS>(AxesConvention.class);
+        m.put(AxesConvention.ORIGINAL, this);
+        return m;
+    }
 
     /**
      * Constructs a coordinate system from a set of properties and a sequence of axes.
@@ -165,12 +177,25 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
      *
      * @param  properties  the properties to be given to the identified object.
      * @param  axes        the sequence of axes.
+     * @throws IllegalArgumentException if an axis has an illegal direction or an illegal unit of measurement.
      */
-    @SuppressWarnings("OverridableMethodCallInConstructor")
-    public AbstractCS(final Map<String,?> properties, CoordinateSystemAxis... axes) {
+    @SuppressWarnings({"this-escape", "OverridableMethodCallInConstructor"})
+    public AbstractCS(final Map<String,?> properties, final CoordinateSystemAxis... axes) {
         super(properties);
         ensureNonNull("axes", axes);
-        this.axes = axes = axes.clone();
+        this.axes = axes.clone();
+        validate(properties);
+        forConvention = forConvention();
+    }
+
+    /**
+     * Verifies that the coordinate system axes are non-null, then validates their directions and units of measurement.
+     * Subclasses may override for adding more verifications, for example ensuring that all axes are perpendicular.
+     *
+     * @param  properties  properties given at construction time, or {@code null} if none.
+     * @throws IllegalArgumentException if an axis has an illegal direction or an illegal unit of measurement.
+     */
+    void validate(final Map<String,?> properties) {
         for (int i=0; i<axes.length; i++) {
             final CoordinateSystemAxis axis = axes[i];
             ensureNonNullElement("axes", i, axis);
@@ -236,30 +261,22 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
     }
 
     /**
-     * Ensures that all known axes are perpendicular, ignoring unknown axis directions.
-     * This method can be invoked by the constructors of coordinate systems having this requirement.
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @param  original  the original coordinate system from which to derive a new one.
+     * @param  name      name of the new coordinate system, or {@code null} to inherit.
+     * @param  axes      the new axes. This array is not cloned.
+     * @param  share     whether the new CS should use a cache shared with the original CS.
+     * @throws IllegalArgumentException if an axis has illegal unit or direction.
      *
-     * @param  properties  properties given at construction time.
-     * @throws IllegalArgumentException if an illegal angle is found between two axes.
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
      */
-    final void ensurePerpendicularAxis(final Map<String,?> properties) throws IllegalArgumentException {
-        final int dimension = getDimension();
-        for (int i=0; i<dimension; i++) {
-            final AxisDirection axis0 = getAxis(i).getDirection();
-            for (int j=i; ++j<dimension;) {
-                final AxisDirection axis1 = getAxis(j).getDirection();
-                final Angle angle = CoordinateSystems.angle(axis0, axis1);
-                /*
-                 * The angle may be null for grid directions (COLUMN_POSITIVE, COLUMN_NEGATIVE,
-                 * ROW_POSITIVE, ROW_NEGATIVE). We conservatively accept those directions even if
-                 * they are not really for Cartesian CS because we do not know the grid geometry.
-                 */
-                if (angle != null && Math.abs(angle.degrees()) != 90) {
-                    throw new IllegalArgumentException(Resources.forProperties(properties).getString(
-                            Resources.Keys.NonPerpendicularDirections_2, axis0, axis1));
-                }
-            }
-        }
+    @SuppressWarnings({"this-escape", "OverridableMethodCallInConstructor"})
+    AbstractCS(final AbstractCS original, final String name, final CoordinateSystemAxis[] axes, final boolean share) {
+        super(original.getPropertiesWithoutIdentifiers(name));
+        this.axes = axes;
+        validate(null);
+        forConvention = share ? original.forConvention : forConvention();
     }
 
     /**
@@ -269,13 +286,16 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(CoordinateSystem)
      */
-    protected AbstractCS(final CoordinateSystem cs) {
-        super(cs);
-        axes = (cs instanceof AbstractCS) ? ((AbstractCS) cs).axes : getAxes(cs);
+    @SuppressWarnings({"this-escape", "OverridableMethodCallInConstructor"})
+    protected AbstractCS(final CoordinateSystem original) {
+        super(original);
+        axes = (original instanceof AbstractCS) ? ((AbstractCS) original).axes : getAxes(original);
+        validate(null);
+        forConvention = forConvention();
     }
 
     /**
@@ -337,6 +357,16 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
         return CoordinateSystem.class;
     }
 
+    /**
+     * Returns the properties (scope, domain of validity) except the identifiers and the EPSG namespace.
+     *
+     * @param  name  name to associate to the {@link #NAME_KEY} in the returned map, or {@code null} to inherit.
+     * @return the identified object properties without identifier.
+     */
+    final Map<String,?> getPropertiesWithoutIdentifiers(final String name) {
+        return ReferencingUtilities.getPropertiesWithoutIdentifiers(this, (name == null) ? null : Map.of(NAME_KEY, name));
+    }
+
     /**
      * Returns the number of dimensions of this coordinate system.
      * This is the number of axes given at construction time.
@@ -360,6 +390,30 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
         return axes[dimension];
     }
 
+    /**
+     * Sets the CS for the given axes convention.
+     *
+     * @param  cs  the CS to cache.
+     * @return the cached CS. May be different than the given {@code cs} if an existing instance has been found.
+     */
+    final AbstractCS setCached(final AxesConvention convention, AbstractCS cs) {
+        assert Thread.holdsLock(forConvention);
+        /*
+         * It happens often that the CRS created by RIGHT_HANDED, DISPLAY_ORIENTED and NORMALIZED are the same.
+         * Sharing the same instance not only saves memory, but can also makes future comparisons faster.
+         */
+        for (final AbstractCS existing : forConvention.values()) {
+            if (cs.equals(existing, ComparisonMode.IGNORE_METADATA)) {
+                cs = existing;
+                break;
+            }
+        }
+        if (forConvention.put(convention, cs) != null) {
+            throw new ConcurrentModificationException();    // Should never happen, unless we have a synchronization bug.
+        }
+        return cs;
+    }
+
     /**
      * Returns a coordinate system equivalent to this one but with axes rearranged according the given convention.
      * If this coordinate system is already compatible with the given convention, then this method returns {@code this}.
@@ -369,33 +423,24 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
      *
      * @see org.apache.sis.referencing.crs.AbstractCRS#forConvention(AxesConvention)
      */
-    public synchronized AbstractCS forConvention(final AxesConvention convention) {
+    public AbstractCS forConvention(final AxesConvention convention) {
         ensureNonNull("convention", convention);
-        if (derived == null) {
-            derived = new EnumMap<>(AxesConvention.class);
-        }
-        AbstractCS cs = derived.get(convention);
-        if (cs == null) {
-            cs = Normalizer.forConvention(this, convention);
+        synchronized (forConvention) {
+            AbstractCS cs = forConvention.get(convention);
             if (cs == null) {
-                cs = this;                                              // This coordinate system is already normalized.
-            } else if (convention != AxesConvention.POSITIVE_RANGE) {
-                cs = cs.resolveEPSG(this);
-            }
-            /*
-             * It happen often that the CRS created by RIGHT_HANDED, DISPLAY_ORIENTED and
-             * NORMALIZED are the same. If this is the case, sharing the same instance
-             * not only save memory but can also make future comparisons faster.
-             */
-            for (final AbstractCS existing : derived.values()) {
-                if (cs.equals(existing)) {
-                    cs = existing;
-                    break;
+                final AbstractCS original = forConvention.get(AxesConvention.ORIGINAL);
+                cs = Normalizer.forConvention(original, convention);
+                if (cs == null) {
+                    cs = original;          // The given coordinate system is already normalized.
+                } else if (equals(cs, ComparisonMode.IGNORE_METADATA)) {
+                    cs = this;
+                } else if (convention != AxesConvention.POSITIVE_RANGE) {
+                    cs = cs.resolveEPSG(original);
                 }
+                cs = setCached(convention, cs);
             }
-            derived.put(convention, cs);
+            return cs;
         }
-        return cs;
     }
 
     /**
@@ -406,13 +451,15 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
      * But if the given {@code axes} array has less elements than this coordinate system dimension, then
      * this method may return another kind of coordinate system. See {@link AxisFilter} for an example.</p>
      *
-     * @param  axes  the set of axes to give to the new coordinate system.
+     * @param  name   name of the new coordinate system.
+     * @param  axes   the set of axes to give to the new coordinate system.
+     * @param  share  whether the new CS should use a cache shared with the original CS.
      * @return a new coordinate system of the same type as {@code this}, but using the given axes.
      * @throws IllegalArgumentException if {@code axes} contains an unexpected number of axes,
      *         or if an axis has an unexpected direction or unexpected unit of measurement.
      */
-    AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        return new AbstractCS(properties, axes);
+    AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
+        return new AbstractCS(this, name, axes, share);
     }
 
     /**
@@ -456,18 +503,19 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
     }
 
     /**
-     * Convenience method for implementations of {@link #createForAxes(Map, CoordinateSystemAxis[])}
+     * Convenience method for implementations of {@code createForAxes(…)}
      * when the resulting coordinate system would have an unexpected number of dimensions.
      *
-     * @param  properties  the properties which was supposed to be given to the constructor.
-     * @param  axes        the axes which was supposed to be given to the constructor.
-     * @param  expected    the minimal expected number of dimensions (may be less than {@link #getDimension()}).
+     * @param  axes  the axes which were supposed to be given to the constructor.
+     * @param  min   minimum number of dimensions, inclusive.
+     * @param  max   maximum number of dimensions, inclusive.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
      */
-    static IllegalArgumentException unexpectedDimension(final Map<String,?> properties,
-            final CoordinateSystemAxis[] axes, final int expected)
-    {
-        return new MismatchedDimensionException(Errors.getResources(properties).getString(
-                Errors.Keys.MismatchedDimension_3, "filter(cs)", expected, axes.length));
+    static IllegalArgumentException unexpectedDimension(final CoordinateSystemAxis[] axes, final int min, final int max) {
+        final int n = axes.length;
+        final int e = (n < min) ? min : max;
+        return new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "filter(cs)", e, n));
     }
 
     /**
@@ -592,6 +640,7 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
      */
     AbstractCS() {
         super(org.apache.sis.referencing.util.NilReferencingObject.INSTANCE);
+        forConvention = forConvention();
         axes = EMPTY;
         /*
          * Coordinate system axes are mandatory for SIS working. We do not verify their presence here
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AxesConvention.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AxesConvention.java
index 846e3ec515..762f5f6977 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AxesConvention.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AxesConvention.java
@@ -101,7 +101,7 @@ import org.apache.sis.measure.Units;
  * (e.g. {@link org.apache.sis.geometry.GeneralEnvelope#normalize()}).
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.5
  *
  * @see AbstractCS#forConvention(AxesConvention)
  * @see org.apache.sis.referencing.crs.AbstractCRS#forConvention(AxesConvention)
@@ -274,5 +274,15 @@ public enum AxesConvention implements AxisFilter {
      *
      * @see org.opengis.referencing.cs.RangeMeaning#WRAPAROUND
      */
-    POSITIVE_RANGE;
+    POSITIVE_RANGE,
+
+    /**
+     * The axis order as they were specified in the original coordinate system.
+     * The first time that a {@code forConvention(…)} method is invoked on a new coordinate system (CS),
+     * a reference to that original CS is associated to this enumeration value and can be retrieved from
+     * any derived object.
+     *
+     * @since 1.5
+     */
+    ORIGINAL;
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultAffineCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultAffineCS.java
index 6330148790..12c9fa3f09 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultAffineCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultAffineCS.java
@@ -51,7 +51,7 @@ import org.apache.sis.measure.Units;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   0.4
  */
 @XmlType(name = "AffineCSType")
@@ -62,14 +62,6 @@ public class DefaultAffineCS extends AbstractCS implements AffineCS {
      */
     private static final long serialVersionUID = 7977674229369042440L;
 
-    /**
-     * Constructs a coordinate system of arbitrary dimension. This constructor is
-     * not public because {@code AffineCS} are restricted to 2 and 3 dimensions.
-     */
-    DefaultAffineCS(final Map<String,?> properties, final CoordinateSystemAxis[] axis) {
-        super(properties, axis);
-    }
-
     /**
      * Constructs a two-dimensional coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -133,6 +125,17 @@ public class DefaultAffineCS extends AbstractCS implements AffineCS {
         super(properties, axis0, axis1, axis2);
     }
 
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    DefaultAffineCS(final DefaultAffineCS original, final String name,
+                    final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
+    }
+
     /**
      * Creates a new coordinate system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -140,12 +143,12 @@ public class DefaultAffineCS extends AbstractCS implements AffineCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(AffineCS)
      */
-    protected DefaultAffineCS(final AffineCS cs) {
-        super(cs);
+    protected DefaultAffineCS(final AffineCS original) {
+        super(original);
     }
 
     /**
@@ -218,11 +221,11 @@ public class DefaultAffineCS extends AbstractCS implements AffineCS {
      * This method shall be overridden by all {@code AffineCS} subclasses in this package.
      */
     @Override
-    AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
             case 2: // Fall through
-            case 3: return new DefaultAffineCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 2);
+            case 3: return new DefaultAffineCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 2, 3);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCartesianCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCartesianCS.java
index 2cf03c2f2a..993b983171 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCartesianCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCartesianCS.java
@@ -19,8 +19,11 @@ package org.apache.sis.referencing.cs;
 import java.util.Map;
 import jakarta.xml.bind.annotation.XmlType;
 import jakarta.xml.bind.annotation.XmlRootElement;
+import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CartesianCS;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.apache.sis.referencing.internal.Resources;
+import org.apache.sis.measure.Angle;
 
 
 /**
@@ -53,7 +56,7 @@ import org.opengis.referencing.cs.CoordinateSystemAxis;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createCartesianCS(String)
  *
@@ -67,15 +70,6 @@ public class DefaultCartesianCS extends DefaultAffineCS implements CartesianCS {
      */
     private static final long serialVersionUID = -6182037957705712945L;
 
-    /**
-     * Creates a new coordinate system from an arbitrary number of axes. This constructor is for
-     * implementations of the {@link #createForAxes(Map, CoordinateSystemAxis[])} method only,
-     * because it does not verify the number of axes.
-     */
-    private DefaultCartesianCS(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        super(properties, axes);
-    }
-
     /**
      * Constructs a two-dimensional coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -118,7 +112,6 @@ public class DefaultCartesianCS extends DefaultAffineCS implements CartesianCS {
                               final CoordinateSystemAxis axis1)
     {
         super(properties, axis0, axis1);
-        ensurePerpendicularAxis(properties);
     }
 
     /**
@@ -139,7 +132,17 @@ public class DefaultCartesianCS extends DefaultAffineCS implements CartesianCS {
                               final CoordinateSystemAxis axis2)
     {
         super(properties, axis0, axis1, axis2);
-        ensurePerpendicularAxis(properties);
+    }
+
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    DefaultCartesianCS(final DefaultCartesianCS original, final String name,
+                       final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
     }
 
     /**
@@ -149,13 +152,12 @@ public class DefaultCartesianCS extends DefaultAffineCS implements CartesianCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(CartesianCS)
      */
-    protected DefaultCartesianCS(final CartesianCS cs) {
-        super(cs);
-        ensurePerpendicularAxis(null);
+    protected DefaultCartesianCS(final CartesianCS original) {
+        super(original);
     }
 
     /**
@@ -173,6 +175,35 @@ public class DefaultCartesianCS extends DefaultAffineCS implements CartesianCS {
                 ? (DefaultCartesianCS) object : new DefaultCartesianCS(object);
     }
 
+    /**
+     * Ensures that all known axes are perpendicular, ignoring unknown axis directions.
+     * This method can be invoked by the constructors of coordinate systems having this requirement.
+     *
+     * @param  properties  properties given at construction time, or {@code null} if none.
+     * @throws IllegalArgumentException if an illegal angle is found between two axes.
+     */
+    @Override
+    final void validate(final Map<String,?> properties) {
+        super.validate(properties);
+        final int dimension = getDimension();
+        for (int i=0; i<dimension; i++) {
+            final AxisDirection axis0 = getAxis(i).getDirection();
+            for (int j=i; ++j < dimension;) {
+                final AxisDirection axis1 = getAxis(j).getDirection();
+                final Angle angle = CoordinateSystems.angle(axis0, axis1);
+                /*
+                 * The angle may be null for grid directions (COLUMN_POSITIVE, COLUMN_NEGATIVE,
+                 * ROW_POSITIVE, ROW_NEGATIVE). We conservatively accept those directions even if
+                 * they are not really for Cartesian CS because we do not know the grid geometry.
+                 */
+                if (angle != null && Math.abs(angle.degrees()) != 90) {
+                    throw new IllegalArgumentException(Resources.forProperties(properties).getString(
+                            Resources.Keys.NonPerpendicularDirections_2, axis0, axis1));
+                }
+            }
+        }
+    }
+
     /**
      * Returns the GeoAPI interface implemented by this class.
      * The SIS implementation returns {@code CartesianCS.class}.
@@ -203,12 +234,12 @@ public class DefaultCartesianCS extends DefaultAffineCS implements CartesianCS {
      * Returns a coordinate system with different axes.
      */
     @Override
-    final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    final AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
-            case 1: return new DefaultVerticalCS(properties, axes);
+            case 1: return SubTypes.createOneDimensional(this, name, axes);
             case 2: // Fall through
-            case 3: return new DefaultCartesianCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 2);
+            case 3: return new DefaultCartesianCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 1, 3);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java
index 37946de847..e1ce919b4c 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCompoundCS.java
@@ -50,7 +50,7 @@ import static org.apache.sis.util.Utilities.deepEquals;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   0.4
  */
 @XmlTransient
@@ -105,6 +105,15 @@ public class DefaultCompoundCS extends AbstractCS {
         this.components = UnmodifiableArrayList.wrap(components);
     }
 
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     * This is used for the {@link #forConvention(AxesConvention)} implementation only.
+     */
+    private DefaultCompoundCS(final DefaultCompoundCS original, final CoordinateSystem[] components) {
+        super(original, null, getAxes(components), true);
+        this.components = UnmodifiableArrayList.wrap(components);
+    }
+
     /**
      * Constructs a compound coordinate system from a sequence of coordinate systems.
      * A default name for this CS will be inferred from the names of all specified CS.
@@ -170,6 +179,39 @@ public class DefaultCompoundCS extends AbstractCS {
         return components;          // Unmodifiable.
     }
 
+    /**
+     * Returns a compound CS equivalent to this one but with axes rearranged according the given convention.
+     * This method reorders the axes of each individual coordinate system {@linkplain #getComponents() component}.
+     *
+     * @return {@inheritDoc}
+     */
+    @Override
+    public DefaultCompoundCS forConvention(final AxesConvention convention) {
+        ensureNonNull("convention", convention);
+        synchronized (forConvention) {
+            DefaultCompoundCS cs = (DefaultCompoundCS) forConvention.get(convention);
+            if (cs == null) {
+                cs = (DefaultCompoundCS) forConvention.get(AxesConvention.ORIGINAL);
+                boolean changed = false;
+                final CoordinateSystem[] newComponents = new CoordinateSystem[cs.components.size()];
+                for (int i=0; i<newComponents.length; i++) {
+                    CoordinateSystem component = cs.components.get(i);
+                    AbstractCS m = castOrCopy(component);
+                    if (m != (m = m.forConvention(convention))) {
+                        component = m;
+                        changed = true;
+                    }
+                    newComponents[i] = component;
+                }
+                if (changed) {
+                    cs = new DefaultCompoundCS(cs, newComponents);
+                }
+                cs = (DefaultCompoundCS) setCached(convention, cs);
+            }
+            return cs;
+        }
+    }
+
     /*
      * Do not override createForAxes(…) and forConvention(…) because we cannot create a new DefaultCompoundCS
      * without knownledge of the CoordinateSystem components to give to it. It would be possible to recursively
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCylindricalCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
index afa4399b83..ae6855ecf0 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
@@ -48,7 +48,7 @@ import org.apache.sis.measure.Units;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see DefaultPolarCS
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createCylindricalCS(String)
@@ -63,15 +63,6 @@ public class DefaultCylindricalCS extends AbstractCS implements CylindricalCS {
      */
     private static final long serialVersionUID = -8290402732390917907L;
 
-    /**
-     * Creates a new coordinate system from an arbitrary number of axes. This constructor is for
-     * implementations of the {@link #createForAxes(Map, CoordinateSystemAxis[])} method only,
-     * because it does not verify the number of axes.
-     */
-    private DefaultCylindricalCS(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        super(properties, axes);
-    }
-
     /**
      * Constructs a three-dimensional coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -118,6 +109,17 @@ public class DefaultCylindricalCS extends AbstractCS implements CylindricalCS {
         super(properties, axis0, axis1, axis2);
     }
 
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    private DefaultCylindricalCS(final DefaultCylindricalCS original, final String name,
+                                 final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
+    }
+
     /**
      * Creates a new coordinate system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -125,12 +127,12 @@ public class DefaultCylindricalCS extends AbstractCS implements CylindricalCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(CylindricalCS)
      */
-    protected DefaultCylindricalCS(final CylindricalCS cs) {
-        super(cs);
+    protected DefaultCylindricalCS(final CylindricalCS original) {
+        super(original);
     }
 
     /**
@@ -197,11 +199,11 @@ public class DefaultCylindricalCS extends AbstractCS implements CylindricalCS {
      * Returns a coordinate system with different axes.
      */
     @Override
-    final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    final AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
-            case 2: return new DefaultPolarCS(properties, axes);
-            case 3: return new DefaultCylindricalCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 2);
+            case 2: return new DefaultPolarCS(getPropertiesWithoutIdentifiers(name), axes[0], axes[1]);
+            case 3: return new DefaultCylindricalCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 2, 3);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
index 63ab943a46..c843716613 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
@@ -48,7 +48,7 @@ import org.apache.sis.measure.Units;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createEllipsoidalCS(String)
  *
@@ -62,16 +62,6 @@ public class DefaultEllipsoidalCS extends AbstractCS implements EllipsoidalCS {
      */
     private static final long serialVersionUID = -1452492488902329211L;
 
-    /**
-     * Creates a new coordinate system from an arbitrary number of axes. This constructor is for
-     * implementations of the {@link #createForAxes(Map, CoordinateSystemAxis[])} method only,
-     * because it does not verify the number of axes.
-     */
-    private DefaultEllipsoidalCS(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        super(properties, axes);
-        validateAxes(properties);
-    }
-
     /**
      * Constructs a two-dimensional coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -114,7 +104,6 @@ public class DefaultEllipsoidalCS extends AbstractCS implements EllipsoidalCS {
                                 final CoordinateSystemAxis axis1)
     {
         super(properties, axis0, axis1);
-        validateAxes(properties);
     }
 
     /**
@@ -135,7 +124,17 @@ public class DefaultEllipsoidalCS extends AbstractCS implements EllipsoidalCS {
                                 final CoordinateSystemAxis axis2)
     {
         super(properties, axis0, axis1, axis2);
-        validateAxes(properties);
+    }
+
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    private DefaultEllipsoidalCS(final DefaultEllipsoidalCS original, final String name,
+                                 final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
     }
 
     /**
@@ -145,12 +144,12 @@ public class DefaultEllipsoidalCS extends AbstractCS implements EllipsoidalCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(EllipsoidalCS)
      */
-    protected DefaultEllipsoidalCS(final EllipsoidalCS cs) {
-        super(cs);
+    protected DefaultEllipsoidalCS(final EllipsoidalCS original) {
+        super(original);
     }
 
     /**
@@ -196,7 +195,9 @@ public class DefaultEllipsoidalCS extends AbstractCS implements EllipsoidalCS {
      *
      * @param  properties  the properties given at construction time.
      */
-    private void validateAxes(final Map<String,?> properties) {
+    @Override
+    final void validate(final Map<String,?> properties) {
+        super.validate(properties);
         int i = super.getDimension();
         int n = i - 2;                      // Number of vertical axes allowed.
         while (--i >= 0) {
@@ -238,12 +239,12 @@ public class DefaultEllipsoidalCS extends AbstractCS implements EllipsoidalCS {
      * Returns a coordinate system with different axes.
      */
     @Override
-    final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    final AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
-            case 1: return new DefaultVerticalCS(properties, axes);
+            case 1: return SubTypes.createOneDimensional(this, name, axes);
             case 2: // Fall through
-            case 3: return new DefaultEllipsoidalCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 1);
+            case 3: return new DefaultEllipsoidalCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 1, 3);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultLinearCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultLinearCS.java
index 668684f0ae..01bb2aa3dc 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultLinearCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultLinearCS.java
@@ -49,7 +49,7 @@ import org.apache.sis.measure.Units;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   0.4
  */
 @XmlType(name = "LinearCSType")
@@ -60,15 +60,6 @@ public class DefaultLinearCS extends AbstractCS implements LinearCS {
      */
     private static final long serialVersionUID = -6890723478287625763L;
 
-    /**
-     * Creates a new coordinate system from an arbitrary number of axes. This constructor is for
-     * implementations of the {@link #createForAxes(Map, CoordinateSystemAxis[])} method only,
-     * because it does not verify the number of axes.
-     */
-    private DefaultLinearCS(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        super(properties, axes);
-    }
-
     /**
      * Constructs a coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -109,6 +100,17 @@ public class DefaultLinearCS extends AbstractCS implements LinearCS {
         super(properties, axis);
     }
 
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    private DefaultLinearCS(final DefaultLinearCS original, final String name,
+                            final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
+    }
+
     /**
      * Creates a new coordinate system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -116,12 +118,12 @@ public class DefaultLinearCS extends AbstractCS implements LinearCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(LinearCS)
      */
-    protected DefaultLinearCS(final LinearCS cs) {
-        super(cs);
+    protected DefaultLinearCS(final LinearCS original) {
+        super(original);
     }
 
     /**
@@ -188,10 +190,10 @@ public class DefaultLinearCS extends AbstractCS implements LinearCS {
      * Returns a coordinate system with different axes.
      */
     @Override
-    final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    final AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
-            case 1: return new DefaultLinearCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 1);
+            case 1: return new DefaultLinearCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 1, 1);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultParametricCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultParametricCS.java
index 229ddc778c..b026f56205 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultParametricCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultParametricCS.java
@@ -45,7 +45,7 @@ import org.opengis.referencing.cs.ParametricCS;
  * constants.
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.crs.DefaultParametricCRS
  * @see org.apache.sis.referencing.datum.DefaultParametricDatum
@@ -61,15 +61,6 @@ public class DefaultParametricCS extends AbstractCS implements ParametricCS {
      */
     private static final long serialVersionUID = -5588239024582484514L;
 
-    /**
-     * Creates a new coordinate system from an arbitrary number of axes. This constructor is for
-     * implementations of the {@link #createForAxes(Map, CoordinateSystemAxis[])} method only,
-     * because it does not verify the number of axes.
-     */
-    private DefaultParametricCS(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        super(properties, axes);
-    }
-
     /**
      * Constructs a coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -110,6 +101,17 @@ public class DefaultParametricCS extends AbstractCS implements ParametricCS {
         super(properties, axis);
     }
 
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    private DefaultParametricCS(final DefaultParametricCS original, final String name,
+                                final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
+    }
+
     /**
      * Creates a new coordinate system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -117,12 +119,12 @@ public class DefaultParametricCS extends AbstractCS implements ParametricCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(ParametricCS)
      */
-    protected DefaultParametricCS(final ParametricCS cs) {
-        super(cs);
+    protected DefaultParametricCS(final ParametricCS original) {
+        super(original);
     }
 
     /**
@@ -170,10 +172,10 @@ public class DefaultParametricCS extends AbstractCS implements ParametricCS {
      * Returns a coordinate system with different axes.
      */
     @Override
-    final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    final AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
-            case 1: return new DefaultParametricCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 1);
+            case 1: return new DefaultParametricCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 1, 1);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultPolarCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultPolarCS.java
index 1332dc078c..b20a4b9113 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultPolarCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultPolarCS.java
@@ -48,7 +48,7 @@ import org.apache.sis.measure.Units;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see DefaultCylindricalCS
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createPolarCS(String)
@@ -63,15 +63,6 @@ public class DefaultPolarCS extends AbstractCS implements PolarCS {
      */
     private static final long serialVersionUID = 3960197260975470951L;
 
-    /**
-     * Creates a new coordinate system from an arbitrary number of axes. This constructor is for
-     * implementations of the {@link #createForAxes(Map, CoordinateSystemAxis[])} method only,
-     * because it does not verify the number of axes.
-     */
-    DefaultPolarCS(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        super(properties, axes);
-    }
-
     /**
      * Constructs a two-dimensional coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -116,6 +107,17 @@ public class DefaultPolarCS extends AbstractCS implements PolarCS {
         super(properties, axis0, axis1);
     }
 
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    private DefaultPolarCS(final DefaultPolarCS original, final String name,
+                           final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
+    }
+
     /**
      * Creates a new coordinate system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -123,12 +125,12 @@ public class DefaultPolarCS extends AbstractCS implements PolarCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(PolarCS)
      */
-    protected DefaultPolarCS(final PolarCS cs) {
-        super(cs);
+    protected DefaultPolarCS(final PolarCS original) {
+        super(original);
     }
 
     /**
@@ -195,10 +197,10 @@ public class DefaultPolarCS extends AbstractCS implements PolarCS {
      * Returns a coordinate system with different axes.
      */
     @Override
-    final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    final AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
-            case 2: return new DefaultPolarCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 2);
+            case 2: return new DefaultPolarCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 2, 2);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultSphericalCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultSphericalCS.java
index 42365f5448..688eeece48 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultSphericalCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultSphericalCS.java
@@ -53,7 +53,7 @@ import org.apache.sis.measure.Units;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createSphericalCS(String)
  *
@@ -67,15 +67,6 @@ public class DefaultSphericalCS extends AbstractCS implements SphericalCS {
      */
     private static final long serialVersionUID = 196295996465774477L;
 
-    /**
-     * Creates a new coordinate system from an arbitrary number of axes. This constructor is for
-     * implementations of the {@link #createForAxes(Map, CoordinateSystemAxis[])} method only,
-     * because it does not verify the number of axes.
-     */
-    private DefaultSphericalCS(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        super(properties, axes);
-    }
-
     /**
      * Constructs a three-dimensional coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -143,6 +134,17 @@ public class DefaultSphericalCS extends AbstractCS implements SphericalCS {
         super(properties, axis0, axis1);
     }
 
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    private DefaultSphericalCS(final DefaultSphericalCS original, final String name,
+                               final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
+    }
+
     /**
      * Creates a new coordinate system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -150,12 +152,12 @@ public class DefaultSphericalCS extends AbstractCS implements SphericalCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(SphericalCS)
      */
-    protected DefaultSphericalCS(final SphericalCS cs) {
-        super(cs);
+    protected DefaultSphericalCS(final SphericalCS original) {
+        super(original);
     }
 
     /**
@@ -220,17 +222,18 @@ public class DefaultSphericalCS extends AbstractCS implements SphericalCS {
      * Returns a coordinate system with different axes.
      */
     @Override
-    @SuppressWarnings("fallthrough")
-    final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    final AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
             case 2: {
+                final Map<String,?> properties = getPropertiesWithoutIdentifiers(name);
                 if (Units.isLinear(axes[0].getUnit()) || Units.isLinear(axes[1].getUnit())) {
-                    return new DefaultPolarCS(properties, axes);
+                    return new DefaultPolarCS(properties, axes[0], axes[1]);
+                } else {
+                    return new DefaultSphericalCS(properties, axes[0], axes[1]);
                 }
-                // Fall through
             }
-            case 3: return new DefaultSphericalCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 2);
+            case 3: return new DefaultSphericalCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 2, 3);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultTimeCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultTimeCS.java
index f1dcef7016..8f8a556816 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultTimeCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultTimeCS.java
@@ -47,7 +47,7 @@ import org.apache.sis.measure.Units;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.crs.DefaultTemporalCRS
  * @see org.apache.sis.referencing.datum.DefaultTemporalDatum
@@ -63,15 +63,6 @@ public class DefaultTimeCS extends AbstractCS implements TimeCS {
      */
     private static final long serialVersionUID = 5222911412381303989L;
 
-    /**
-     * Creates a new coordinate system from an arbitrary number of axes. This constructor is for
-     * implementations of the {@link #createForAxes(Map, CoordinateSystemAxis[])} method only,
-     * because it does not verify the number of axes.
-     */
-    private DefaultTimeCS(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        super(properties, axes);
-    }
-
     /**
      * Constructs a coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -112,6 +103,17 @@ public class DefaultTimeCS extends AbstractCS implements TimeCS {
         super(properties, axis);
     }
 
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    private DefaultTimeCS(final DefaultTimeCS original, final String name,
+                          final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
+    }
+
     /**
      * Creates a new coordinate system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -119,12 +121,12 @@ public class DefaultTimeCS extends AbstractCS implements TimeCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(TimeCS)
      */
-    protected DefaultTimeCS(final TimeCS cs) {
-        super(cs);
+    protected DefaultTimeCS(final TimeCS original) {
+        super(original);
     }
 
     /**
@@ -189,10 +191,10 @@ public class DefaultTimeCS extends AbstractCS implements TimeCS {
      * Returns a coordinate system with different axes.
      */
     @Override
-    final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    final AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
-            case 1: return new DefaultTimeCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 1);
+            case 1: return new DefaultTimeCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 1, 1);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
index 2ff922858e..1637f2536f 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
@@ -43,7 +43,7 @@ import org.opengis.referencing.cs.CoordinateSystemAxis;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   0.4
  */
 @XmlType(name = "UserDefinedCSType")
@@ -54,15 +54,6 @@ public class DefaultUserDefinedCS extends AbstractCS implements UserDefinedCS {
      */
     private static final long serialVersionUID = -4904091898305706316L;
 
-    /**
-     * Creates a new coordinate system from an arbitrary number of axes. This constructor is for
-     * implementations of the {@link #createForAxes(Map, CoordinateSystemAxis[])} method only,
-     * because it does not verify the number of axes.
-     */
-    private DefaultUserDefinedCS(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        super(properties, axes);
-    }
-
     /**
      * Constructs a two-dimensional coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -127,6 +118,17 @@ public class DefaultUserDefinedCS extends AbstractCS implements UserDefinedCS {
         super(properties, axis0, axis1, axis2);
     }
 
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    private DefaultUserDefinedCS(final DefaultUserDefinedCS original, final String name,
+                                 final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
+    }
+
     /**
      * Creates a new coordinate system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -134,12 +136,12 @@ public class DefaultUserDefinedCS extends AbstractCS implements UserDefinedCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(UserDefinedCS)
      */
-    protected DefaultUserDefinedCS(final UserDefinedCS cs) {
-        super(cs);
+    protected DefaultUserDefinedCS(final UserDefinedCS original) {
+        super(original);
     }
 
     /**
@@ -187,11 +189,11 @@ public class DefaultUserDefinedCS extends AbstractCS implements UserDefinedCS {
      * Returns a coordinate system with different axes.
      */
     @Override
-    final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    final AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
             case 2: // Fall through
-            case 3: return new DefaultUserDefinedCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 2);
+            case 3: return new DefaultUserDefinedCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 2, 3);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultVerticalCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultVerticalCS.java
index 6f50d5b0b6..69f646c179 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultVerticalCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DefaultVerticalCS.java
@@ -58,7 +58,7 @@ import org.apache.sis.measure.Units;
  * constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  *
  * @see org.apache.sis.referencing.crs.DefaultVerticalCRS
  * @see org.apache.sis.referencing.datum.DefaultVerticalDatum
@@ -74,15 +74,6 @@ public class DefaultVerticalCS extends AbstractCS implements VerticalCS {
      */
     private static final long serialVersionUID = 1201155778896630499L;
 
-    /**
-     * Creates a new coordinate system from an arbitrary number of axes. This constructor is for
-     * implementations of the {@link #createForAxes(Map, CoordinateSystemAxis[])} method only,
-     * because it does not verify the number of axes.
-     */
-    DefaultVerticalCS(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
-        super(properties, axes);
-    }
-
     /**
      * Constructs a coordinate system from a set of properties.
      * The properties map is given unchanged to the
@@ -123,6 +114,17 @@ public class DefaultVerticalCS extends AbstractCS implements VerticalCS {
         super(properties, axis);
     }
 
+    /**
+     * Creates a new CS derived from the specified one, but with different axis order or unit.
+     *
+     * @see #createForAxes(String, CoordinateSystemAxis[], boolean)
+     */
+    private DefaultVerticalCS(final DefaultVerticalCS original, final String name,
+                              final CoordinateSystemAxis[] axes, final boolean share)
+    {
+        super(original, name, axes, share);
+    }
+
     /**
      * Creates a new coordinate system with the same values as the specified one.
      * This copy constructor provides a way to convert an arbitrary implementation into a SIS one
@@ -130,12 +132,12 @@ public class DefaultVerticalCS extends AbstractCS implements VerticalCS {
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  cs  the coordinate system to copy.
+     * @param  original  the coordinate system to copy.
      *
      * @see #castOrCopy(VerticalCS)
      */
-    protected DefaultVerticalCS(final VerticalCS cs) {
-        super(cs);
+    protected DefaultVerticalCS(final VerticalCS original) {
+        super(original);
     }
 
     /**
@@ -205,10 +207,10 @@ public class DefaultVerticalCS extends AbstractCS implements VerticalCS {
      * Returns a coordinate system with different axes.
      */
     @Override
-    final AbstractCS createForAxes(final Map<String,?> properties, final CoordinateSystemAxis[] axes) {
+    final AbstractCS createForAxes(final String name, final CoordinateSystemAxis[] axes, final boolean share) {
         switch (axes.length) {
-            case 1: return new DefaultVerticalCS(properties, axes);
-            default: throw unexpectedDimension(properties, axes, 1);
+            case 1: return new DefaultVerticalCS(this, name, axes, share);
+            default: throw unexpectedDimension(axes, 1, 1);
         }
     }
 
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/Normalizer.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/Normalizer.java
index e9e3ae16bc..8859b632a8 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/Normalizer.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/Normalizer.java
@@ -120,6 +120,9 @@ final class Normalizer implements Comparable<Normalizer> {
             AxisDirections.CLOCKWISE,
             AxisDirections.AWAY_FROM
         }) ORDER.put(d, ++code);
+        // Set the time coordinate as the last coordinate in all cases.
+        ORDER.put(AxisDirection.PAST,   (Integer.MAX_VALUE >>> 1) - 1);
+        ORDER.put(AxisDirection.FUTURE, (Integer.MAX_VALUE >>> 1));
     }
 
     /**
@@ -359,8 +362,9 @@ final class Normalizer implements Comparable<Normalizer> {
          * We need to change the Coordinate System name, since it is likely to not be valid anymore.
          */
         final AbstractCS impl = castOrCopy(cs);
-        final StringBuilder buffer = (StringBuilder) CharSequences.camelCaseToSentence(impl.getInterface().getSimpleName());
-        return impl.createForAxes(Map.of(AbstractCS.NAME_KEY, AxisDirections.appendTo(buffer, newAxes)), newAxes);
+        final var buffer = (StringBuilder) CharSequences.camelCaseToSentence(impl.getInterface().getSimpleName());
+        final String name = AxisDirections.appendTo(buffer, newAxes);
+        return impl.createForAxes(name, newAxes, changes instanceof AxesConvention);
     }
 
     /**
@@ -382,9 +386,11 @@ final class Normalizer implements Comparable<Normalizer> {
      * of -60° still locate the same point in the old and the new coordinate system. But the preferred way to
      * locate that point become the 300° value if the longitude range has been shifted to positive values.</p>
      *
+     * @param  cs     the coordinate system to shift.
+     * @param  share  whether the new CS should use a cache shared with the original CS.
      * @return a coordinate system using the given kind of longitude range, or {@code null} if no change is needed.
      */
-    private static AbstractCS shiftAxisRange(final CoordinateSystem cs) {
+    private static AbstractCS shiftAxisRange(final CoordinateSystem cs, final boolean share) {
         boolean changed = false;
         final CoordinateSystemAxis[] axes = new CoordinateSystemAxis[cs.getDimension()];
         for (int i=0; i<axes.length; i++) {
@@ -408,7 +414,7 @@ final class Normalizer implements Comparable<Normalizer> {
         if (!changed) {
             return null;
         }
-        return castOrCopy(cs).createForAxes(IdentifiedObjects.getProperties(cs, EXCLUDES), axes);
+        return castOrCopy(cs).createForAxes(null, axes, share);
     }
 
     /**
@@ -448,7 +454,7 @@ final class Normalizer implements Comparable<Normalizer> {
             case NORMALIZED:       // Fall through
             case DISPLAY_ORIENTED: return normalize(cs, convention, true);
             case RIGHT_HANDED:     return normalize(cs, null, true);
-            case POSITIVE_RANGE:   return shiftAxisRange(cs);
+            case POSITIVE_RANGE:   return shiftAxisRange(cs, true);
             default: throw new AssertionError(convention);
         }
     }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/SubTypes.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/SubTypes.java
index 71ae3137b9..83783054bc 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/SubTypes.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/SubTypes.java
@@ -16,8 +16,11 @@
  */
 package org.apache.sis.referencing.cs;
 
+import java.util.Map;
 import org.opengis.referencing.cs.AffineCS;
+import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CylindricalCS;
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.LinearCS;
@@ -26,6 +29,7 @@ import org.opengis.referencing.cs.SphericalCS;
 import org.opengis.referencing.cs.TimeCS;
 import org.opengis.referencing.cs.UserDefinedCS;
 import org.opengis.referencing.cs.VerticalCS;
+import org.apache.sis.referencing.util.AxisDirections;
 
 
 /**
@@ -91,4 +95,31 @@ final class SubTypes {
         }
         return new AbstractCS(object);
     }
+
+    /**
+     * Creates a one-dimensional coordinate system derived from the specified CS.
+     *
+     * @param  original  the two- or three-dimensional CRS from which to extract an axis.
+     * @param  name      name of the new coordinate system.
+     * @param  axes      the user-specified coordinate system axes. Only the first one will be used.
+     * @return the one-dimensional coordinate system with the specified axis.
+     */
+    static AbstractCS createOneDimensional(final AbstractCS original, final String name, final CoordinateSystemAxis[] axes) {
+        final CoordinateSystemAxis axis = axes[0];
+        final AxisDirection dir = AxisDirections.absolute(axis.getDirection());
+        final boolean isTemporal;
+        if (AxisDirection.UP.equals(dir)) {
+            isTemporal = false;
+        } else if (AxisDirection.FUTURE.equals(dir)) {          // Happen with Minkowski coordinate system.
+            isTemporal = true;
+        } else {
+            throw AbstractCS.unexpectedDimension(axes, 2, 3);
+        }
+        final Map<String,?> properties = original.getPropertiesWithoutIdentifiers(name);
+        if (isTemporal) {
+            return new DefaultTimeCS(properties, axis);
+        } else {
+            return new DefaultVerticalCS(properties, axis);
+        }
+    }
 }
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/package-info.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/package-info.java
index 0b9e22c1be..ba79901991 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/package-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/package-info.java
@@ -33,7 +33,7 @@
  * and units between two coordinate systems, or filtering axes.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   0.4
  */
 @XmlSchema(location = "http://schemas.opengis.net/gml/3.2.1/coordinateSystems.xsd",
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/util/ReferencingUtilities.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/util/ReferencingUtilities.java
index 5a96079516..fb136a72ac 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/util/ReferencingUtilities.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/util/ReferencingUtilities.java
@@ -47,6 +47,7 @@ import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
@@ -424,6 +425,33 @@ public final class ReferencingUtilities extends Static {
                 && AxisDirection.EAST .equals(cs.getAxis(1).getDirection());
     }
 
+    /**
+     * Returns the properties (scope, domain of validity) except the identifiers and the EPSG namespace.
+     * The identifiers are removed because a modified CRS is no longer conform to the authoritative definition.
+     * If the name contains a namespace (e.g. "EPSG"), this method removes that namespace for the same reason.
+     * For example, "EPSG:WGS 84" will become simply "WGS 84".
+     *
+     * @param  object     the identified object for which to get properties map.
+     * @param  overwrite  properties overwriting the inherited ones, or {@code null} if none.
+     * @return the identified object properties.
+     */
+    public static Map<String,?> getPropertiesWithoutIdentifiers(final IdentifiedObject object, final Map<String,?> overwrite) {
+        final Map<String,?> properties = IdentifiedObjects.getProperties(object, IdentifiedObject.IDENTIFIERS_KEY);
+        final Identifier name = object.getName();
+        final boolean keepName = name.getCodeSpace() == null && name.getAuthority() == null;
+        if (keepName && overwrite == null) {
+            return properties;
+        }
+        final var copy = new HashMap<String,Object>(properties);
+        if (!keepName) {
+            copy.put(IdentifiedObject.NAME_KEY, new NamedIdentifier(null, name.getCode()));
+        }
+        if (overwrite != null) {
+            copy.putAll(overwrite);
+        }
+        return copy;
+    }
+
     /**
      * Returns the properties of the given object but potentially with a modified name.
      * Current implement truncates the name at the first non-white character which is not
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultPolarCSTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
index 623c0894bc..02aea810f8 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
@@ -74,10 +74,10 @@ public final class DefaultPolarCSTest extends TestCase {
                 radius);
 
         DefaultPolarCS normalized = cs.forConvention(AxesConvention.RIGHT_HANDED);
-        assertNotSame("Should create a new CoordinateSystem.", cs, normalized);
         assertAxisDirectionsEqual("Right-handed", normalized,
-                AxisDirections.CLOCKWISE,                       // Interchanged (r,θ) order for making right handed.
+                AxisDirections.CLOCKWISE,
                 AxisDirection.SOUTH);
+        assertSame(cs, normalized);
 
         normalized = cs.forConvention(AxesConvention.NORMALIZED);
         assertNotSame("Should create a new CoordinateSystem.", cs, normalized);