| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.internal.referencing.provider; |
| |
| import java.util.Arrays; |
| import java.lang.reflect.Array; |
| import java.nio.file.Path; |
| import javax.measure.Unit; |
| import javax.measure.Quantity; |
| import org.opengis.parameter.ParameterDescriptor; |
| import org.opengis.parameter.ParameterDescriptorGroup; |
| import org.opengis.parameter.GeneralParameterDescriptor; |
| import org.opengis.referencing.operation.NoninvertibleTransformException; |
| import org.apache.sis.math.DecimalFunctions; |
| import org.apache.sis.util.collection.Cache; |
| import org.apache.sis.parameter.Parameters; |
| import org.apache.sis.referencing.datum.DatumShiftGrid; |
| import org.apache.sis.internal.referencing.j2d.AffineTransform2D; |
| |
| |
| /** |
| * A datum shift grid loaded from a file. |
| * The filename is usually a parameter defined in the EPSG database. |
| * This class should not be in public API because it requires implementation to expose internal mechanic: |
| * Subclasses need to give an access to their internal data (not a copy) through the {@link #getData()} |
| * and {@link #setData(Object[])} methods. We use that for managing the cache, reducing memory usage by |
| * sharing data and for {@link #equals(Object)} and {@link #hashCode()} implementations. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.0 |
| * |
| * @param <C> dimension of the coordinate unit (usually {@link javax.measure.quantity.Angle}). |
| * @param <T> dimension of the translation unit (usually {@link javax.measure.quantity.Angle} |
| * or {@link javax.measure.quantity.Length}). |
| * |
| * @see org.apache.sis.referencing.operation.transform.InterpolatedTransform |
| * |
| * @since 0.7 |
| * @module |
| */ |
| abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> extends DatumShiftGrid<C,T> { |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = -4471670781277328193L; |
| |
| /** |
| * Cache of grids loaded so far. Those grids will be stored by soft references until the amount of |
| * data exceed 32768 (about 128 kilobytes if the values use the {@code float} type). in which case |
| * the oldest grids will be replaced by weak references. |
| */ |
| static final Cache<Object, DatumShiftGridFile<?,?>> CACHE = new Cache<Object, DatumShiftGridFile<?,?>>(4, 32*1024, true) { |
| @Override protected int cost(final DatumShiftGridFile<?,?> grid) { |
| int p = 1; |
| for (final Object array : grid.getData()) { |
| p *= Array.getLength(array); |
| } |
| return p; |
| } |
| }; |
| |
| /** |
| * The parameter descriptor of the provider that created this grid. |
| */ |
| private final ParameterDescriptorGroup descriptor; |
| |
| /** |
| * The files from which the grid has been loaded. This is not used directly by this class |
| * (except for {@link #equals(Object)} and {@link #hashCode()}), but can be used by math |
| * transform for setting the parameter values. Shall never be null and never empty. |
| */ |
| private final Path[] files; |
| |
| /** |
| * Number of grid cells along the <var>x</var> axis. |
| * This is <code>{@linkplain #getGridSize()}[0]</code> as a field for performance reasons. |
| */ |
| protected final int nx; |
| |
| /** |
| * The best translation accuracy that we can expect from this file. |
| * The unit of measurement depends on {@link #isCellValueRatio()}. |
| * |
| * <p>This field is initialized to {@link Double#NaN}. It is loader responsibility |
| * to assign a value to this field after {@code DatumShiftGridFile} construction.</p> |
| * |
| * @see #getCellPrecision() |
| */ |
| double accuracy; |
| |
| /** |
| * Creates a new datum shift grid for the given grid geometry. |
| * The actual offset values need to be provided by subclasses. |
| * |
| * @param coordinateUnit the unit of measurement of input values, before conversion to grid indices by {@code coordinateToGrid}. |
| * @param translationUnit the unit of measurement of output values. |
| * @param isCellValueRatio {@code true} if results of {@link #interpolateInCell interpolateInCell(…)} are divided by grid cell size. |
| * @param x0 longitude in degrees of the center of the cell at grid index (0,0). |
| * @param y0 latitude in degrees of the center of the cell at grid index (0,0). |
| * @param Δx increment in <var>x</var> value between cells at index <var>gridX</var> and <var>gridX</var> + 1. |
| * @param Δy increment in <var>y</var> value between cells at index <var>gridY</var> and <var>gridY</var> + 1. |
| * @param nx number of cells along the <var>x</var> axis in the grid. |
| * @param ny number of cells along the <var>y</var> axis in the grid. |
| * @param descriptor the parameter descriptor of the provider that created this grid. |
| * @param files the file(s) from which the grid has been loaded. This array is not cloned. |
| */ |
| DatumShiftGridFile(final Unit<C> coordinateUnit, |
| final Unit<T> translationUnit, |
| final boolean isCellValueRatio, |
| final double x0, final double y0, |
| final double Δx, final double Δy, |
| final int nx, final int ny, |
| final ParameterDescriptorGroup descriptor, |
| final Path... files) throws NoninvertibleTransformException |
| { |
| super(coordinateUnit, new AffineTransform2D(Δx, 0, 0, Δy, x0, y0).inverse(), |
| new int[] {nx, ny}, isCellValueRatio, translationUnit); |
| this.descriptor = descriptor; |
| this.files = files; |
| this.nx = nx; |
| this.accuracy = Double.NaN; |
| } |
| |
| /** |
| * Creates a new datum shift grid with the same grid geometry than the given grid. |
| * |
| * @param other the other datum shift grid from which to copy the grid geometry. |
| */ |
| protected DatumShiftGridFile(final DatumShiftGridFile<C,T> other) { |
| super(other); |
| descriptor = other.descriptor; |
| files = other.files; |
| nx = other.nx; |
| accuracy = other.accuracy; |
| } |
| |
| /** |
| * Returns {@code this} casted to the given type, after verification that those types are valid. |
| * This method is invoked after {@link NADCON}, {@link NTv2} or other providers got an existing |
| * {@code DatumShiftGridFile} instance from the {@link #CACHE}. |
| */ |
| @SuppressWarnings("unchecked") |
| final <NC extends Quantity<NC>, NT extends Quantity<NT>> DatumShiftGridFile<NC,NT> castTo( |
| final Class<NC> coordinateType, final Class<NT> translationType) |
| { |
| super.getCoordinateUnit() .asType(coordinateType); |
| super.getTranslationUnit().asType(translationType); |
| return (DatumShiftGridFile<NC,NT>) this; |
| } |
| |
| /** |
| * If a grid exists in the cache for the same data, returns a new grid sharing the same data arrays. |
| * Otherwise returns {@code this}. |
| * |
| * @return a grid using the same data than this grid, or {@code this}. |
| * |
| * @see #getData() |
| * @see #setData(Object[]) |
| */ |
| protected final DatumShiftGridFile<C,T> useSharedData() { |
| final Object[] data = getData(); |
| for (final DatumShiftGridFile<?,?> grid : CACHE.values()) { |
| final Object[] other = grid.getData(); |
| if (Arrays.deepEquals(data, other)) { |
| return setData(other); |
| } |
| } |
| return this; |
| } |
| |
| /** |
| * Returns a new grid with the same geometry than this grid but different data arrays. |
| * This method is invoked by {@link #useSharedData()} when it detected that a newly created grid uses |
| * the same data than an existing grid. The typical use case is when a filename is different but still |
| * reference the same grid (e.g. symbolic link, lower case versus upper case in a case-insensitive file |
| * system). |
| * |
| * @param other data from another {@code DatumShiftGridFile} that we can share. |
| * @return a new {@code DatumShiftGridFile} using the given data reference. |
| */ |
| protected abstract DatumShiftGridFile<C,T> setData(Object[] other); |
| |
| /** |
| * Returns the data for each shift dimensions. This method is for cache management, {@link #equals(Object)} |
| * and {@link #hashCode()} implementations only and should not be invoked in other context. |
| * |
| * @return a direct (not cloned) reference to the internal data array. |
| */ |
| protected abstract Object[] getData(); |
| |
| /** |
| * Suggests a precision for the translation values in this grid. |
| * This information is used for deciding when to stop iterations in inverse transformations. |
| * The default implementation returns the {@linkplain #accuracy} divided by an arbitrary value. |
| * |
| * @return a precision for the translation values in this grid. |
| */ |
| @Override |
| public double getCellPrecision() { |
| return accuracy / 10; // Division by 10 is arbitrary. |
| } |
| |
| /** |
| * Returns the descriptor specified at construction time. |
| * |
| * @return a description of the values in this grid. |
| */ |
| @Override |
| public final ParameterDescriptorGroup getParameterDescriptors() { |
| return descriptor; |
| } |
| |
| /** |
| * Sets all parameters for a value of type {@link Path} to the values given to the constructor. |
| * Subclasses may override for defining other kinds of parameters too. |
| * |
| * @param parameters the parameter group where to set the values. |
| */ |
| @Override |
| public final void getParameterValues(final Parameters parameters) { |
| int i = 0; |
| for (final GeneralParameterDescriptor gd : descriptor.descriptors()) { |
| if (gd instanceof ParameterDescriptor<?>) { |
| final ParameterDescriptor<?> d = (ParameterDescriptor<?>) gd; |
| if (Path.class.isAssignableFrom(d.getValueClass())) { |
| if (i >= files.length) break; // Safety in case of invalid parameters. |
| parameters.getOrCreate(d).setValue(files[i++]); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns {@code true} if the given object is a grid containing the same data than this grid. |
| * |
| * @param other the other object to compare with this datum shift grid. |
| * @return {@code true} if the given object is non-null, of the same class than this {@code DatumShiftGrid} |
| * and contains the same data. |
| */ |
| @Override |
| public boolean equals(final Object other) { |
| if (other == this) { // Optimization for a common case. |
| return true; |
| } |
| if (super.equals(other)) { |
| final DatumShiftGridFile<?,?> that = (DatumShiftGridFile<?,?>) other; |
| return Arrays.equals(files, that.files) && Arrays.deepEquals(getData(), that.getData()); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns a hash code value for this datum shift grid. |
| * |
| * @return {@inheritDoc} |
| */ |
| @Override |
| public int hashCode() { |
| return super.hashCode() + Arrays.hashCode(files); |
| } |
| |
| |
| |
| |
| /** |
| * An implementation of {@link DatumShiftGridFile} which stores the offset values in {@code float[]} arrays. |
| * This class is in internal package (not public API) because it makes the following assumptions: |
| * <ul> |
| * <li>Values <var>x₀</var>, <var>y₀</var>, <var>Δx</var> and <var>Δy</var> |
| * given to the constructor are in degrees and needs to be converted to radians.</li> |
| * <li>Single floating-point precision ({@code float)} is sufficient.</li> |
| * <li>Values were defined in base 10, usually in ASCII files. This assumption has an impact on conversions |
| * from {@code float} to {@code double} performed by the {@link #getCellValue(int, int, int)} method.</li> |
| * </ul> |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 0.7 |
| * @since 0.7 |
| * @module |
| */ |
| static final class Float<C extends Quantity<C>, T extends Quantity<T>> extends DatumShiftGridFile<C,T> { |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = -9221609983475286496L; |
| |
| /** |
| * The translation values. {@code offsets.length} is the number of dimensions, and {@code offsets[dim].length} |
| * shall be the same for all {@code dim} value. Component {@code dim} of the translation vector at coordinate |
| * {@code gridX}, {@code gridY} is {@code offsets[dim][gridX + gridY*nx]}. |
| */ |
| final float[][] offsets; |
| |
| /** |
| * Creates a new datum shift grid with the given grid geometry, filename and number of shift dimensions. |
| * All {@code double} values given to this constructor will be converted from degrees to radians. |
| */ |
| Float(final int dim, |
| final Unit<C> coordinateUnit, |
| final Unit<T> translationUnit, |
| final boolean isCellValueRatio, |
| final double x0, final double y0, |
| final double Δx, final double Δy, |
| final int nx, final int ny, |
| final ParameterDescriptorGroup descriptor, |
| final Path... files) throws NoninvertibleTransformException |
| { |
| super(coordinateUnit, translationUnit, isCellValueRatio, x0, y0, Δx, Δy, nx, ny, descriptor, files); |
| offsets = new float[dim][]; |
| final int size = Math.multiplyExact(nx, ny); |
| for (int i=0; i<dim; i++) { |
| Arrays.fill(offsets[i] = new float[size], java.lang.Float.NaN); |
| } |
| } |
| |
| /** |
| * Creates a new grid of the same geometry than the given grid but using a different data array. |
| */ |
| private Float(final DatumShiftGridFile<C,T> grid, final float[][] offsets) { |
| super(grid); |
| this.offsets = offsets; |
| } |
| |
| /** |
| * Returns a new grid with the same geometry than this grid but different data arrays. |
| */ |
| @Override |
| protected final DatumShiftGridFile<C,T> setData(final Object[] other) { |
| return new Float<>(this, (float[][]) other); |
| } |
| |
| /** |
| * Returns direct references (not cloned) to the data arrays. This method is for cache management, |
| * {@link #equals(Object)} and {@link #hashCode()} implementations only and should not be invoked |
| * in other context. |
| */ |
| @Override |
| @SuppressWarnings("ReturnOfCollectionOrArrayField") |
| protected final Object[] getData() { |
| return offsets; |
| } |
| |
| /** |
| * Returns the number of shift dimensions. |
| */ |
| @Override |
| public final int getTranslationDimensions() { |
| return offsets.length; |
| } |
| |
| /** |
| * Returns the cell value at the given dimension and grid index. |
| * This method casts the {@code float} values to {@code double} by setting the extra <em>decimal</em> digits |
| * (not the <em>binary</em> digits) to 0. This is on the assumption that the {@code float} values were parsed |
| * from an ASCII file, or any other medium that format numbers in base 10. |
| * |
| * @param dim the dimension for which to get an average value. |
| * @param gridX the grid index along the <var>x</var> axis, from 0 inclusive to {@link #nx} exclusive. |
| * @param gridY the grid index along the <var>y</var> axis, from 0 inclusive to {@code ny} exclusive. |
| * @return the offset at the given dimension in the grid cell at the given index. |
| */ |
| @Override |
| public final double getCellValue(final int dim, final int gridX, final int gridY) { |
| return DecimalFunctions.floatToDouble(offsets[dim][gridX + gridY*nx]); |
| } |
| } |
| } |