# Class TransformJoiner

Object
TransformJoiner

public final class TransformJoiner extends Object
Handler used during concatenations for simplifying a single transform with its neighbor transforms. Instances of `TransformJoiner` may be created automatically by Apache SIS at any step during a concatenation. Those `TransformJoiner` instances give to `AbstractMathTransform` implementations an opportunity to replace the default concatenation algorithm by an optimized alternative. Some examples of optimizations are:

- detecting when two consecutive non-linear operations are equivalent to a third non-linear operation,
- modifying the linear transforms that are executed before or after the transform to optimize (it is sometime possible to remove a transform step that way).

Because the simplifications that are possible depend on the transform's formulas, each `AbstractMathTransform` subclass uses `TransformJoiner` in a different way.

## Usage

This class is used by overriding the `AbstractMathTransform​.tryConcatenate(TransformJoiner)` method. The implementation can inspect the surrounding transforms with calls to `getTransform(int)`. The argument given to `getTransform(…)` is a relative index: value 0 identifies the transform on which `tryConcatenate(…)` has been invoked, -1 the transform immediately before it, and +1 the transform immediately after it. The `tryConcatenate(…)` implementation can replace the transform at relative index 0 together with a neighbor transform (at relative index -1 or +1) by a call to `replace(int, MathTransform)`.

Since:
  1.5
See Also:
  - `AbstractMathTransform​.tryConcatenate(TransformJoiner)`

- ## Field Summary

  Fields
  Modifier and Type
  Field
  Description
  `final MathTransformFactory`
  `factory`
  The factory to use for creating the transform to use as a replacement of the transforms to simplify.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `Optional<MathTransform>`
  `getTransform(int relativeIndex)`
  Returns a transform before or after the transform to simplify.
  `boolean`
  `replace(int from, int to, MathTransform concatenation)`
  Tries to replace the transforms in the given range of relative indices, which must include 0.
  `boolean`
  `replace(int firstOrLast, MathTransform concatenation)`
  Tries to replace the transform at relative index 0, together with a neighbor, by the given transform.
  `boolean`
  `replacePassThrough(Map<Integer,Integer> dimensions)`
  Tries to simplify the transform chain when some coordinates are passed-through the transform at index 0.
  `boolean`
  `replaceRoundtrip(int middle, UnaryOperator<MathTransform> mapper)`
  Tries to replace a forward → middle → inverse chain by a new transform.

  ### Methods inherited from class Object

  `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- ## Field Details

  - ### factory

    public final MathTransformFactory factory
    The factory to use for creating the transform to use as a replacement of the transforms to simplify. Never null.

- ## Method Details

  - ### getTransform

    public Optional<MathTransform> getTransform(int relativeIndex)
    Returns a transform before or after the transform to simplify. By definition, invoking this method with a relative index of 0 always returns the transform on which `tryConcatenate(…)` has been invoked. A relative index of -1 returns the previous transform while a relative index of +1 returns the next transform.

    This method may return an empty value if the requested transform is not available from this `TransformJoiner` instance. It does not mean that the transform does not exist at all in the complete chain of transforms, because each `TransformJoiner` instance may know only a fragment of that chain.

    Parameters:
      `relativeIndex` - index relative to the transform on which `tryConcatenate(…)` has been invoked.
    Returns:
      the requested transform. An empty value does not necessarily mean that the transform does not exist.

  - ### replace

    public boolean replace(int firstOrLast, MathTransform concatenation) throws FactoryException
    Tries to replace the transform at relative index 0, together with a neighbor, by the given transform. The `firstOrLast` argument specifies which neighbors are replaced by the specified transform:
    - If -1, then transforming a point p by `concatenation` shall be equivalent to first transforming p by `getTransform(-1)`, and then transforming the result by `getTransform(0)`.
    - If +1, then transforming a point p by `concatenation` shall be equivalent to first transforming p by `getTransform(0)`, and then transforming the result by `getTransform(+1)`.
    - If 0, then only the transform at index 0 is replaced. Neighbor are unchanged.
    Parameters:
      `firstOrLast` - relative index of first (if negative) or last (if positive) transform to replace.
      `concatenation` - the transform to use instead of the one at index 0 and the specified neighbors.
    Returns:
      whether the replacement has been done. May be `false` if `firstOrLast` is too large.
    Throws:
      `FactoryException` - if the given concatenation is not a valid replacement.

  - ### replace

    public boolean replace(int from, int to, MathTransform concatenation) throws FactoryException
    Tries to replace the transforms in the given range of relative indices, which must include 0. This method is similar to `replace(int, MathTransform)`, but allowing to replace the neighbor transforms on both sides.

    #### Example

    A call to `transform(-1, +1, tr)` replaces the transform on which `tryConcatenate(…)` has been invoked (this is the transform at relative index 0) together with the transforms located immediately before (-1) and immediately after (+1) by the given `tr` transform.
    Parameters:
      `from` - relative index of the first transform to replace, inclusive. This is usually -1 or 0.
      `to` - relative index of the last transform to replace, inclusive. This is usually 0 or +1.
      `concatenation` - the transform to use instead of the transforms in the given range.
    Returns:
      whether the replacement has been done.
    Throws:
      `FactoryException` - if the given concatenation is not a valid replacement.

  - ### replaceRoundtrip

    public boolean replaceRoundtrip(int middle, UnaryOperator<MathTransform> mapper) throws FactoryException
    Tries to replace a forward → middle → inverse chain by a new transform. The transform that determines whether a replacement is possible is the middle transform, and its relative index is given by the `middle` argument in this method. There is two main scenarios:
    - If `middle` is +1, then forward is the transform at relative index 0 and inverse is the transform at relative index 2.
    - If `middle` is -1, then forward is the transform at relative index -2 and inverse is the transform at relative index 0.
    If inverse is the
    invalid reference

        inverse

    of forward, then this method invokes `mapper​.apply(middle)` where middle is the transform between the forward and inverse steps. If the mapper returns a non-null value, then that value replaces the three steps of above-cited chain.

    #### Example

    In a chain of Reverse Mercator → Longitude rotation → Forward Mercator transforms where the forward and reverse Mercator steps operate on coordinates in radians on an ellipsoid having a semi-major axis length of one (this is how Apache SIS implements map projections), then because these Mercator steps just pass-through the longitude value unchanged and without using them in calculation, these three steps can be replaced by the Longitude rotation step alone (it is mathematically equivalent because of the radian units and the semi-major axis length of 1).
    Parameters:
      `middle` - relative index of the transform which is potentially a middle step. Value can be -1 or +1 for the step before or after the transform on which `tryConcatenate(…)` has been invoked.
      `mapper` - a function receiving the middle transform in argument and returning the replacement for the forward → middle → inverse chain. May return `null` if the replacement cannot be done.
    Returns:
      whether the replacement has been done as a result of this method call.
    Throws:
      `FactoryException` - if the mapped transform is not a valid replacement.

  - ### replacePassThrough

    public boolean replacePassThrough(Map<Integer,Integer> dimensions) throws FactoryException
    Tries to simplify the transform chain when some coordinates are passed-through the transform at index 0. A "pass-through" coordinate is an input coordinate which is copied in the output tuple by the transform with no change and without using this coordinate value in calculation. In such cases, for any `MathTransform` that use or modify this coordinate value immediately before or after the transform at relative index 0, it does not matter if the order of those operations is modified.

    `AbstractMathTransform​.tryConcatenate(TransformJoiner)` Implementations can invoke this method with a map containing the zero-based indexes of the dimensions that are passed-through by the transform on which `tryConcatenate(…)` has been invoked. Each map key is the index of a pass-through coordinate in source tuples, while the associated map value is the index for the same coordinate but in target tuples. In the common case where the transform does not change the coordinate order, the keys and the values are the same numbers.

    #### Example

    In a spherical to geographic conversion using a biaxial ellipsoid, the longitude value is a pass-through coordinate because the spherical longitude and the geodetic longitude are the same value. Since the implementation of `EllipsoidToCentricTransform` does not use the longitude value in its calculation (in spherical case only), it does not matter if longitudes values are converted from degrees to something else before or after the spherical to geographic conversion. By invoking the following method, unit conversion of longitude values (at dimension 0) may be moved before or after the transform if this method detects that such move would simplify the transform chain:

        @Override
        protected void tryConcatenate(TransformJoiner context) throws FactoryException {
            if (!joiner.replacePassThrough(Map.of(0, 0))) {
                super.tryConcatenate(joiner);
            }
        }

    Parameters:
      `dimensions` - the pass-through dimensions in source tuples (map keys) and target tuples (map values).
    Returns:
      whether the transform chain has been modified as a result of this method call.
    Throws:
      `FactoryException` - if the concatenation is not a valid replacement.
      `IllegalArgumentException` - if a value is repeated twice in the given map.