TransformationUtil.java

/*
 * Copyright (C) 2023 B3Partners B.V.
 *
 * SPDX-License-Identifier: MIT
 */
package org.tailormap.api.geotools;

import jakarta.validation.constraints.NotNull;
import org.geotools.api.data.SimpleFeatureSource;
import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.api.referencing.operation.MathTransform;
import org.geotools.referencing.CRS;
import org.tailormap.api.persistence.Application;

public class TransformationUtil {
  private TransformationUtil() {
    // utility class
  }

  /**
   * Determine whether we need to transform geometries to the application CRS. Note that this uses the "default
   * geometry" attribute of the feature source, in cases where a feature source has multiple geometry attributes (with
   * possibly different CRSs) or heterogenous CRSs across a single geometry attribute this may not be accurate.
   *
   * @param application the referenced application
   * @param simpleFeatureSource the feature source used in the application
   * @return {@code null} when no transform is required and a valid transform otherwise
   * @throws FactoryException when the CRS cannot be decoded
   */
  public static MathTransform getTransformationToApplication(
      @NotNull Application application, @NotNull SimpleFeatureSource simpleFeatureSource)
      throws FactoryException {
    // this is the CRS of the "default geometry" attribute
    final CoordinateReferenceSystem dataSourceCRS =
        simpleFeatureSource.getSchema().getCoordinateReferenceSystem();
    final CoordinateReferenceSystem appCRS = CRS.decode(application.getCrs());
    if (!CRS.equalsIgnoreMetadata(dataSourceCRS, appCRS)) {
      return CRS.findMathTransform(dataSourceCRS, appCRS);
    } else {
      return null;
    }
  }

  /**
   * Determine whether we need to transform geometry to data source crs. Note that this uses the "default geometry"
   * attribute of the feature source, in cases where a feature source has multiple geometry attributes (with possibly
   * different CRSs) or heterogenous CRSs across a single geometry attribute this may not be accurate.
   *
   * @param application the referenced application
   * @param simpleFeatureSource the feature source used in the application
   * @return {@code null} when no transform is required and a valid transform otherwise
   * @throws FactoryException when the CRS cannot be decoded
   */
  public static MathTransform getTransformationToDataSource(
      @NotNull Application application, @NotNull SimpleFeatureSource simpleFeatureSource)
      throws FactoryException {
    MathTransform transform = null;
    // this is the CRS of the "default geometry" attribute
    final CoordinateReferenceSystem dataSourceCRS =
        simpleFeatureSource.getSchema().getCoordinateReferenceSystem();
    final CoordinateReferenceSystem appCRS = CRS.decode(application.getCrs());
    if (!CRS.equalsIgnoreMetadata(dataSourceCRS, appCRS)) {
      transform = CRS.findMathTransform(appCRS, dataSourceCRS);
    }
    return transform;
  }
}