blob: f701d510dd367b6e4c4d80a2c08168f484399a37 [file] [log] [blame]
/*
* 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.logging.Level;
import java.util.logging.LogRecord;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.NoSuchFileException;
import java.nio.channels.ReadableByteChannel;
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.referencing.Resources;
import org.apache.sis.referencing.factory.FactoryDataException;
import org.apache.sis.referencing.factory.MissingFactoryResourceException;
/**
* Base class of datum shift grid loaders.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.7
* @since 0.7
* @module
*/
class DatumShiftGridLoader {
/**
* Conversion factor from degrees to seconds.
*/
static final double DEGREES_TO_SECONDS = 3600;
/**
* Possible precision for offset values in seconds of angle. This value is used only as a hint
* when attempting to compress the grid in arrays of {@code short} values. It does not hurt if
* this value is wrong, as it will only cause the grid to not be compressed.
*
* <p>Some interesting values:</p>
* <ul>
* <li>1E-4 is about 3 millimetres on Earth.</li>
* <li>1E-6 matches the precision found in ASCII outputs of NADCON grids.</li>
* <li>1E-7 is about 1 ULP of 1 second of angle.</li>
* </ul>
*
* We use a value of 1E-4 because more accurate values tend to cause overflows in the compression algorithm,
* in which case the compression fails. With a more reasonable value, we have better chances of success.
*/
static final double SECOND_PRECISION = 1E-4;
/**
* The file to load, used for parameter declaration and if we have errors to report.
*/
final Path file;
/**
* The channel opened on the file.
*/
private final ReadableByteChannel channel;
/**
* The buffer to use for transferring data from the channel.
*/
final ByteBuffer buffer;
/**
* Creates a new loader for the given channel and an existing buffer.
*
* @param channel where to read data from.
* @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 {
this.file = file;
this.buffer = buffer;
this.channel = channel;
channel.read(buffer);
buffer.flip();
}
/**
* Makes sure that the buffer contains at least <var>n</var> remaining bytes.
* It is caller's responsibility to ensure that the given number of bytes is
* not greater than the {@linkplain ByteBuffer#capacity() buffer capacity}.
*
* @param n the minimal number of bytes needed in the {@linkplain #buffer}.
* @throws EOFException if the channel has reached the end of stream.
* @throws IOException if an other kind of error occurred while reading.
*/
final void ensureBufferContains(int n) throws IOException {
assert n >= 0 && n <= buffer.capacity() : n;
n -= buffer.remaining();
if (n > 0) {
buffer.compact();
do {
final int c = channel.read(buffer);
if (c <= 0) {
if (c != 0) {
throw new EOFException(Errors.format(Errors.Keys.UnexpectedEndOfFile_1, file));
} else {
throw new IOException(Errors.format(Errors.Keys.CanNotRead_1, file));
}
}
n -= c;
} while (n > 0);
buffer.flip();
}
}
/**
* Skips exactly <var>n</var> bytes.
*/
final void skip(int n) throws IOException {
int p;
while ((p = buffer.position() + n) > buffer.limit()) {
n -= buffer.remaining();
buffer.clear();
ensureBufferContains(Math.min(n, buffer.capacity()));
}
buffer.position(p);
}
/**
* 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}.
*/
static void log(final Class<?> caller, final Object file) {
final LogRecord record = Resources.forLocale(null).getLogRecord(Level.FINE, Resources.Keys.LoadingDatumShiftFile_1, file);
record.setLoggerName(Loggers.COORDINATE_OPERATION);
Logging.log(caller, "createMathTransform", record);
}
/**
* Creates the exception to thrown when the provider failed to load the grid file.
*
* @param format the format name (e.g. "NTv2" or "NADCON").
* @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) {
final String message = Errors.format(Errors.Keys.CanNotParseFile_2, format, file);
if (cause instanceof NoSuchFileException) {
return new MissingFactoryResourceException(message, cause);
} else {
return new FactoryDataException(message, cause);
}
}
}