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
commit 8991c55d92826eb9fcda9f3d0570adfedbc33a77 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Jan 14 17:31:56 2023 +0100 Replace `Path` parameter value by `URI` in operation methods using datum shift grids. https://issues.apache.org/jira/browse/SIS-569 --- .../main/java/org/apache/sis/gui/RecentFiles.java | 2 +- .../referencing/provider/DatumShiftGridFile.java | 65 ++-- .../referencing/provider/DatumShiftGridGroup.java | 6 +- .../referencing/provider/DatumShiftGridLoader.java | 53 ++- .../provider/FranceGeocentricInterpolation.java | 359 +++++++++++---------- .../provider/MolodenskyInterpolation.java | 1 + .../sis/internal/referencing/provider/NADCON.java | 93 +++--- .../sis/internal/referencing/provider/NTv2.java | 54 ++-- .../referencing/provider/DatumShiftTestCase.java | 23 +- .../FranceGeocentricInterpolationTest.java | 34 +- .../internal/referencing/provider/NADCONTest.java | 11 +- .../internal/referencing/provider/NTv2Test.java | 19 +- .../sis/test/integration/DatumShiftTest.java | 10 +- .../apache/sis/internal/system/DataDirectory.java | 23 +- .../src/test/java/org/apache/sis/test/Assume.java | 7 +- .../java/org/apache/sis/test/package-info.java | 2 +- 16 files changed, 403 insertions(+), 359 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/RecentFiles.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/RecentFiles.java index 5ea3439497..7e20c151c2 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/RecentFiles.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/RecentFiles.java @@ -121,7 +121,7 @@ final class RecentFiles implements EventHandler<ActionEvent> { try { file = path.toFile(); } catch (UnsupportedOperationException e) { - // Future version may have an "recently used URI" section. We don't do that for now. + // Recently used URIs are not saved here. return; } final int size = items.size(); diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java index 70510c524d..5b5cf52834 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java @@ -18,12 +18,14 @@ package org.apache.sis.internal.referencing.provider; import java.util.Arrays; import java.util.Collection; +import java.util.AbstractMap; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.logging.Level; +import java.util.concurrent.Callable; import java.lang.reflect.Array; -import java.nio.file.Path; +import java.net.URI; import javax.measure.Unit; import javax.measure.Quantity; import javax.measure.quantity.Angle; @@ -47,10 +49,11 @@ import org.apache.sis.util.collection.Containers; import org.apache.sis.util.resources.Errors; import org.apache.sis.parameter.Parameters; import org.apache.sis.referencing.datum.DatumShiftGrid; -import org.apache.sis.internal.referencing.j2d.AffineTransform2D; +import org.apache.sis.referencing.factory.FactoryDataException; import org.apache.sis.referencing.operation.matrix.AffineTransforms2D; import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.referencing.operation.transform.InterpolatedTransform; +import org.apache.sis.internal.referencing.j2d.AffineTransform2D; /** @@ -62,7 +65,7 @@ import org.apache.sis.referencing.operation.transform.InterpolatedTransform; * sharing data and for {@link #equals(Object)} and {@link #hashCode()} implementations. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * * @param <C> dimension of the coordinate unit (usually {@link Angle}). * @param <T> dimension of the translation unit. Usually {@link Angle}, @@ -76,10 +79,10 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> /** * Serial number for inter-operability with different versions. */ - private static final long serialVersionUID = -5801692909082130314L; + private static final long serialVersionUID = -1690433946781367085L; /** - * Cache of grids loaded so far. The keys are typically {@link java.nio.file.Path}s or a tuple of paths. + * Cache of grids loaded so far. The keys are typically {@link URI}s or a tuple of URIs. * Values are grids stored by hard 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 soft references. * @@ -87,8 +90,10 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> * The use of soft references instead of weak references is on the assumption that users typically use * the same few Coordinate Reference Systems for their work. Consequently, we presume that users will not * load a lot of grids and are likely to reuse the already loaded grids. + * + * @see #getOrLoad(URI, URI, Callable) */ - static final Cache<Object, DatumShiftGridFile<?,?>> CACHE = new Cache<Object, DatumShiftGridFile<?,?>>(4, 32*1024, true) { + private 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 data : grid.getData()) { @@ -112,10 +117,8 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> * 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. - * - * @todo We have a serialization problem here. Possible workaround may be to replace by URI. */ - private final Path[] files; + private final URI[] files; /** * Number of cells between the start of adjacent rows in the grid. This is usually {@code getGridSize(0)}, @@ -150,14 +153,14 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> * in the domain of validity of this grid. Children do not change the way this {@code DatumShiftGrid} * performs its calculation; this list is used only at the time of building {@link MathTransform} tree. * - * <div class="note"><b>Design note:</b> + * <h4>Design note</h4> * we do not provide sub-grids functionality in the {@link DatumShiftGrid} parent class because * the {@link MathTransform} tree will depend on assumptions about {@link #getCoordinateToGrid()}, * in particular that it contains only translations and scales (no rotation, no shear). - * Those assumptions are enforced by the {@link DatumShiftGridFile} constructor.</div> + * Those assumptions are enforced by the {@link DatumShiftGridFile} constructor. * - * This field has protected access for usage by {@link DatumShiftGridGroup} subclass only. - * No access to this field should be done except by subclasses. + * <p>This field has protected access for usage by {@link DatumShiftGridGroup} subclass only. + * No access to this field should be done except by subclasses.</p> * * @see #setSubGrids(Collection) */ @@ -186,7 +189,7 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> final double Δx, final double Δy, final int nx, final int ny, final ParameterDescriptorGroup descriptor, - final Path... files) throws NoninvertibleTransformException + final URI... files) throws NoninvertibleTransformException { super(coordinateUnit, new AffineTransform2D(Δx, 0, 0, Δy, x0, y0).inverse(), new int[] {nx, ny}, isCellValueRatio, translationUnit); @@ -246,6 +249,28 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> // Accuracy to be set by caller. Initial value needs to be zero. } + /** + * Gets the grid from the cache if available, or loads it. + * + * @param f1 the main file to load. + * @param f2 a second file to load, or {@code null} if none. + * @param loader the loader to execute if the grid is not in the cache. + * @return the cached or loaded grid. + * @throws FactoryException if an error occurred while loading the grid. + */ + static DatumShiftGridFile<?,?> getOrLoad(final URI f1, final URI f2, final Callable<DatumShiftGridFile<?,?>> loader) + throws FactoryException + { + final Object key = (f2 != null) ? new AbstractMap.SimpleImmutableEntry<>(f1, f2) : f1; + try { + return CACHE.getOrCreate(key, loader); + } catch (FactoryException e) { + throw e; + } catch (Exception e) { + throw new FactoryDataException(Errors.format(Errors.Keys.CanNotRead_1, f1), e); + } + } + /** * Sets the sub-grids that are direct children of this grid. * This method can be invoked only once. @@ -288,6 +313,7 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> /** * Formats this grid as a tree with its children. + * Used for building a tree representation of children nodes. */ private void toTree(final TreeTable.Node branch) { String label = super.toString(); @@ -420,8 +446,7 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> } /** - * 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. + * Sets all parameters for a value of type {@link URI} to the values given to the constructor. * * @param parameters the parameter group where to set the values. */ @@ -431,8 +456,8 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> 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. + if (URI.class.isAssignableFrom(d.getValueClass())) { + if (i >= files.length) break; // Safety in case of invalid parameters. parameters.getOrCreate(d).setValue(files[i++]); } } @@ -519,7 +544,7 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> final double Δx, final double Δy, final int nx, final int ny, final ParameterDescriptorGroup descriptor, - final Path... files) throws NoninvertibleTransformException + final URI... files) throws NoninvertibleTransformException { super(coordinateUnit, translationUnit, isCellValueRatio, x0, y0, Δx, Δy, nx, ny, descriptor, files); offsets = new float[dim][Math.multiplyExact(nx, ny)]; @@ -631,7 +656,7 @@ abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> final double Δx, final double Δy, final int nx, final int ny, final ParameterDescriptorGroup descriptor, - final Path... files) throws NoninvertibleTransformException + final URI... files) throws NoninvertibleTransformException { super(coordinateUnit, translationUnit, isCellValueRatio, x0, y0, Δx, Δy, nx, ny, descriptor, files); offsets = new double[dim][Math.multiplyExact(nx, ny)]; diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridGroup.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridGroup.java index 7d98c5b849..7e86ebb069 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridGroup.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridGroup.java @@ -18,12 +18,12 @@ package org.apache.sis.internal.referencing.provider; import java.util.Map; import java.util.List; -import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.net.URI; import java.io.IOException; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.geom.AffineTransform; -import java.util.LinkedHashMap; import javax.measure.Quantity; import org.opengis.util.FactoryException; import org.opengis.referencing.operation.NoninvertibleTransformException; @@ -155,7 +155,7 @@ final class DatumShiftGridGroup<C extends Quantity<C>, T extends Quantity<T>> ex * @throws IOException declared because {@link Tile#getRegion()} declares it, but should not happen. */ static <C extends Quantity<C>, T extends Quantity<T>> DatumShiftGridGroup<C,T> create( - final Path file, final List<DatumShiftGridFile<C,T>> subgrids) + final URI file, final List<DatumShiftGridFile<C,T>> subgrids) throws IOException, FactoryException, NoninvertibleTransformException { final TileOrganizer mosaic = new TileOrganizer(null); diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridLoader.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridLoader.java index fdc1a11cc7..1db194ec33 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridLoader.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridLoader.java @@ -17,18 +17,25 @@ package org.apache.sis.internal.referencing.provider; import java.util.logging.Level; +import java.util.logging.Logger; import java.util.logging.LogRecord; import java.util.concurrent.atomic.AtomicBoolean; import java.io.EOFException; import java.io.IOException; +import java.net.URI; import java.nio.ByteBuffer; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.Files; import java.nio.file.NoSuchFileException; +import java.nio.file.FileSystemNotFoundException; import java.nio.channels.ReadableByteChannel; +import java.nio.channels.Channels; import org.opengis.util.FactoryException; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.logging.Logging; import org.apache.sis.internal.system.Loggers; +import org.apache.sis.internal.system.Modules; import org.apache.sis.internal.system.DataDirectory; import org.apache.sis.internal.referencing.Resources; import org.apache.sis.referencing.factory.FactoryDataException; @@ -37,12 +44,13 @@ import org.apache.sis.referencing.factory.MissingFactoryResourceException; /** * Base class of datum shift grid loaders. + * This loader uses {@link ReadableByteChannel}. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * @since 0.7 */ -class DatumShiftGridLoader { +abstract class DatumShiftGridLoader { /** * Conversion factor from degrees to seconds. */ @@ -68,7 +76,7 @@ class DatumShiftGridLoader { /** * The file to load, used for parameter declaration and if we have errors to report. */ - final Path file; + final URI file; /** * The channel opened on the file. @@ -93,7 +101,7 @@ class DatumShiftGridLoader { * @param buffer the buffer to use. * @param file path to the longitude or latitude difference file. Used for parameter declaration and error reporting. */ - DatumShiftGridLoader(final ReadableByteChannel channel, final ByteBuffer buffer, final Path file) throws IOException { + DatumShiftGridLoader(final ReadableByteChannel channel, final ByteBuffer buffer, final URI file) throws IOException { this.file = file; this.buffer = buffer; this.channel = channel; @@ -143,12 +151,45 @@ class DatumShiftGridLoader { buffer.position(p); } + /** + * If the given URI is not absolute, tries to make it absolute + * with a path to the common directory of datum shift grid files. + * + * @param path the URI to make absolute. + * @return an absolute (if possible) URI to the data. + */ + static URI toAbsolutePath(final URI path) { + if (!path.isAbsolute() && !path.isOpaque()) { + final Path dir = DataDirectory.DATUM_CHANGES.getDirectory(); + if (dir != null) { + return dir.resolve(path.getPath()).toUri(); + } + } + return path; + } + + /** + * Creates a channel for reading bytes from the file at the specified path. + * + * @param path the path from where to read bytes. + * @return a channel for reading bytes from the given path. + * @throws IOException if the channel can not be created. + */ + static ReadableByteChannel newByteChannel(final URI path) throws IOException { + try { + return Files.newByteChannel(Paths.get(path)); + } catch (FileSystemNotFoundException e) { + Logging.ignorableException(Logger.getLogger(Modules.REFERENCING), DatumShiftGridLoader.class, "newByteChannel", e); + } + return Channels.newChannel(path.toURL().openStream()); + } + /** * Logs a message about a grid which is about to be loaded. * * @param caller the provider to logs as the source class. * the source method will be set to {@code "createMathTransform"}. - * @param file the grid file, as a {@link String} or a {@link Path}. + * @param file the grid file, as a {@link String} or a {@link URI}. */ static void startLoading(final Class<?> caller, final Object file) { log(caller, Resources.forLocale(null).getLogRecord(Level.FINE, Resources.Keys.LoadingDatumShiftFile_1, file)); @@ -173,7 +214,7 @@ class DatumShiftGridLoader { * @param file the grid file that the subclass tried to load. * @param cause the cause of the failure to load the grid file. */ - static FactoryException canNotLoad(final String format, final Path file, final Exception cause) { + static FactoryException canNotLoad(final String format, final URI file, final Exception cause) { if (!datumDirectoryLogged.get()) { final Path directory = DataDirectory.DATUM_CHANGES.getDirectory(); if (directory != null && !datumDirectoryLogged.getAndSet(true)) { diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java index ddb61882f5..2e7b64259e 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolation.java @@ -16,6 +16,7 @@ */ package org.apache.sis.internal.referencing.provider; +import java.net.URI; import java.util.Map; import java.util.Arrays; import java.util.Locale; @@ -23,12 +24,11 @@ import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.LogRecord; +import java.util.concurrent.Callable; import java.io.BufferedReader; import java.io.EOFException; import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.Files; +import java.io.InputStreamReader; import javax.xml.bind.annotation.XmlTransient; import javax.measure.quantity.Angle; import javax.measure.quantity.Length; @@ -45,7 +45,6 @@ import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.NoninvertibleTransformException; import org.opengis.util.FactoryException; import org.apache.sis.internal.system.Loggers; -import org.apache.sis.internal.system.DataDirectory; import org.apache.sis.internal.referencing.NilReferencingObject; import org.apache.sis.internal.referencing.Resources; import org.apache.sis.parameter.ParameterBuilder; @@ -54,7 +53,6 @@ import org.apache.sis.measure.Units; import org.apache.sis.util.CharSequences; import org.apache.sis.util.logging.Logging; import org.apache.sis.util.resources.Errors; -import org.apache.sis.util.collection.Cache; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.referencing.datum.DefaultEllipsoid; import org.apache.sis.referencing.operation.transform.InterpolatedGeocentricTransform; @@ -87,7 +85,7 @@ import static org.apache.sis.internal.util.Constants.DIM; * * @author Simon Reynard (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * @since 0.7 */ @XmlTransient @@ -95,7 +93,7 @@ public class FranceGeocentricInterpolation extends GeodeticOperation { /** * Serial number for inter-operability with different versions. */ - private static final long serialVersionUID = -4707304160205218546L; + private static final long serialVersionUID = -298193260915837911L; /** * Geocentric translation parameters to use as a first guess before to use the grid in France. @@ -137,7 +135,7 @@ public class FranceGeocentricInterpolation extends GeodeticOperation { * Name of the default grid file, as mentioned in the NTG_88 document. * We use the 5 first characters ({@code "gr3df"}) as a sentinel value for French grid file. * - * @see #isRecognized(Path) + * @see #isRecognized(URI) */ private static final String DEFAULT = "gr3df97a.txt"; @@ -154,7 +152,7 @@ public class FranceGeocentricInterpolation extends GeodeticOperation { * <li>Default value: {@code gr3df97a.txt}</li> * </ul> */ - public static final ParameterDescriptor<Path> FILE; + public static final ParameterDescriptor<URI> FILE; /** * The operation parameter descriptor for the <cite>EPSG code for Interpolation CRS</cite> parameter value. @@ -197,7 +195,7 @@ public class FranceGeocentricInterpolation extends GeodeticOperation { FILE = builder .addIdentifier("8727") .addName("Geocentric translation file") - .create(Path.class, Paths.get(DEFAULT)); + .create(URI.class, URI.create(DEFAULT)); INTERPOLATION_CRS = builder .addIdentifier("1048") .addName("EPSG code for Interpolation CRS") @@ -258,8 +256,10 @@ public class FranceGeocentricInterpolation extends GeodeticOperation { * @param file the grid file. * @return {@code true} if the given file looks like a fie from the French mapping agency. */ - public static boolean isRecognized(final Path file) { - return file.getFileName().toString().regionMatches(true, 0, DEFAULT, 0, 5); + public static boolean isRecognized(final URI file) { + final String filename = file.getPath(); + final int s = filename.lastIndexOf('/') + 1; + return filename.regionMatches(true, s, DEFAULT, 0, 5); } /** @@ -326,7 +326,7 @@ public class FranceGeocentricInterpolation extends GeodeticOperation { default: throw new InvalidParameterValueException(Errors.format( Errors.Keys.IllegalArgumentValue_2, DIM, dim), DIM, dim); } - final Path file = pg.getMandatoryValue(FILE); + final URI file = pg.getMandatoryValue(FILE); final DatumShiftGridFile<Angle,Length> grid = getOrLoad(file, isRecognized(file) ? new double[] {TX, TY, TZ} : null, PRECISION); @@ -361,176 +361,205 @@ public class FranceGeocentricInterpolation extends GeodeticOperation { * Returns the grid of the given name. This method returns the cached instance if it still exists, * or load the grid otherwise. * - * @param file name of the datum shift grid file to load. + * @param file an absolute or relative reference to the datum shift grid file to load. * @param averages an "average" value for the offset in each dimension, or {@code null} if unknown. * @param scale the factor by which to multiply each compressed value before to add to the average value. */ - static DatumShiftGridFile<Angle,Length> getOrLoad(final Path file, final double[] averages, final double scale) + static DatumShiftGridFile<Angle,Length> getOrLoad(final URI file, final double[] averages, final double scale) throws FactoryException { - final Path resolved = DataDirectory.DATUM_CHANGES.resolve(file).toAbsolutePath(); - DatumShiftGridFile<?,?> grid = DatumShiftGridFile.CACHE.peek(resolved); - if (grid == null) { - final Cache.Handler<DatumShiftGridFile<?,?>> handler = DatumShiftGridFile.CACHE.lock(resolved); - try { - grid = handler.peek(); - if (grid == null) { - try (BufferedReader in = Files.newBufferedReader(resolved)) { - DatumShiftGridLoader.startLoading(FranceGeocentricInterpolation.class, file); - final DatumShiftGridFile.Float<Angle,Length> g = load(in, file); - grid = DatumShiftGridCompressed.compress(g, averages, scale); - } catch (IOException | NoninvertibleTransformException | RuntimeException e) { - // NumberFormatException, ArithmeticException, NoSuchElementException, possibly other. - throw DatumShiftGridLoader.canNotLoad(HEADER, file, e); - } - grid = grid.useSharedData(); - } - } finally { - handler.putAndUnlock(grid); - } - } - return grid.castTo(Angle.class, Length.class); + final URI resolved = DatumShiftGridLoader.toAbsolutePath(file); + return DatumShiftGridFile.getOrLoad(resolved, null, new Loader(resolved, averages, scale)) + .castTo(Angle.class, Length.class); } /** - * Unconditionally loads the grid for the given file without in-memory compression. - * - * @param in reader of the RGF93 datum shift file. - * @param file path to the file being read, used for parameter declaration and error reporting. - * @throws IOException if an I/O error occurred. - * @throws NumberFormatException if a number cannot be parsed. - * @throws NoSuchElementException if a data line is missing a value. - * @throws FactoryException if an problem is found with the file content. - * @throws ArithmeticException if the width or the height exceed the integer capacity. + * Temporary object created for loading gridded data if not present in the cache. + * The data are provided in a text file, which is read with {@link BufferedReader}. */ - static DatumShiftGridFile.Float<Angle,Length> load(final BufferedReader in, final Path file) - throws IOException, FactoryException, NoninvertibleTransformException - { - DatumShiftGridFile.Float<Angle,Length> grid = null; - double x0 = 0; - double xf = 0; - double y0 = 0; - double yf = 0; - double Δx = 0; - double Δy = 0; - int nx = 0; - int ny = 0; - /* - * The header should be like below, but the only essential line for this class is the one - * starting with "GR3D1". We also check that "GR3D2" declares the expected interpolation. + static final class Loader implements Callable<DatumShiftGridFile<?,?>> { + /** The file to load. */ + private final URI file; + + /** An "average" value for the offset in each dimension, or {@code null} if unknown. */ + private final double[] averages; + + /** The factor by which to multiply each compressed value before to add to the average value. */ + private final double scale; + + /** Creates a new loader for the given file. */ + Loader(final URI file, final double[] averages, final double scale) { + this.file = file; + this.averages = averages; + this.scale = scale; + } + + /** Returns the reader for the specified URI. */ + static BufferedReader newBufferedReader(final URI file) throws IOException { + return new BufferedReader(new InputStreamReader(file.toURL().openStream())); + } + + /** + * Invoked when the gridded data are not in the cache. + * This method load grid data from the file specified at construction time. * - * GR3D 002024 024 20370201 - * GR3D1 -5.5000 10.0000 41.0000 52.0000 .1000 .1000 - * GR3D2 INTERPOLATION BILINEAIRE - * GR3D3 PREC CM 01:5 02:10 03:20 04:50 99>100 + * @return the loaded grid data. + * @throws FactoryException if an error occurred while loading the grid data. */ - String line; - while (true) { - line = in.readLine(); - if (line == null) { - throw new EOFException(Errors.format(Errors.Keys.UnexpectedEndOfFile_1, file)); - } - final int length = CharSequences.skipTrailingWhitespaces(line, 0, line.length()); - if (length <= 0) { - continue; // Skip empty lines. + @Override + public DatumShiftGridFile<?,?> call() throws FactoryException { + final DatumShiftGridFile<?,?> grid; + try (BufferedReader in = newBufferedReader(file)) { + DatumShiftGridLoader.startLoading(FranceGeocentricInterpolation.class, file); + final DatumShiftGridFile.Float<Angle,Length> g = load(in, file); + grid = DatumShiftGridCompressed.compress(g, averages, scale); + } catch (IOException | NoninvertibleTransformException | RuntimeException e) { + // NumberFormatException, ArithmeticException, NoSuchElementException, possibly other. + throw DatumShiftGridLoader.canNotLoad(HEADER, file, e); } - int p = CharSequences.skipLeadingWhitespaces(line, 0, length); - if (line.charAt(p) == '#') { - continue; // Skip comment lines (not officially part of the format). - } - if (!line.regionMatches(true, p, HEADER, 0, HEADER.length())) { - break; // End of header. - } - if ((p += HEADER.length()) < length) { - final char c = line.charAt(p); - p = CharSequences.skipLeadingWhitespaces(line, p+1, length); - switch (c) { - case '1': { - if (grid != null) { - throw new FactoryException(Errors.format(Errors.Keys.DuplicatedElement_1, HEADER)); - } - final double[] gridGeometry = CharSequences.parseDoubles(line.substring(p, length), ' '); - if (gridGeometry.length == 6) { - x0 = gridGeometry[0]; - xf = gridGeometry[1]; - y0 = gridGeometry[2]; - yf = gridGeometry[3]; - Δx = gridGeometry[4]; - Δy = gridGeometry[5]; - nx = Math.toIntExact(Math.round((xf - x0) / Δx + 1)); - ny = Math.toIntExact(Math.round((yf - y0) / Δy + 1)); - grid = new DatumShiftGridFile.Float<>(3, - Units.DEGREE, Units.METRE, false, - x0, y0, Δx, Δy, nx, ny, PARAMETERS, file); - grid.accuracy = Double.NaN; - for (final float[] data : grid.offsets) { - Arrays.fill(data, Float.NaN); + return grid.useSharedData(); + } + + /** + * Unconditionally loads the grid for the given file without in-memory compression. + * + * @param in reader of the RGF93 datum shift file. + * @param file path to the file being read, used for parameter declaration and error reporting. + * @throws IOException if an I/O error occurred. + * @throws NumberFormatException if a number cannot be parsed. + * @throws NoSuchElementException if a data line is missing a value. + * @throws FactoryException if an problem is found with the file content. + * @throws ArithmeticException if the width or the height exceed the integer capacity. + */ + static DatumShiftGridFile.Float<Angle,Length> load(final BufferedReader in, final URI file) + throws IOException, FactoryException, NoninvertibleTransformException + { + DatumShiftGridFile.Float<Angle,Length> grid = null; + double x0 = 0; + double xf = 0; + double y0 = 0; + double yf = 0; + double Δx = 0; + double Δy = 0; + int nx = 0; + int ny = 0; + /* + * The header should be like below, but the only essential line for this class is the one + * starting with "GR3D1". We also check that "GR3D2" declares the expected interpolation. + * + * GR3D 002024 024 20370201 + * GR3D1 -5.5000 10.0000 41.0000 52.0000 .1000 .1000 + * GR3D2 INTERPOLATION BILINEAIRE + * GR3D3 PREC CM 01:5 02:10 03:20 04:50 99>100 + */ + String line; + while (true) { + line = in.readLine(); + if (line == null) { + throw new EOFException(Errors.format(Errors.Keys.UnexpectedEndOfFile_1, file)); + } + final int length = CharSequences.skipTrailingWhitespaces(line, 0, line.length()); + if (length <= 0) { + continue; // Skip empty lines. + } + int p = CharSequences.skipLeadingWhitespaces(line, 0, length); + if (line.charAt(p) == '#') { + continue; // Skip comment lines (not officially part of the format). + } + if (!line.regionMatches(true, p, HEADER, 0, HEADER.length())) { + break; // End of header. + } + if ((p += HEADER.length()) < length) { + final char c = line.charAt(p); + p = CharSequences.skipLeadingWhitespaces(line, p+1, length); + switch (c) { + case '1': { + if (grid != null) { + throw new FactoryException(Errors.format(Errors.Keys.DuplicatedElement_1, HEADER)); + } + final double[] gridGeometry = CharSequences.parseDoubles(line.substring(p, length), ' '); + if (gridGeometry.length == 6) { + x0 = gridGeometry[0]; + xf = gridGeometry[1]; + y0 = gridGeometry[2]; + yf = gridGeometry[3]; + Δx = gridGeometry[4]; + Δy = gridGeometry[5]; + nx = Math.toIntExact(Math.round((xf - x0) / Δx + 1)); + ny = Math.toIntExact(Math.round((yf - y0) / Δy + 1)); + grid = new DatumShiftGridFile.Float<>(3, + Units.DEGREE, Units.METRE, false, + x0, y0, Δx, Δy, nx, ny, PARAMETERS, file); + grid.accuracy = Double.NaN; + for (final float[] data : grid.offsets) { + Arrays.fill(data, Float.NaN); + } } + break; } - break; - } - case '2': { - final String interp = line.substring(p, length); - if (!interp.matches("(?i)INTERPOLATION[^A-Z]+BILINEAIRE")) { - final LogRecord record = Errors.getResources((Locale) null).getLogRecord( - Level.WARNING, Errors.Keys.UnsupportedInterpolation_1, interp); - record.setLoggerName(Loggers.COORDINATE_OPERATION); - Logging.log(FranceGeocentricInterpolation.class, "createMathTransform", record); - // We declare `createMathTransform(…)` method because it is closer to public API. + case '2': { + final String interp = line.substring(p, length); + if (!interp.matches("(?i)INTERPOLATION[^A-Z]+BILINEAIRE")) { + final LogRecord record = Errors.getResources((Locale) null).getLogRecord( + Level.WARNING, Errors.Keys.UnsupportedInterpolation_1, interp); + record.setLoggerName(Loggers.COORDINATE_OPERATION); + Logging.log(FranceGeocentricInterpolation.class, "createMathTransform", record); + // We declare `createMathTransform(…)` method because it is closer to public API. + } + break; } - break; } } } - } - if (grid == null) { - throw new FactoryException(Resources.format(Resources.Keys.FileNotFound_2, HEADER, file)); - } - /* - * Loads the data with the sign of all offsets reversed. Data columns are - * - * (unknown), longitude, latitude, tX, tY, tZ, accuracy code, data sheet (ignored) - * - * where the longitude and latitude values are in RGF93 system. - * Example: - * - * 00002 -5.500000000 41.000000000 -165.027 -67.100 315.813 99 -0158 - * 00002 -5.500000000 41.100000000 -165.169 -66.948 316.007 99 -0157 - * 00002 -5.500000000 41.200000000 -165.312 -66.796 316.200 99 -0157 - * - * Translation values in the IGN file are from NTF to RGF93, but Apache SIS implementation needs - * the opposite direction (from RGF93 to NTF). The reason is that SIS expect the source datum to - * be the datum in which longitude and latitude values are expressed. - */ - final float[] tX = grid.offsets[0]; - final float[] tY = grid.offsets[1]; - final float[] tZ = grid.offsets[2]; - do { - final StringTokenizer t = new StringTokenizer(line.trim()); - t.nextToken(); // Ignored - final double x = Double.parseDouble(t.nextToken()); // Longitude in degrees - final double y = Double.parseDouble(t.nextToken()); // Latitude in degrees - final int i = Math.toIntExact(Math.round((x - x0) / Δx)); // Column index - final int j = Math.toIntExact(Math.round((y - y0) / Δy)); // Row index - if (i < 0 || i >= nx) { - throw new FactoryException(Errors.format(Errors.Keys.ValueOutOfRange_4, "x", x, x0, xf)); - } - if (j < 0 || j >= ny) { - throw new FactoryException(Errors.format(Errors.Keys.ValueOutOfRange_4, "y", y, y0, yf)); - } - final int p = j*nx + i; - if (!Double.isNaN(tX[p]) || !Double.isNaN(tY[p]) || !Double.isNaN(tZ[p])) { - throw new FactoryException(Errors.format(Errors.Keys.ValueAlreadyDefined_1, x + ", " + y)); - } - tX[p] = -parseFloat(t.nextToken()); // See javadoc for the reason why we reverse the sign. - tY[p] = -parseFloat(t.nextToken()); - tZ[p] = -parseFloat(t.nextToken()); - final double accuracy = ACCURACY[Math.min(ACCURACY.length - 1, - Math.max(0, Integer.parseInt(t.nextToken()) - 1))]; - if (!(accuracy >= grid.accuracy)) { // Use `!` for replacing the initial NaN. - grid.accuracy = accuracy; + if (grid == null) { + throw new FactoryException(Resources.format(Resources.Keys.FileNotFound_2, HEADER, file)); } - } while ((line = in.readLine()) != null); - return grid; + /* + * Loads the data with the sign of all offsets reversed. Data columns are + * + * (unknown), longitude, latitude, tX, tY, tZ, accuracy code, data sheet (ignored) + * + * where the longitude and latitude values are in RGF93 system. + * Example: + * + * 00002 -5.500000000 41.000000000 -165.027 -67.100 315.813 99 -0158 + * 00002 -5.500000000 41.100000000 -165.169 -66.948 316.007 99 -0157 + * 00002 -5.500000000 41.200000000 -165.312 -66.796 316.200 99 -0157 + * + * Translation values in the IGN file are from NTF to RGF93, but Apache SIS implementation needs + * the opposite direction (from RGF93 to NTF). The reason is that SIS expect the source datum to + * be the datum in which longitude and latitude values are expressed. + */ + final float[] tX = grid.offsets[0]; + final float[] tY = grid.offsets[1]; + final float[] tZ = grid.offsets[2]; + do { + final StringTokenizer t = new StringTokenizer(line.trim()); + t.nextToken(); // Ignored + final double x = Double.parseDouble(t.nextToken()); // Longitude in degrees + final double y = Double.parseDouble(t.nextToken()); // Latitude in degrees + final int i = Math.toIntExact(Math.round((x - x0) / Δx)); // Column index + final int j = Math.toIntExact(Math.round((y - y0) / Δy)); // Row index + if (i < 0 || i >= nx) { + throw new FactoryException(Errors.format(Errors.Keys.ValueOutOfRange_4, "x", x, x0, xf)); + } + if (j < 0 || j >= ny) { + throw new FactoryException(Errors.format(Errors.Keys.ValueOutOfRange_4, "y", y, y0, yf)); + } + final int p = j*nx + i; + if (!Double.isNaN(tX[p]) || !Double.isNaN(tY[p]) || !Double.isNaN(tZ[p])) { + throw new FactoryException(Errors.format(Errors.Keys.ValueAlreadyDefined_1, x + ", " + y)); + } + tX[p] = -parseFloat(t.nextToken()); // See javadoc for the reason why we reverse the sign. + tY[p] = -parseFloat(t.nextToken()); + tZ[p] = -parseFloat(t.nextToken()); + final double accuracy = ACCURACY[Math.min(ACCURACY.length - 1, + Math.max(0, Integer.parseInt(t.nextToken()) - 1))]; + if (!(accuracy >= grid.accuracy)) { // Use `!` for replacing the initial NaN. + grid.accuracy = accuracy; + } + } while ((line = in.readLine()) != null); + return grid; + } } } diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java index 33f3eb118f..e74f6274fe 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MolodenskyInterpolation.java @@ -47,6 +47,7 @@ import org.apache.sis.referencing.operation.transform.InterpolatedMolodenskyTran */ @XmlTransient @Deprecated(since="1.4", forRemoval=true) +// Note: after removal, delete overrideable method in parent class. public final class MolodenskyInterpolation extends FranceGeocentricInterpolation { /** * Serial number for inter-operability with different versions. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NADCON.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NADCON.java index 0828872fd7..e50e10723d 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NADCON.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NADCON.java @@ -16,14 +16,11 @@ */ package org.apache.sis.internal.referencing.provider; -import java.util.AbstractMap; import java.io.IOException; +import java.net.URI; import java.nio.ByteOrder; import java.nio.ByteBuffer; import java.nio.FloatBuffer; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.Files; import java.nio.channels.ReadableByteChannel; import javax.xml.bind.annotation.XmlTransient; import javax.measure.quantity.Angle; @@ -40,9 +37,7 @@ import org.opengis.referencing.operation.NoninvertibleTransformException; import org.apache.sis.parameter.ParameterBuilder; import org.apache.sis.parameter.Parameters; import org.apache.sis.util.CharSequences; -import org.apache.sis.util.collection.Cache; import org.apache.sis.util.resources.Errors; -import org.apache.sis.internal.system.DataDirectory; import org.apache.sis.measure.Units; @@ -56,7 +51,7 @@ import org.apache.sis.measure.Units; * * @author Martin Desruisseaux (Geomatys) * @author Rueben Schulz (UBC) - * @version 1.3 + * @version 1.4 * * @see <a href="http://www.ngs.noaa.gov/cgi-bin/nadcon.prl">NADCON on-line computation</a> * @@ -83,7 +78,7 @@ public final class NADCON extends AbstractProvider { * <li>Default value: {@code conus.las}</li> * </ul> */ - private static final ParameterDescriptor<Path> LATITUDE; + private static final ParameterDescriptor<URI> LATITUDE; /** * The operation parameter descriptor for the <cite>"Longitude difference file"</cite> parameter value. @@ -99,7 +94,7 @@ public final class NADCON extends AbstractProvider { * <li>Default value: {@code conus.los}</li> * </ul> */ - private static final ParameterDescriptor<Path> LONGITUDE; + private static final ParameterDescriptor<URI> LONGITUDE; /** * The group of all parameters expected by this coordinate operation. @@ -110,11 +105,11 @@ public final class NADCON extends AbstractProvider { LATITUDE = builder .addIdentifier("8657") .addName("Latitude difference file") - .create(Path.class, Paths.get("conus.las")); + .create(URI.class, URI.create("conus.las")); LONGITUDE = builder .addIdentifier("8658") .addName("Longitude difference file") - .create(Path.class, Paths.get("conus.los")); + .create(URI.class, URI.create("conus.los")); PARAMETERS = builder .addIdentifier("9613") .addName("NADCON") @@ -149,52 +144,42 @@ public final class NADCON extends AbstractProvider { } /** - * Returns the grid of the given name. This method returns the cached instance if it still exists, - * or load the grid otherwise. + * Returns the grid of the given name. + * This method returns the cached instance if it still exists, or load the grid otherwise. * - * @param latitudeShifts name of the grid file for latitude shifts. - * @param longitudeShifts name of the grid file for longitude shifts. + * @param latitudeShifts relative or absolute path of the grid file for latitude shifts. + * @param longitudeShifts relative or absolute path name of the grid file for longitude shifts. */ - static DatumShiftGridFile<Angle,Angle> getOrLoad(final Path latitudeShifts, final Path longitudeShifts) + static DatumShiftGridFile<Angle,Angle> getOrLoad(final URI latitudeShifts, final URI longitudeShifts) throws FactoryException { - final Path rlat = DataDirectory.DATUM_CHANGES.resolve(latitudeShifts).toAbsolutePath(); - final Path rlon = DataDirectory.DATUM_CHANGES.resolve(longitudeShifts).toAbsolutePath(); - final Object key = new AbstractMap.SimpleImmutableEntry<>(rlat, rlon); - DatumShiftGridFile<?,?> grid = DatumShiftGridFile.CACHE.peek(key); - if (grid == null) { - final Cache.Handler<DatumShiftGridFile<?,?>> handler = DatumShiftGridFile.CACHE.lock(key); + final URI rlat = Loader.toAbsolutePath(latitudeShifts); + final URI rlon = Loader.toAbsolutePath(longitudeShifts); + return DatumShiftGridFile.getOrLoad(rlat, rlon, () -> { + final Loader loader; + URI file = latitudeShifts; + final DatumShiftGridFile<?,?> grid; try { - grid = handler.peek(); - if (grid == null) { - final Loader loader; - Path file = latitudeShifts; - try { - // Note: buffer size must be divisible by the size of `float` data type. - final ByteBuffer buffer = ByteBuffer.allocate(4096).order(ByteOrder.LITTLE_ENDIAN); - final FloatBuffer fb = buffer.asFloatBuffer(); - try (ReadableByteChannel in = Files.newByteChannel(rlat)) { - DatumShiftGridLoader.startLoading(NADCON.class, CharSequences.commonPrefix( - latitudeShifts.toString(), longitudeShifts.toString()).toString() + '…'); - loader = new Loader(in, buffer, file); - loader.readGrid(fb, null, longitudeShifts); - } - buffer.clear(); - file = longitudeShifts; - try (ReadableByteChannel in = Files.newByteChannel(rlon)) { - new Loader(in, buffer, file).readGrid(fb, loader, null); - } - } catch (IOException | NoninvertibleTransformException | RuntimeException e) { - throw DatumShiftGridLoader.canNotLoad("NADCON", file, e); - } - grid = DatumShiftGridCompressed.compress(loader.grid, null, loader.grid.accuracy); - grid = grid.useSharedData(); + // Note: buffer size must be divisible by the size of `float` data type. + final ByteBuffer buffer = ByteBuffer.allocate(4096).order(ByteOrder.LITTLE_ENDIAN); + final FloatBuffer fb = buffer.asFloatBuffer(); + try (ReadableByteChannel in = Loader.newByteChannel(rlat)) { + DatumShiftGridLoader.startLoading(NADCON.class, CharSequences.commonPrefix( + latitudeShifts.toString(), longitudeShifts.toString()).toString() + '…'); + loader = new Loader(in, buffer, file); + loader.readGrid(fb, null, longitudeShifts); } - } finally { - handler.putAndUnlock(grid); + buffer.clear(); + file = longitudeShifts; + try (ReadableByteChannel in = Loader.newByteChannel(rlon)) { + new Loader(in, buffer, file).readGrid(fb, loader, null); + } + } catch (IOException | NoninvertibleTransformException | RuntimeException e) { + throw DatumShiftGridLoader.canNotLoad("NADCON", file, e); } - } - return grid.castTo(Angle.class, Angle.class); + grid = DatumShiftGridCompressed.compress(loader.grid, null, loader.grid.accuracy); + return grid.useSharedData(); + }).castTo(Angle.class, Angle.class); } @@ -260,7 +245,7 @@ public final class NADCON extends AbstractProvider { private final StringBuilder ascii; /** - * The grid created by {@link #readGrid(FloatBuffer, Loader, Path)}. + * The grid created by {@link #readGrid(FloatBuffer, Loader, URI)}. */ DatumShiftGridFile.Float<Angle,Angle> grid; @@ -273,7 +258,7 @@ public final class NADCON extends AbstractProvider { * and have a capacity divisible by the size of the {@code float} type. * @param file path to the longitude or latitude difference file. Used for parameter declaration and error reporting. */ - Loader(final ReadableByteChannel channel, final ByteBuffer buffer, final Path file) + Loader(final ReadableByteChannel channel, final ByteBuffer buffer, final URI file) throws IOException, FactoryException { super(channel, buffer, file); @@ -387,7 +372,7 @@ public final class NADCON extends AbstractProvider { * @param latitudeShifts the previously loaded latitude shifts, or {@code null} if not yet loaded. * @param longitudeShifts the file for the longitude grid. */ - final void readGrid(final FloatBuffer fb, final Loader latitudeShifts, final Path longitudeShifts) + final void readGrid(final FloatBuffer fb, final Loader latitudeShifts, final URI longitudeShifts) throws IOException, FactoryException, NoninvertibleTransformException { final int dim; @@ -403,7 +388,7 @@ public final class NADCON extends AbstractProvider { y0 != latitudeShifts.y0 || Δy != latitudeShifts.Δy || ny != latitudeShifts.ny || nz != latitudeShifts.nz) { throw new FactoryException(Errors.format(Errors.Keys.MismatchedGridGeometry_2, - latitudeShifts.file.getFileName(), file.getFileName())); + latitudeShifts.file.getPath(), file.getPath())); } dim = 0; // Dimension of longitudes scale = -DEGREES_TO_SECONDS * Δx; // NADCON shifts are positive west. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java index 108d96fe9e..a44fb3a054 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/NTv2.java @@ -25,10 +25,9 @@ import java.util.Arrays; import java.util.Locale; import java.util.logging.Level; import java.io.IOException; +import java.net.URI; import java.nio.ByteOrder; import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Path; import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; import javax.xml.bind.annotation.XmlTransient; @@ -44,13 +43,11 @@ import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.Transformation; import org.opengis.referencing.operation.NoninvertibleTransformException; -import org.apache.sis.internal.system.DataDirectory; import org.apache.sis.internal.referencing.Formulas; import org.apache.sis.internal.referencing.Resources; import org.apache.sis.internal.util.Strings; import org.apache.sis.parameter.ParameterBuilder; import org.apache.sis.parameter.Parameters; -import org.apache.sis.util.collection.Cache; import org.apache.sis.util.collection.Containers; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.resources.Messages; @@ -63,7 +60,7 @@ import org.apache.sis.measure.Units; * * @author Simon Reynard (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * @since 0.7 */ @XmlTransient @@ -87,7 +84,7 @@ public final class NTv2 extends AbstractProvider { * <li>No default value</li> * </ul> */ - static final ParameterDescriptor<Path> FILE; + static final ParameterDescriptor<URI> FILE; /** * The group of all parameters expected by this coordinate operation. @@ -98,7 +95,7 @@ public final class NTv2 extends AbstractProvider { FILE = builder .addIdentifier("8656") .addName("Latitude and longitude difference file") - .create(Path.class, null); + .create(URI.class, null); PARAMETERS = builder .addIdentifier("9615") .addName("NTv2") @@ -151,38 +148,29 @@ public final class NTv2 extends AbstractProvider { } /** - * Returns the grid of the given name. This method returns the cached instance if it still exists, - * or load the grid otherwise. + * Returns the grid of the given name. + * This method returns the cached instance if it still exists, or load the grid otherwise. * * @param provider the provider which is creating a transform. - * @param file name of the datum shift grid file to load. + * @param file relative or absolute path of the datum shift grid file to load. * @param version the expected version (1 or 2). */ static DatumShiftGridFile<Angle,Angle> getOrLoad(final Class<? extends AbstractProvider> provider, - final Path file, final int version) throws FactoryException + final URI file, final int version) throws FactoryException { - final Path resolved = DataDirectory.DATUM_CHANGES.resolve(file).toAbsolutePath(); - DatumShiftGridFile<?,?> grid = DatumShiftGridFile.CACHE.peek(resolved); - if (grid == null) { - final Cache.Handler<DatumShiftGridFile<?,?>> handler = DatumShiftGridFile.CACHE.lock(resolved); - try { - grid = handler.peek(); - if (grid == null) { - try (ReadableByteChannel in = Files.newByteChannel(resolved)) { - DatumShiftGridLoader.startLoading(provider, file); - final Loader loader = new Loader(in, file, version); - grid = loader.readAllGrids(); - loader.report(provider); - } catch (IOException | NoninvertibleTransformException | RuntimeException e) { - throw DatumShiftGridLoader.canNotLoad(provider.getSimpleName(), file, e); - } - grid = grid.useSharedData(); - } - } finally { - handler.putAndUnlock(grid); + final URI resolved = Loader.toAbsolutePath(file); + return DatumShiftGridFile.getOrLoad(resolved, null, () -> { + final DatumShiftGridFile<?,?> grid; + try (ReadableByteChannel in = Loader.newByteChannel(resolved)) { + DatumShiftGridLoader.startLoading(provider, file); + final Loader loader = new Loader(in, file, version); + grid = loader.readAllGrids(); + loader.report(provider); + } catch (IOException | NoninvertibleTransformException | RuntimeException e) { + throw DatumShiftGridLoader.canNotLoad(provider.getSimpleName(), file, e); } - } - return grid.castTo(Angle.class, Angle.class); + return grid.useSharedData(); + }).castTo(Angle.class, Angle.class); } @@ -321,7 +309,7 @@ public final class NTv2 extends AbstractProvider { * @param version the expected version (1 or 2). * @throws FactoryException if a data record cannot be parsed. */ - Loader(final ReadableByteChannel channel, final Path file, int version) throws IOException, FactoryException { + Loader(final ReadableByteChannel channel, final URI file, int version) throws IOException, FactoryException { super(channel, ByteBuffer.allocate(4096), file); header = new LinkedHashMap<>(); ensureBufferContains(RECORD_LENGTH); diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/DatumShiftTestCase.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/DatumShiftTestCase.java index 4139f4b352..13cb9d581d 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/DatumShiftTestCase.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/DatumShiftTestCase.java @@ -16,11 +16,9 @@ */ package org.apache.sis.internal.referencing.provider; +import java.net.URI; import java.net.URL; import java.net.URISyntaxException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.FileSystemNotFoundException; import org.apache.sis.test.TestCase; import static org.junit.Assert.*; @@ -30,10 +28,10 @@ import static org.junit.Assume.assumeFalse; /** * Base class of tests that need to load a datum shift grid. This base class provides a * {@link #getResourceAsConvertibleURL(String)} method for fetching the data in a form - * convertible to {@link Path}. + * convertible to {@link URI}. * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.4 * @since 0.8 */ public abstract class DatumShiftTestCase extends TestCase { @@ -44,7 +42,7 @@ public abstract class DatumShiftTestCase extends TestCase { } /** - * Finds resource of the given name as a URL convertible to a {@link Path}. + * Finds resource of the given name as a URL convertible to a {@link URI}. * If the URL is not convertible, then this method declares the test as ignored. * * @param name name of the resource to get. @@ -61,22 +59,17 @@ public abstract class DatumShiftTestCase extends TestCase { } /** - * Finds resource of the given name as a path. If the resource cannot be obtained because + * Finds resource of the given name as an URI. If the resource cannot be obtained because * the grid file is inside a JAR file, declares the test as ignored instead of failed. * * @param name name of the resource to get. * @return the requested resources. */ - static Path getResource(final String name) throws URISyntaxException { - final URL file = DatumShiftTestCase.class.getResource(name); + static URI getResource(final String name) throws URISyntaxException { + final URL file = getResourceAsConvertibleURL(name); if (file == null) { - fail("Test file \"" + name + "\" not found."); - } else try { - return Paths.get(file.toURI()); - } catch (FileSystemNotFoundException e) { assumeFalse("Cannot read grid data in a JAR file.", "jar".equals(file.getProtocol())); - throw e; } - return null; + return file.toURI(); } } diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java index 41917c679b..2dc409a8d5 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java @@ -19,9 +19,7 @@ package org.apache.sis.internal.referencing.provider; import java.net.URISyntaxException; import java.io.BufferedReader; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.Path; +import java.net.URI; import javax.measure.quantity.Angle; import javax.measure.quantity.Length; import org.opengis.geometry.Envelope; @@ -38,7 +36,7 @@ import static org.opengis.test.Assert.*; * Tests {@link FranceGeocentricInterpolation}. * * @author Martin Desruisseaux (Geomatys) - * @version 0.7 + * @version 1.4 * * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint() * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint() @@ -90,14 +88,16 @@ public final class FranceGeocentricInterpolationTest extends DatumShiftTestCase public static final double ANGULAR_TOLERANCE = (0.0001 / 60 / 60) / 2; /** - * Tests {@link FranceGeocentricInterpolation#isRecognized(Path)}. + * Tests {@link FranceGeocentricInterpolation#isRecognized(URI)}. + * + * @throws URISyntaxException if the URL to the test file is not valid. */ @Test - public void testIsRecognized() { - assertTrue (FranceGeocentricInterpolation.isRecognized(Paths.get("GR3DF97A.txt"))); - assertTrue (FranceGeocentricInterpolation.isRecognized(Paths.get("gr3df"))); - assertFalse(FranceGeocentricInterpolation.isRecognized(Paths.get("gr3d"))); - assertTrue (FranceGeocentricInterpolation.isRecognized(Paths.get(TEST_FILE))); + public void testIsRecognized() throws URISyntaxException { + assertTrue (FranceGeocentricInterpolation.isRecognized(new URI("GR3DF97A.txt"))); + assertTrue (FranceGeocentricInterpolation.isRecognized(new URI("gr3df"))); + assertFalse(FranceGeocentricInterpolation.isRecognized(new URI("gr3d"))); + assertTrue (FranceGeocentricInterpolation.isRecognized(new URI(TEST_FILE))); } /** @@ -111,7 +111,7 @@ public final class FranceGeocentricInterpolationTest extends DatumShiftTestCase /** * Tests a small grid file with interpolations in geocentric coordinates. * - * @throws URISyntaxException if the URL to the test file cannot be converted to a path. + * @throws URISyntaxException if the URL to the test file is not valid. * @throws IOException if an error occurred while loading the grid. * @throws FactoryException if an error occurred while computing the grid. * @throws TransformException if an error occurred while computing the envelope. @@ -128,7 +128,7 @@ public final class FranceGeocentricInterpolationTest extends DatumShiftTestCase * The next method is {@link #testGridAsShorts(DatumShiftGridFile)}.</p> * * @return the loaded grid with values as {@code float}. - * @throws URISyntaxException if the URL to the test file cannot be converted to a path. + * @throws URISyntaxException if the URL to the test file is not valid. * @throws IOException if an error occurred while loading the grid. * @throws FactoryException if an error occurred while computing the grid. * @throws TransformException if an error occurred while computing the envelope. @@ -137,10 +137,10 @@ public final class FranceGeocentricInterpolationTest extends DatumShiftTestCase private static DatumShiftGridFile<Angle,Length> testGridAsFloats() throws URISyntaxException, IOException, FactoryException, TransformException { - final Path file = getResource(TEST_FILE); + final URI file = getResource(TEST_FILE); final DatumShiftGridFile.Float<Angle,Length> grid; - try (BufferedReader in = Files.newBufferedReader(file)) { - grid = FranceGeocentricInterpolation.load(in, file); + try (BufferedReader in = FranceGeocentricInterpolation.Loader.newBufferedReader(file)) { + grid = FranceGeocentricInterpolation.Loader.load(in, file); } assertEquals("cellPrecision", 0.005, grid.getCellPrecision(), STRICT); assertEquals("getCellMean", 168.2587, grid.getCellMean(0), 0.0001); @@ -219,9 +219,9 @@ public final class FranceGeocentricInterpolationTest extends DatumShiftTestCase } /** - * Tests the {@link FranceGeocentricInterpolation#getOrLoad(Path, double[], double)} method and its cache. + * Tests the {@link FranceGeocentricInterpolation#getOrLoad(URI, double[], double)} method and its cache. * - * @throws URISyntaxException if the URL to the test file cannot be converted to a path. + * @throws URISyntaxException if the URL to the test file is not valid. * @throws FactoryException if an error occurred while computing the grid. * @throws TransformException if an error occurred while computing the envelope. */ diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java index 7a03e51bc3..c159b4e9f4 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java @@ -19,6 +19,7 @@ package org.apache.sis.internal.referencing.provider; import java.util.Locale; import java.io.BufferedWriter; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Files; @@ -40,7 +41,7 @@ import static org.opengis.test.Assert.*; * * @author Martin Desruisseaux (Geomatys) * @author Simon Reynard (Geomatys) - * @version 0.8 + * @version 1.4 * @since 0.7 */ public final class NADCONTest extends DatumShiftTestCase { @@ -91,7 +92,7 @@ public final class NADCONTest extends DatumShiftTestCase { * Tests loading a grid file and interpolating a sample point. * The point used for this test is given by {@link #samplePoint(int)}. * - * @throws URISyntaxException if the URL to the test file cannot be converted to a path. + * @throws URISyntaxException if the URL to the test file is not valid. * @throws FactoryException if an error occurred while loading or computing the grid. * @throws TransformException if an error occurred while computing the envelope or testing the point. */ @@ -113,21 +114,21 @@ public final class NADCONTest extends DatumShiftTestCase { * @throws FactoryException if an error occurred while loading or computing the grid. * @throws TransformException if an error occurred while computing the envelope or testing the point. */ - public static void testNADCON(final Path latitudeShifts, final Path longitudeShifts) + public static void testNADCON(final URI latitudeShifts, final URI longitudeShifts) throws FactoryException, TransformException { testNADCON(latitudeShifts, longitudeShifts, -131, -63, 20, 50); } /** - * Implementation of {@link #testLoader()} and {@link #testNADCON(Path, Path)}. + * Implementation of {@link #testLoader()} and {@link #testNADCON(URI, URI)}. * * @param xmin westmost longitude. * @param xmax eastmost longitude. * @param ymin southmost latitude. * @param ymax northmost latitude. */ - private static void testNADCON(final Path latitudeShifts, final Path longitudeShifts, + private static void testNADCON(final URI latitudeShifts, final URI longitudeShifts, final double xmin, final double xmax, final double ymin, final double ymax) throws FactoryException, TransformException { diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java index c3e6299551..82a2b93f1a 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java @@ -16,6 +16,7 @@ */ package org.apache.sis.internal.referencing.provider; +import java.net.URI; import java.net.URISyntaxException; import java.io.IOException; import java.nio.ByteBuffer; @@ -35,7 +36,6 @@ import org.apache.sis.geometry.Envelope2D; import org.apache.sis.geometry.Envelopes; import org.apache.sis.measure.Units; import org.apache.sis.internal.referencing.Formulas; -import org.apache.sis.internal.system.DataDirectory; import org.apache.sis.test.DependsOn; import org.junit.Test; @@ -49,7 +49,7 @@ import static org.apache.sis.internal.referencing.provider.DatumShiftGridLoader. * It will also indirectly tests {@link DatumShiftGridGroup} class. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint() * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint() @@ -80,7 +80,7 @@ public final class NTv2Test extends DatumShiftTestCase { * Tests loading a grid file and interpolating a sample point. The point used for * this test is given by {@link FranceGeocentricInterpolationTest#samplePoint(int)}. * - * @throws URISyntaxException if the URL to the test file cannot be converted to a path. + * @throws URISyntaxException if the URL to the test file is not valid. * @throws IOException if an error occurred while loading the grid. * @throws FactoryException if an error occurred while computing the grid. * @throws TransformException if an error occurred while computing the envelope or testing the point. @@ -104,19 +104,19 @@ public final class NTv2Test extends DatumShiftTestCase { * @throws FactoryException if an error occurred while loading or computing the grid. * @throws TransformException if an error occurred while computing the envelope or testing the point. */ - public static void testRGF93(final Path file) throws FactoryException, TransformException { + public static void testRGF93(final URI file) throws FactoryException, TransformException { testRGF93(file, -19800, 36000, 147600, 187200); } /** - * Implementation of {@link #testLoader()} and {@link #testRGF93(Path)}. + * Implementation of {@link #testLoader()} and {@link #testRGF93(URI)}. * * @param xmin negative of value of {@code "W_LONG"} record. * @param xmax negative of value of {@code "E_LONG"} record. * @param ymin value of the {@code "S_LAT"} record. * @param ymax value of the {@code "N_LAT"} record. */ - private static void testRGF93(final Path file, final double xmin, final double xmax, + private static void testRGF93(final URI file, final double xmin, final double xmax, final double ymin, final double ymax) throws FactoryException, TransformException { final double cellSize = 360; @@ -177,14 +177,15 @@ public final class NTv2Test extends DatumShiftTestCase { * to be present in the {@code $SIS_DATA/DatumChanges} directory. This test is executed only if the * {@link #RUN_EXTENSIVE_TESTS} flag is set. * + * @throws URISyntaxException if the URL to the test file is not valid. * @throws FactoryException if an error occurred while loading or computing the grid. * @throws TransformException if an error occurred while computing the envelope or testing the point. */ @Test - public void testMultiGrids() throws FactoryException, TransformException { + public void testMultiGrids() throws URISyntaxException, FactoryException, TransformException { assumeTrue(RUN_EXTENSIVE_TESTS); - final Path file = DataDirectory.DATUM_CHANGES.resolve(Paths.get(MULTIGRID_TEST_FILE)); - assumeTrue(Files.exists(file)); + final URI file = DatumShiftGridLoader.toAbsolutePath(new URI(MULTIGRID_TEST_FILE)); + assumeTrue(Files.exists(Paths.get(file))); final DatumShiftGridFile<Angle,Angle> grid = NTv2.getOrLoad(NTv2.class, file, 2); assertInstanceOf("Should contain many grids.", DatumShiftGridGroup.class, grid); assertEquals("coordinateUnit", Units.ARC_SECOND, grid.getCoordinateUnit()); diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/DatumShiftTest.java b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/DatumShiftTest.java index 304ff3dcdb..38b116d3e8 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/DatumShiftTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/DatumShiftTest.java @@ -16,7 +16,7 @@ */ package org.apache.sis.test.integration; -import java.nio.file.Path; +import java.net.URI; import java.io.IOException; import org.opengis.util.FactoryException; import org.opengis.referencing.operation.TransformException; @@ -37,7 +37,7 @@ import static org.apache.sis.test.Assume.*; * directory. * * @author Martin Desruisseaux (Geomatys) - * @version 0.7 + * @version 1.4 * @since 0.7 */ @DependsOn({ @@ -56,7 +56,7 @@ public final class DatumShiftTest extends TestCase { */ @Test public void testRGF93() throws IOException, FactoryException, TransformException { - final Path file = assumeDataExists(DataDirectory.DATUM_CHANGES, "ntf_r93.gsb"); + final URI file = assumeDataExists(DataDirectory.DATUM_CHANGES, "ntf_r93.gsb"); NTv2Test.testRGF93(file); } @@ -70,8 +70,8 @@ public final class DatumShiftTest extends TestCase { */ @Test public void testNADCON() throws IOException, FactoryException, TransformException { - final Path latitudeShifts = assumeDataExists(DataDirectory.DATUM_CHANGES, "conus.las"); - final Path longitudeShifts = assumeDataExists(DataDirectory.DATUM_CHANGES, "conus.los"); + final URI latitudeShifts = assumeDataExists(DataDirectory.DATUM_CHANGES, "conus.las"); + final URI longitudeShifts = assumeDataExists(DataDirectory.DATUM_CHANGES, "conus.los"); NADCONTest.testNADCON(latitudeShifts, longitudeShifts); } } diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/system/DataDirectory.java b/core/sis-utility/src/main/java/org/apache/sis/internal/system/DataDirectory.java index 32746aa757..641bd5b7ac 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/internal/system/DataDirectory.java +++ b/core/sis-utility/src/main/java/org/apache/sis/internal/system/DataDirectory.java @@ -33,7 +33,7 @@ import static java.util.logging.Logger.getLogger; * Sub-directories of {@code SIS_DATA} where SIS looks for EPSG database, datum shift grids and other resources. * * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.4 * @since 0.7 */ public enum DataDirectory { @@ -228,25 +228,4 @@ public enum DataDirectory { } return directory; } - - /** - * If the given path is relative, returns the path as a child of the directory represented by this enum. - * If no valid directory is configured by the {@code SIS_DATA} environment variable, then the relative - * path is returned as-is. - * - * <p>This method is invoked for files that may be user-specified, for example datum shift file specified - * in {@link org.opengis.parameter.ParameterValue}.</p> - * - * @param file the path to resolve, or {@code null}. - * @return the path to use, or {@code null} if the given path was null. - */ - public Path resolve(Path file) { - if (file != null && !file.isAbsolute()) { - final Path dir = getDirectory(); - if (dir != null) { - return dir.resolve(file); - } - } - return file; - } } diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/Assume.java b/core/sis-utility/src/test/java/org/apache/sis/test/Assume.java index 6b3db4f30c..efe64d3fcd 100644 --- a/core/sis-utility/src/test/java/org/apache/sis/test/Assume.java +++ b/core/sis-utility/src/test/java/org/apache/sis/test/Assume.java @@ -16,6 +16,7 @@ */ package org.apache.sis.test; +import java.net.URI; import java.nio.file.Path; import java.nio.file.Files; import org.apache.sis.internal.system.DataDirectory; @@ -25,7 +26,7 @@ import org.apache.sis.internal.system.DataDirectory; * Assumption methods used by the SIS project in addition of the JUnit ones. * * @author Martin Desruisseaux (Geomatys) - * @version 0.7 + * @version 1.4 * @since 0.7 */ public final class Assume extends org.junit.Assume { @@ -48,13 +49,13 @@ public final class Assume extends org.junit.Assume { * @param file the file that needs to exist. * @return the path to the given file. */ - public static Path assumeDataExists(final DataDirectory type, final String file) { + public static URI assumeDataExists(final DataDirectory type, final String file) { assumeNotNull("$SIS_DATA environment variable not set.", System.getenv(DataDirectory.ENV)); Path path = type.getDirectory(); assumeNotNull("$SIS_DATA/" + type + " directory not found.", path); path = path.resolve(file); assumeTrue("Specified file or directory not found.", Files.exists(path)); assumeTrue("Specified directory not readable.", Files.isReadable(path)); - return path; + return path.toUri(); } } diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/package-info.java b/core/sis-utility/src/test/java/org/apache/sis/test/package-info.java index 537edf9e95..8bf3aab850 100644 --- a/core/sis-utility/src/test/java/org/apache/sis/test/package-info.java +++ b/core/sis-utility/src/test/java/org/apache/sis/test/package-info.java @@ -41,7 +41,7 @@ * </ul> * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * @since 0.3 */ package org.apache.sis.test;