Author: aaime Date: 2012-03-17 12:58:42 -0700 (Sat, 17 Mar 2012) New Revision: 38634 Added: trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/ trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/ClasspathGridShiftLocator.java trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/GridShiftLocator.java trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NADCONGridShiftFactory.java trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NADConGridShift.java trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NTv2GridShiftFactory.java trunk/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/NTv2Transform.java trunk/modules/library/referencing/src/main/resources/META-INF/services/org.geotools.referencing.factory.gridshift.GridShiftLocator trunk/modules/library/referencing/src/test/java/org/geotools/referencing/factory/gridshift/ trunk/modules/library/referencing/src/test/java/org/geotools/referencing/factory/gridshift/NTv2GridShiftFactoryTest.java trunk/modules/library/referencing/src/test/java/org/geotools/referencing/factory/gridshift/TestGridShiftLocator.java trunk/modules/library/referencing/src/test/java/org/geotools/referencing/operation/transform/NADCONTransformTest.java trunk/modules/library/referencing/src/test/java/org/geotools/referencing/operation/transform/NTv2TransformTest.java trunk/modules/library/referencing/src/test/resources/org/geotools/referencing/factory/gridshift/ trunk/modules/library/referencing/src/test/resources/org/geotools/referencing/factory/gridshift/BALR2009.gsb trunk/modules/library/referencing/src/test/resources/org/geotools/referencing/factory/gridshift/BALR2009_copyright_and_disclaimer.txt trunk/modules/library/referencing/src/test/resources/org/geotools/referencing/factory/gridshift/malformedNTv2grid.gsb trunk/modules/library/referencing/src/test/resources/org/geotools/referencing/factory/gridshift/stpaul.las trunk/modules/library/referencing/src/test/resources/org/geotools/referencing/factory/gridshift/stpaul.los trunk/modules/plugin/epsg-hsql/src/test/resources/ trunk/modules/plugin/epsg-hsql/src/test/resources/org/ trunk/modules/plugin/epsg-hsql/src/test/resources/org/geotools/ trunk/modules/plugin/epsg-hsql/src/test/resources/org/geotools/referencing/ trunk/modules/plugin/epsg-hsql/src/test/resources/org/geotools/referencing/factory/ trunk/modules/plugin/epsg-hsql/src/test/resources/org/geotools/referencing/factory/gridshift/ trunk/modules/plugin/epsg-hsql/src/test/resources/org/geotools/referencing/factory/gridshift/stgeorge.las trunk/modules/plugin/epsg-hsql/src/test/resources/org/geotools/referencing/factory/gridshift/stgeorge.los Modified: trunk/modules/library/referencing/pom.xml trunk/modules/library/referencing/src/main/java/org/geotools/referencing/ReferencingFactoryFinder.java trunk/modules/library/referencing/src/main/java/org/geotools/referencing/operation/MathTransformProvider.java trunk/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/NADCONTransform.java trunk/modules/library/referencing/src/main/resources/META-INF/services/org.geotools.referencing.operation.MathTransformProvider trunk/modules/plugin/epsg-hsql/src/test/java/org/geotools/referencing/CRSTest.java Log: [GEOT-4063] NTv2 grid shift transformation (EPSG:9615), patch by Oscar Fonts with improvements for grid lookups and NADCON support by yours truly Modified: trunk/modules/library/referencing/pom.xml =================================================================== --- trunk/modules/library/referencing/pom.xml 2012-03-17 14:24:27 UTC (rev 38633) +++ trunk/modules/library/referencing/pom.xml 2012-03-17 19:58:42 UTC (rev 38634) @@ -189,6 +189,11 @@ <artifactId>gt-metadata</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>jgridshift</groupId> + <artifactId>jgridshift</artifactId> + <version>1.0</version> + </dependency> <!-- Test dependencies --> <dependency> Modified: trunk/modules/library/referencing/src/main/java/org/geotools/referencing/ReferencingFactoryFinder.java =================================================================== --- trunk/modules/library/referencing/src/main/java/org/geotools/referencing/ReferencingFactoryFinder.java 2012-03-17 14:24:27 UTC (rev 38633) +++ trunk/modules/library/referencing/src/main/java/org/geotools/referencing/ReferencingFactoryFinder.java 2012-03-17 19:58:42 UTC (rev 38634) @@ -18,37 +18,38 @@ import java.io.IOException; import java.io.Writer; -import java.util.Set; -import java.util.Locale; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Set; + import javax.imageio.spi.ServiceRegistry; +import org.geotools.factory.FactoryCreator; +import org.geotools.factory.FactoryFinder; +import org.geotools.factory.FactoryRegistry; +import org.geotools.factory.FactoryRegistryException; +import org.geotools.factory.GeoTools; +import org.geotools.factory.Hints; +import org.geotools.metadata.iso.citation.Citations; +import org.geotools.referencing.factory.gridshift.GridShiftLocator; +import org.geotools.resources.Arguments; +import org.geotools.resources.LazySet; import org.opengis.metadata.Identifier; import org.opengis.metadata.citation.Citation; +import org.opengis.referencing.AuthorityFactory; import org.opengis.referencing.Factory; -import org.opengis.referencing.AuthorityFactory; +import org.opengis.referencing.crs.CRSAuthorityFactory; import org.opengis.referencing.crs.CRSFactory; -import org.opengis.referencing.crs.CRSAuthorityFactory; +import org.opengis.referencing.cs.CSAuthorityFactory; import org.opengis.referencing.cs.CSFactory; -import org.opengis.referencing.cs.CSAuthorityFactory; +import org.opengis.referencing.datum.DatumAuthorityFactory; import org.opengis.referencing.datum.DatumFactory; -import org.opengis.referencing.datum.DatumAuthorityFactory; +import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory; import org.opengis.referencing.operation.CoordinateOperationFactory; -import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory; import org.opengis.referencing.operation.MathTransformFactory; -import org.geotools.factory.Hints; -import org.geotools.factory.GeoTools; -import org.geotools.factory.FactoryFinder; -import org.geotools.factory.FactoryCreator; -import org.geotools.factory.FactoryRegistry; -import org.geotools.factory.FactoryRegistryException; -import org.geotools.metadata.iso.citation.Citations; -import org.geotools.resources.Arguments; -import org.geotools.resources.LazySet; - /** * Defines static methods used to access the application's default {@linkplain Factory * factory} implementation. @@ -120,7 +121,8 @@ CRSAuthorityFactory.class, MathTransformFactory.class, CoordinateOperationFactory.class, - CoordinateOperationAuthorityFactory.class}); + CoordinateOperationAuthorityFactory.class, + GridShiftLocator.class}); } return registry; } @@ -486,8 +488,21 @@ { return getFactories(CoordinateOperationAuthorityFactory.class, hints); } - + /** + * Returns a set of all available implementations for the + * {@link GridShiftLocator} interface. + * + * @param hints An optional map of hints, or {@code null} if none. + * @return Set of available grid shift factory implementations. + */ + public static Set<GridShiftLocator> getGridShiftLocators( + final Hints hints) + { + return getFactories(GridShiftLocator.class, hints); + } + + /** * Returns the first implementation of {@link MathTransformFactory} matching the specified * hints. If no implementation matches, a new one is created if possible or an exception is * thrown otherwise. If more than one implementation is registered and an Added: trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/ClasspathGridShiftLocator.java =================================================================== --- trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/ClasspathGridShiftLocator.java (rev 0) +++ trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/ClasspathGridShiftLocator.java 2012-03-17 19:58:42 UTC (rev 38634) @@ -0,0 +1,47 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2012, Open Source Geospatial Foundation (OSGeo) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package org.geotools.referencing.factory.gridshift; + +import java.net.URL; + +import org.geotools.factory.AbstractFactory; +import org.geotools.metadata.iso.citation.Citations; +import org.opengis.metadata.citation.Citation; + +/** + * Default grid shift file locator, looks up grids in the classpath + * + * @author Andrea Aime - GeoSolutions + * + */ +public class ClasspathGridShiftLocator extends AbstractFactory implements GridShiftLocator { + + public ClasspathGridShiftLocator() { + super(NORMAL_PRIORITY); + } + + @Override + public Citation getVendor() { + return Citations.GEOTOOLS; + } + + @Override + public URL locateGrid(String grid) { + return getClass().getResource(grid); + } + +} Added: trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/GridShiftLocator.java =================================================================== --- trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/GridShiftLocator.java (rev 0) +++ trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/GridShiftLocator.java 2012-03-17 19:58:42 UTC (rev 38634) @@ -0,0 +1,38 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2012, Open Source Geospatial Foundation (OSGeo) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package org.geotools.referencing.factory.gridshift; + +import java.net.URL; + +import org.opengis.referencing.Factory; + + +/** + * Provides a hook to locate grid shift files, such as NTv1, NTv2 and NADCON ones + * + * Andrea Aime - Geosolutions + */ +public interface GridShiftLocator extends Factory { + + /** + * Locate the specified resource. + * + * @param grid the grid name/location + * @return the fully resolved URL of the grid or null, if the resource cannot be located. + */ + URL locateGrid(String grid); +} Added: trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NADCONGridShiftFactory.java =================================================================== --- trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NADCONGridShiftFactory.java (rev 0) +++ trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NADCONGridShiftFactory.java 2012-03-17 19:58:42 UTC (rev 38634) @@ -0,0 +1,440 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2012, Open Source Geospatial Foundation (OSGeo) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package org.geotools.referencing.factory.gridshift; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.StringTokenizer; +import java.util.logging.Logger; + +import org.geotools.factory.BufferedFactory; +import org.geotools.referencing.factory.ReferencingFactory; +import org.geotools.resources.i18n.ErrorKeys; +import org.geotools.resources.i18n.Errors; +import org.geotools.util.SoftValueHashMap; +import org.geotools.util.logging.Logging; +import org.opengis.referencing.FactoryException; + +/** + * Loads and caches NADCON grid shifts + * + * @author Andrea Aime - GeoSolutions + * + */ +public class NADCONGridShiftFactory extends ReferencingFactory implements BufferedFactory { + + static final class NADCONKey { + String latFile; + + String longFile; + + public NADCONKey(String latFile, String longFile) { + super(); + this.latFile = latFile; + this.longFile = longFile; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((latFile == null) ? 0 : latFile.hashCode()); + result = prime * result + ((longFile == null) ? 0 : longFile.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NADCONKey other = (NADCONKey) obj; + if (latFile == null) { + if (other.latFile != null) + return false; + } else if (!latFile.equals(other.latFile)) + return false; + if (longFile == null) { + if (other.longFile != null) + return false; + } else if (!longFile.equals(other.longFile)) + return false; + return true; + } + + } + + /** + * The number of hard references to hold internally. + */ + private static final int GRID_CACHE_HARD_REFERENCES = 10; + + /** + * Logger. + */ + protected static final Logger LOGGER = Logging.getLogger("org.geotools.referencing"); + + /** + * The soft cache that holds loaded grids. + */ + private SoftValueHashMap<NADCONKey, NADConGridShift> gridCache; + + /** + * Constructs a factory with the default priority. + */ + public NADCONGridShiftFactory() { + gridCache = new SoftValueHashMap<NADCONKey, NADConGridShift>(GRID_CACHE_HARD_REFERENCES); + } + + public NADConGridShift loadGridShift(URL latGridURL, URL longGridURL) throws FactoryException { + NADCONKey key = new NADCONKey(latGridURL.toExternalForm(), longGridURL.toExternalForm()); + synchronized (gridCache) { // Prevent simultaneous threads trying to load same grid + NADConGridShift grid = gridCache.get(key); + if (grid != null) { // Cached: + return grid; // - Return + } else { // Not cached: + grid = loadGridShiftInternal(latGridURL, longGridURL); // - Load + if (grid != null) { + gridCache.put(key, grid); // - Cache + return grid; // - Return + } + } + throw new FactoryException("NTv2 Grid " + latGridURL + ", " + longGridURL + + " could not be created."); + } + } + + private NADConGridShift loadGridShiftInternal(URL latGridURL, URL longGridURL) + throws FactoryException { + // decide if text or binary grid will be used + String latGridName = latGridURL.getFile(); + String longGridName = longGridURL.getFile(); + try { + if ((latGridName.endsWith(".las") && longGridName.endsWith(".los")) + || (latGridName.endsWith(".LAS") && longGridName.endsWith(".LOS"))) { + return loadBinaryGrid(latGridURL, longGridURL); + } else if ((latGridName.endsWith(".laa") && longGridName.endsWith(".loa")) + || (latGridName.endsWith(".LAA") && longGridName.endsWith(".LOA"))) { + return loadTextGrid(latGridURL, longGridURL); + } else { + throw new FactoryException(Errors.format(ErrorKeys.UNSUPPORTED_FILE_TYPE_$2, + latGridName.substring(latGridName.lastIndexOf('.') + 1), + longGridName.substring(longGridName.lastIndexOf('.') + 1))); + // Note: the +1 above hide the dot, but also make sure that the code is + // valid even if the path do not contains '.' at all (-1 + 1 == 0). + } + } catch (IOException exception) { + final Throwable cause = exception.getCause(); + if (cause instanceof FactoryException) { + throw (FactoryException) cause; + } + throw new FactoryException(exception.getLocalizedMessage(), exception); + } + } + + /** + * Reads latitude and longitude binary grid shift file data into {@link grid}. The file is + * organized into records, with the first record containing the header information, followed by + * the shift data. The header values are: text describing grid (64 bytes), num. columns (int), + * num. rows (int), num. z (int), min x (float), delta x (float), min y (float), delta y (float) + * and angle (float). Each record is num. columns 4 bytes + 4 byte separator long and the file + * contains num. rows + 1 (for the header) records. The data records (with the grid shift + * values) are all floats and have a 4 byte separator (0's) before the data. Row records are + * organized from low y (latitude) to high and columns are orderd from low longitude to high. + * Everything is written in low byte order. + * + * @param latGridUrl URL to the binary latitude shift file (.las extention). + * @param longGridUrl URL to the binary longitude shift file (.los extention). + * @throws IOException if the data files cannot be read. + * @throws FactoryException if there is an inconsistency in the data + */ + private NADConGridShift loadBinaryGrid(final URL latGridUrl, final URL longGridUrl) + throws IOException, FactoryException { + final int HEADER_BYTES = 96; + final int SEPARATOR_BYTES = 4; + final int DESCRIPTION_LENGTH = 64; + ReadableByteChannel latChannel; + ReadableByteChannel longChannel; + ByteBuffer latBuffer; + ByteBuffer longBuffer; + + // ////////////////////// + // setup + // ////////////////////// + latChannel = getReadChannel(latGridUrl); + latBuffer = fillBuffer(latChannel, HEADER_BYTES); + + longChannel = getReadChannel(longGridUrl); + longBuffer = fillBuffer(longChannel, HEADER_BYTES); + + // ////////////////////// + // read header info + // ////////////////////// + // skip the header description + latBuffer.position(latBuffer.position() + DESCRIPTION_LENGTH); + + int nc = latBuffer.getInt(); + int nr = latBuffer.getInt(); + int nz = latBuffer.getInt(); + + float xmin = latBuffer.getFloat(); + float dx = latBuffer.getFloat(); + float ymin = latBuffer.getFloat(); + float dy = latBuffer.getFloat(); + + float angle = latBuffer.getFloat(); + float xmax = xmin + ((nc - 1) * dx); + float ymax = ymin + ((nr - 1) * dy); + + // skip the longitude header description + longBuffer.position(longBuffer.position() + DESCRIPTION_LENGTH); + + // check that latitude grid header is the same as for latitude grid + if ((nc != longBuffer.getInt()) || (nr != longBuffer.getInt()) + || (nz != longBuffer.getInt()) || (xmin != longBuffer.getFloat()) + || (dx != longBuffer.getFloat()) || (ymin != longBuffer.getFloat()) + || (dy != longBuffer.getFloat()) || (angle != longBuffer.getFloat())) { + throw new FactoryException(Errors.format(ErrorKeys.GRID_LOCATIONS_UNEQUAL)); + } + + // ////////////////////// + // read grid shift data into LocalizationGrid + // ////////////////////// + final int RECORD_LENGTH = (nc * 4) + SEPARATOR_BYTES; + final int NUM_BYTES_LEFT = ((nr + 1) * RECORD_LENGTH) - HEADER_BYTES; + final int START_OF_DATA = RECORD_LENGTH - HEADER_BYTES; + + latBuffer = fillBuffer(latChannel, NUM_BYTES_LEFT); + latBuffer.position(START_OF_DATA); // start of second record (data) + + longBuffer = fillBuffer(longChannel, NUM_BYTES_LEFT); + longBuffer.position(START_OF_DATA); + + NADConGridShift gridShift = new NADConGridShift(xmin, ymin, xmax, ymax, dx, dy, nc, nr); + + int i = 0; + int j = 0; + for (i = 0; i < nr; i++) { + latBuffer.position(latBuffer.position() + SEPARATOR_BYTES); // skip record separator + longBuffer.position(longBuffer.position() + SEPARATOR_BYTES); + + for (j = 0; j < nc; j++) { + gridShift.setLocalizationPoint(j, i, longBuffer.getFloat(), latBuffer.getFloat()); + } + } + + assert i == nr : i; + assert j == nc : j; + + return gridShift; + } + + /** + * Returns a new bytebuffer, of numBytes length and little endian byte order, filled from the + * channel. + * + * @param channel the channel to fill the buffer from + * @param numBytes number of bytes to read + * @return a new bytebuffer filled from the channel + * @throws IOException if there is a problem reading the channel + * @throws EOFException if the end of the channel is reached + */ + private ByteBuffer fillBuffer(ReadableByteChannel channel, int numBytes) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(numBytes); + + if (fill(buf, channel) == -1) { + throw new EOFException(Errors.format(ErrorKeys.END_OF_DATA_FILE)); + } + + buf.flip(); + buf.order(ByteOrder.LITTLE_ENDIAN); + + return buf; + } + + /** + * Fills the bytebuffer from the channel. Code was lifted from ShapefileDataStore. + * + * @param buffer bytebuffer to fill from the channel + * @param channel channel to fill the buffer from + * @return number of bytes read + * @throws IOException if there is a problem reading the channel + */ + private int fill(ByteBuffer buffer, ReadableByteChannel channel) throws IOException { + int r = buffer.remaining(); + + // channel reads return -1 when EOF or other error + // because they a non-blocking reads, 0 is a valid return value!! + while ((buffer.remaining() > 0) && (r != -1)) { + r = channel.read(buffer); + } + + if (r == -1) { + buffer.limit(buffer.position()); + } + + return r; + } + + /** + * Obtain a ReadableByteChannel from the given URL. If the url protocol is file, a FileChannel + * will be returned. Otherwise a generic channel will be obtained from the urls input stream. + * Code swiped from ShapefileDataStore. + * + * @param url URL to create the channel from + * @return a new PeadableByteChannel from the input url + * @throws IOException if there is a problem creating the channel + */ + private ReadableByteChannel getReadChannel(URL url) throws IOException { + ReadableByteChannel channel = null; + + if (url.getProtocol().equals("file")) { + File file = new File(url.getFile()); + + if (!file.exists() || !file.canRead()) { + throw new IOException(Errors.format(ErrorKeys.FILE_DOES_NOT_EXIST_$1, file)); + } + + FileInputStream in = new FileInputStream(file); + channel = in.getChannel(); + } else { + InputStream in = url.openConnection().getInputStream(); + channel = Channels.newChannel(in); + } + + return channel; + } + + /** + * Reads latitude and longitude text grid shift file data into {@link grid}. The first two lines + * of the shift data file contain the header, with the first being a description of the grid. + * The second line contains 8 values separated by spaces: num. columns, num. rows, num. z, min + * x, delta x, min y, delta y and angle. Shift data values follow this and are also separated by + * spaces. Row records are organized from low y (latitude) to high and columns are orderd from + * low longitude to high. + * + * @param latGridUrl URL to the text latitude shift file (.laa extention). + * @param longGridUrl URL to the text longitude shift file (.loa extention). + * @throws IOException if the data files cannot be read. + * @throws FactoryException if there is an inconsistency in the data + */ + private NADConGridShift loadTextGrid(URL latGridUrl, URL longGridUrl) throws IOException, + FactoryException { + String latLine; + String longLine; + StringTokenizer latSt; + StringTokenizer longSt; + + // ////////////////////// + // setup + // ////////////////////// + InputStreamReader latIsr = new InputStreamReader(latGridUrl.openStream()); + BufferedReader latBr = new BufferedReader(latIsr); + + InputStreamReader longIsr = new InputStreamReader(longGridUrl.openStream()); + BufferedReader longBr = new BufferedReader(longIsr); + + // ////////////////////// + // read header info + // ////////////////////// + latLine = latBr.readLine(); // skip header description + latLine = latBr.readLine(); + latSt = new StringTokenizer(latLine, " "); + + if (latSt.countTokens() != 8) { + throw new FactoryException(Errors.format(ErrorKeys.HEADER_UNEXPECTED_LENGTH_$1, + String.valueOf(latSt.countTokens()))); + } + + int nc = Integer.parseInt(latSt.nextToken()); + int nr = Integer.parseInt(latSt.nextToken()); + int nz = Integer.parseInt(latSt.nextToken()); + + float xmin = Float.parseFloat(latSt.nextToken()); + float dx = Float.parseFloat(latSt.nextToken()); + float ymin = Float.parseFloat(latSt.nextToken()); + float dy = Float.parseFloat(latSt.nextToken()); + + float angle = Float.parseFloat(latSt.nextToken()); + float xmax = xmin + ((nc - 1) * dx); + float ymax = ymin + ((nr - 1) * dy); + + // now read long shift grid + longLine = longBr.readLine(); // skip header description + longLine = longBr.readLine(); + longSt = new StringTokenizer(longLine, " "); + + if (longSt.countTokens() != 8) { + throw new FactoryException(Errors.format(ErrorKeys.HEADER_UNEXPECTED_LENGTH_$1, + String.valueOf(longSt.countTokens()))); + } + + // check that latitude grid header is the same as for latitude grid + if ((nc != Integer.parseInt(longSt.nextToken())) + || (nr != Integer.parseInt(longSt.nextToken())) + || (nz != Integer.parseInt(longSt.nextToken())) + || (xmin != Float.parseFloat(longSt.nextToken())) + || (dx != Float.parseFloat(longSt.nextToken())) + || (ymin != Float.parseFloat(longSt.nextToken())) + || (dy != Float.parseFloat(longSt.nextToken())) + || (angle != Float.parseFloat(longSt.nextToken()))) { + throw new FactoryException(Errors.format(ErrorKeys.GRID_LOCATIONS_UNEQUAL)); + } + + // ////////////////////// + // read grid shift data into LocalizationGrid + // ////////////////////// + NADConGridShift gridShift = new NADConGridShift(xmin, ymin, xmax, ymax, dx, dy, nc, nr); + + int i = 0; + int j = 0; + for (i = 0; i < nr; i++) { + for (j = 0; j < nc;) { + latLine = latBr.readLine(); + latSt = new StringTokenizer(latLine, " "); + longLine = longBr.readLine(); + longSt = new StringTokenizer(longLine, " "); + + while (latSt.hasMoreTokens() && longSt.hasMoreTokens()) { + gridShift.setLocalizationPoint(j, i, + (double) Float.parseFloat(longSt.nextToken()), + (double) Float.parseFloat(latSt.nextToken())); + ++j; + } + } + } + + assert i == nr : i; + assert j == nc : j; + + return gridShift; + } + +} Added: trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NADConGridShift.java =================================================================== --- trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NADConGridShift.java (rev 0) +++ trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NADConGridShift.java 2012-03-17 19:58:42 UTC (rev 38634) @@ -0,0 +1,157 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2012, Open Source Geospatial Foundation (OSGeo) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package org.geotools.referencing.factory.gridshift; + +import org.geotools.referencing.operation.builder.LocalizationGrid; + +/** + * A NADCON localization grid + * + * @author Andrea Aime - GeoSolutions + * @author Rueben Schulz + */ +public class NADConGridShift extends LocalizationGrid { + + /** + * The minimum longitude value covered by this grid (decimal degrees) + */ + private double minX; + + /** + * The minimum latitude value covered by this grid (decimal degrees) + */ + private double minY; + + /** + * The maximum longitude value covered by this grid (decimal degrees) + */ + private double maxX; + + /** + * The maximum latitude value covered by this grid (decimal degrees) + */ + private double maxY; + + /** + * The difference between longitude grid points (decimal degrees) + */ + private double dx; + + /** + * The difference between latitude grid points (decimal degrees) + */ + private double dy; + + public NADConGridShift(double xmin, double ymin, double xmax, double ymax, double dx, double dy, int width, int height) { + super(width, height); + this.minX = xmin; + this.maxX = xmax; + this.minY = ymin; + this.maxY = ymax; + this.dx = dx; + this.dy = dy; + } + + /** + * Returns a hash value for this transform. To make this faster it does not + * check the grid values. + * + * @return a hash value for this transform. + */ + @Override + public final int hashCode() { + final long code = Double.doubleToLongBits(minX) + + (37 * (Double.doubleToLongBits(minY) + + (37 * (Double.doubleToLongBits(maxX) + + (37 * (Double.doubleToLongBits(maxY) + + (37 * (Double.doubleToLongBits(dx) + + (37 * (Double.doubleToLongBits(dy))))))))))); + + return (int) code ^ (int) (code >>> 32); + } + + /** + * Compares the specified object with this math transform for equality. + * + * @param object the object to compare to + * @return {@code true} if the objects are equal. + */ + @Override + public final boolean equals(final Object object) { + if (object == this) { + // Slight optimization + return true; + } + + if (super.equals(object)) { + final NADConGridShift that = (NADConGridShift) object; + + return (Double.doubleToLongBits(this.minX) == Double.doubleToLongBits(that.minX)) + && (Double.doubleToLongBits(this.minY) == Double.doubleToLongBits(that.minY)) + && (Double.doubleToLongBits(this.maxX) == Double.doubleToLongBits(that.maxX)) + && (Double.doubleToLongBits(this.maxY) == Double.doubleToLongBits(that.maxY)) + && (Double.doubleToLongBits(this.dx) == Double.doubleToLongBits(that.dx)) + && (Double.doubleToLongBits(this.dy) == Double.doubleToLongBits(that.dy)); + } + + return false; + } + + /** + * The minimum longitude value covered by this grid (decimal degrees) + */ + public double getMinX() { + return minX; + } + + /** + * The minimum latitude value covered by this grid (decimal degrees) + */ + public double getMinY() { + return minY; + } + + /** + * The maximum longitude value covered by this grid (decimal degrees) + */ + public double getMaxX() { + return maxX; + } + + /** + * The maximum latitude value covered by this grid (decimal degrees) + */ + public double getMaxY() { + return maxY; + } + + /** + * The difference between longitude grid points (decimal degrees) + */ + public double getDx() { + return dx; + } + + /** + * The difference between latitude grid points (decimal degrees) + */ + public double getDy() { + return dy; + } + + +} Added: trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NTv2GridShiftFactory.java =================================================================== --- trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NTv2GridShiftFactory.java (rev 0) +++ trunk/modules/library/referencing/src/main/java/org/geotools/referencing/factory/gridshift/NTv2GridShiftFactory.java 2012-03-17 19:58:42 UTC (rev 38634) @@ -0,0 +1,223 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2012, Open Source Geospatial Foundation (OSGeo) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package org.geotools.referencing.factory.gridshift; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.geotools.factory.AbstractFactory; +import org.geotools.factory.BufferedFactory; +import org.geotools.referencing.factory.ReferencingFactory; +import org.geotools.resources.i18n.ErrorKeys; +import org.geotools.resources.i18n.Errors; +import org.geotools.util.SoftValueHashMap; +import org.geotools.util.logging.Logging; +import org.opengis.referencing.FactoryException; + +import au.com.objectix.jgridshift.GridShiftFile; + +/** + * Loads and caches NTv2 grid files. Thisthat incorporates a soft cache mechanism to keep grids in + * memory when first loaded. It also checks NTv2 grid file format in {@link #isNTv2Grid(String)} + * method. + * + * @author Oscar Fonts + */ +public class NTv2GridShiftFactory extends ReferencingFactory implements BufferedFactory { + + /** + * The number of hard references to hold internally. + */ + private static final int GRID_CACHE_HARD_REFERENCES = 10; + + /** + * Logger. + */ + protected static final Logger LOGGER = Logging.getLogger("org.geotools.referencing"); + + /** + * The soft cache that holds loaded grids. + */ + private SoftValueHashMap<String, GridShiftFile> ntv2GridCache; + + /** + * Constructs a factory with the default priority. + */ + public NTv2GridShiftFactory() { + super(); + ntv2GridCache = new SoftValueHashMap<String, GridShiftFile>(GRID_CACHE_HARD_REFERENCES); + } + + /** + * Constructs an instance using the specified priority level. + * + * @param priority The priority for this factory, as a number between + * {@link AbstractFactory#MINIMUM_PRIORITY MINIMUM_PRIORITY} and + * {@link AbstractFactory#MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive. + */ + public NTv2GridShiftFactory(final int priority) { + super(priority); + ntv2GridCache = new SoftValueHashMap<String, GridShiftFile>(GRID_CACHE_HARD_REFERENCES); + } + + /** + * Performs a NTv2 grid file lookup given its name, and checks for file format correctness. + * + * @param name The NTv2 grid file name + * @return {@code true} if file exists and is valid, {@code false} otherwise + */ + public boolean isNTv2Grid(URL location) { + if (location != null) { + return isNTv2GridFileValid(location); // Check + } else { + return false; + } + } + + /** + * Creates a NTv2 Grid. + * + * @param name The NTv2 grid name + * @return the grid + * @throws FactoryException if grid cannot be created + */ + public GridShiftFile createNTv2Grid(URL gridLocation) throws FactoryException { + if(gridLocation == null) { + throw new FactoryException("The grid location must be not null"); + } + + synchronized (ntv2GridCache) { // Prevent simultaneous threads trying to load same grid + GridShiftFile grid = ntv2GridCache.get(gridLocation.toExternalForm()); + if (grid != null) { // Cached: + return grid; // - Return + } else { // Not cached: + if (gridLocation != null) { + grid = loadNTv2Grid(gridLocation); // - Load + if (grid != null) { + ntv2GridCache.put(gridLocation.toExternalForm(), grid); // - Cache + return grid; // - Return + } + } + throw new FactoryException("NTv2 Grid " + gridLocation + " could not be created."); + } + } + } + + + + /** + * Checks if a given resource is a valid NTv2 file without fully loading it. + * + * If file is not valid, the cause is logged at {@link Level#WARNING warning level}. + * + * @param location the NTv2 file absolute path + * @return true if file has NTv2 format, false otherwise + */ + protected boolean isNTv2GridFileValid(URL url) { + RandomAccessFile raf = null; + InputStream is = null; + try { + + // Loading as RandomAccessFile doesn't load the full grid + // in memory, but is a quick method to see if file format + // is NTv2. + if (url.getProtocol().equals("file")) { + File file = new File(url.getFile()); + + if (!file.exists() || !file.canRead()) { + throw new IOException(Errors.format(ErrorKeys.FILE_DOES_NOT_EXIST_$1, file)); + } + + raf = new RandomAccessFile(file, "r"); + + // will throw an exception if not a valid file + new GridShiftFile().loadGridShiftFile(raf); + } else { + InputStream in = url.openConnection().getInputStream(); + + // will throw an exception if not a valid file + new GridShiftFile().loadGridShiftFile(in, false); + } + + return true; // No exception thrown => valid file. + } catch (IllegalArgumentException e) { + // This usually means resource is not a valid NTv2 file. + // Let exception message describe the cause. + LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e); + return false; + } catch (IOException e) { + LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e); + return false; + } finally { + try { + if (raf != null) + raf.close(); + } catch (IOException e) { + } + + try { + if (is != null) + is.close(); + } catch (IOException e) { + } + } + } + + /** + * Loads the grid in memory. + * + * If file cannot be loaded, the cause is logged at {@link Level#SEVERE severe level}. + * + * @param location the NTv2 file absolute path + * @return the grid, or {@code null} on error + * @throws FactoryException + */ + private GridShiftFile loadNTv2Grid(URL location) throws FactoryException { + InputStream in = null; + try { + GridShiftFile grid = new GridShiftFile(); + in = location.openStream(); + grid.loadGridShiftFile(in, false); // Load full grid in memory + in.close(); + return grid; + } catch (FileNotFoundException e) { + LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); + return null; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); + return null; + } catch (IllegalArgumentException e) { + LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); + throw new FactoryException(e.getLocalizedMessage(), e); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + // never mind + } + } + } + +} Modified: trunk/modules/library/referencing/src/main/java/org/geotools/referencing/operation/MathTransformProvider.java =================================================================== --- trunk/modules/library/referencing/src/main/java/org/geotools/referencing/operation/MathTransformProvider.java 2012-03-17 14:24:27 UTC (rev 38633) +++ trunk/modules/library/referencing/src/main/java/org/geotools/referencing/operation/MathTransformProvider.java 2012-03-17 19:58:42 UTC (rev 38634) @@ -238,7 +238,7 @@ * Put the identifiers into a properties map suitable for {@link IdentifiedObject} * constructor. */ - private static Map<String,Object> toMap(final ReferenceIdentifier[] identifiers) { + protected static Map<String,Object> toMap(final ReferenceIdentifier[] identifiers) { ensureNonNull("identifiers", identifiers); if (identifiers.length == 0) { throw new IllegalArgumentException(Errors.format(ErrorKeys.EMPTY_ARRAY)); Modified: trunk/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/NADCONTransform.java =================================================================== --- trunk/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/NADCONTransform.java 2012-03-17 14:24:27 UTC (rev 38633) +++ trunk/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/NADCONTransform.java 2012-03-17 19:58:42 UTC (rev 38634) @@ -16,49 +16,42 @@ */ package org.geotools.referencing.operation.transform; -import java.io.BufferedReader; -import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.PrintWriter; import java.io.Serializable; +import java.net.URI; import java.net.URL; -import java.net.MalformedURLException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; -import java.util.StringTokenizer; import java.util.prefs.Preferences; -import org.opengis.parameter.GeneralParameterValue; -import org.opengis.parameter.ParameterDescriptor; -import org.opengis.parameter.ParameterDescriptorGroup; -import org.opengis.parameter.ParameterNotFoundException; -import org.opengis.parameter.ParameterValue; -import org.opengis.parameter.ParameterValueGroup; -import org.opengis.referencing.FactoryException; -import org.opengis.referencing.operation.MathTransform; -import org.opengis.referencing.operation.MathTransform2D; -import org.opengis.referencing.operation.Transformation; -import org.opengis.referencing.operation.TransformException; - import org.geotools.metadata.iso.citation.Citations; import org.geotools.parameter.DefaultParameterDescriptor; import org.geotools.parameter.Parameter; import org.geotools.parameter.ParameterGroup; import org.geotools.referencing.NamedIdentifier; +import org.geotools.referencing.ReferencingFactoryFinder; +import org.geotools.referencing.factory.gridshift.GridShiftLocator; +import org.geotools.referencing.factory.gridshift.NADCONGridShiftFactory; +import org.geotools.referencing.factory.gridshift.NADConGridShift; import org.geotools.referencing.operation.MathTransformProvider; import org.geotools.referencing.operation.builder.LocalizationGrid; import org.geotools.resources.Arguments; -import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; +import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; +import org.opengis.parameter.GeneralParameterValue; +import org.opengis.parameter.ParameterDescriptor; +import org.opengis.parameter.ParameterDescriptorGroup; +import org.opengis.parameter.ParameterNotFoundException; +import org.opengis.parameter.ParameterValue; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.NoSuchIdentifierException; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.MathTransform2D; +import org.opengis.referencing.operation.TransformException; +import org.opengis.referencing.operation.Transformation; /** @@ -140,6 +133,11 @@ * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -4707304160205218546L; + + /** + * The factory that loads the NADCON grids + */ + private static NADCONGridShiftFactory FACTORY = new NADCONGridShiftFactory(); /** * Preference node for the grid shift file location. @@ -170,44 +168,15 @@ /** * Latitude grid shift file names. Output in WKT. */ - private final String latGridName; + private final URI latGridName; /** * Longitude grid shift file names. Output in WKT. */ - private final String longGridName; + private final URI longGridName; + /** - * The minimum longitude value covered by this grid (decimal degrees) - */ - private double xmin; - - /** - * The minimum latitude value covered by this grid (decimal degrees) - */ - private double ymin; - - /** - * The maximum longitude value covered by this grid (decimal degrees) - */ - private double xmax; - - /** - * The maximum latitude value covered by this grid (decimal degrees) - */ - private double ymax; - - /** - * The difference between longitude grid points (decimal degrees) - */ - private double dx; - - /** - * The difference between latitude grid points (decimal degrees) - */ - private double dy; - - /** * Longitude and latitude grid shift values. Values are organized from low * to high longitude (low x index to high) and low to high latitude (low y * index to high). @@ -226,6 +195,12 @@ private transient MathTransform2D inverse; /** + * The grid driving this transform + */ + NADConGridShift grid; + + + /** * Constructs a {@code NADCONTransform} from the specified grid shift files. * * @param latGridName path and name (or just name if {@link #GRID_LOCATION} @@ -240,42 +215,40 @@ * (ie file extentions are unknown or there is an error reading the * grid files) */ - public NADCONTransform(final String latGridName, final String longGridName) + public NADCONTransform(final URI latGridName, final URI longGridName) throws ParameterNotFoundException, FactoryException { + if(latGridName == null) { + throw new NoSuchIdentifierException("Latitud grid shift file name is null", null); + } + + if(longGridName == null) { + throw new NoSuchIdentifierException("Latitud grid shift file name is null", null); + } + this.latGridName = latGridName; this.longGridName = longGridName; - - //decide if text or binary grid will be used - try { - final URL latGridURL = makeURL(latGridName); - final URL longGridURL = makeURL(longGridName); - - if ((latGridName.endsWith(".las") && longGridName.endsWith(".los")) - || (latGridName.endsWith(".LAS") && longGridName.endsWith(".LOS"))) { - loadBinaryGrid(latGridURL, longGridURL); - } else if ((latGridName.endsWith(".laa") && longGridName.endsWith(".loa")) - || (latGridName.endsWith(".LAA") && longGridName.endsWith(".LOA"))) { - loadTextGrid(latGridURL, longGridURL); - } else { - throw new FactoryException(Errors.format(ErrorKeys.UNSUPPORTED_FILE_TYPE_$2, - latGridName.substring(latGridName.lastIndexOf('.') + 1), - longGridName.substring(longGridName.lastIndexOf('.') + 1))); - // Note: the +1 above hide the dot, but also make sure that the code is - // valid even if the path do not contains '.' at all (-1 + 1 == 0). + + URL latGridURL = locateGrid(latGridName); + URL longGridURL = locateGrid(longGridName); + + this.grid = FACTORY.loadGridShift(latGridURL, longGridURL); + this.gridShiftTransform = grid.getMathTransform(); + } + + URL locateGrid(URI uri ) throws FactoryException { + String grid = uri.toString(); + for (GridShiftLocator locator : ReferencingFactoryFinder.getGridShiftLocators(null)) { + URL result = locator.locateGrid(grid); + if(result != null) { + return result; } - - gridShiftTransform = gridShift.getMathTransform(); - } catch (IOException exception) { - final Throwable cause = exception.getCause(); - if (cause instanceof FactoryException) { - throw (FactoryException) cause; - } - throw new FactoryException(exception.getLocalizedMessage(), - exception); - } + }; + + throw new FactoryException("Could not locate grid file " + grid); } + /** * Returns the parameter descriptors for this math transform. */ @@ -320,332 +293,6 @@ } /** - * Returns a URL from the string representation. If the string has no - * path, the default path preferece is added. - * - * @param str a string representation of a URL - * @return a URL created from the string representation - * @throws MalformedURLException if the URL cannot be created - */ - private URL makeURL(final String str) throws MalformedURLException { - //has '/' or '\' or ':', so probably full path to file - if ((str.indexOf('\\') >= 0) || (str.indexOf('/') >= 0) || (str.indexOf(':') >= 0)) { - return makeURLfromString(str); - } else { - // just a file name , prepend base location - final Preferences prefs = Preferences.userNodeForPackage(NADCONTransform.class); - final String baseLocation = prefs.get(GRID_LOCATION, DEFAULT_GRID_LOCATION); - return makeURLfromString(baseLocation + "/" + str); - } - } - - /** - * Returns a URL based on a string representation. If no protocol is given, - * it is assumed to be a local file. - * - * @param str a string representation of a URL - * @return a URL created from the string representation - * @throws MalformedURLException if the URL cannot be created - */ - private URL makeURLfromString(final String str) throws MalformedURLException { - try { - return new URL(str); - } catch (MalformedURLException e) { - //try making this with a file protocal - return new URL("file", "", str); - } - } - - /** - * Reads latitude and longitude binary grid shift file data into {@link - * grid}. The file is organized into records, with the first record - * containing the header information, followed by the shift data. The - * header values are: text describing grid (64 bytes), num. columns (int), - * num. rows (int), num. z (int), min x (float), delta x (float), min y - * (float), delta y (float) and angle (float). Each record is num. - * columns 4 bytes + 4 byte separator long and the file contains num. - * rows + 1 (for the header) records. The data records (with the grid - * shift values) are all floats and have a 4 byte separator (0's) before - * the data. Row records are organized from low y (latitude) to high and - * columns are orderd from low longitude to high. Everything is written - * in low byte order. - * - * @param latGridUrl URL to the binary latitude shift file (.las extention). - * @param longGridUrl URL to the binary longitude shift file (.los extention). - * @throws IOException if the data files cannot be read. - * @throws FactoryException if there is an inconsistency in the data - */ - private void loadBinaryGrid(final URL latGridUrl, final URL longGridUrl) - throws IOException, FactoryException - { - final int HEADER_BYTES = 96; - final int SEPARATOR_BYTES = 4; - final int DESCRIPTION_LENGTH = 64; - ReadableByteChannel latChannel; - ReadableByteChannel longChannel; - ByteBuffer latBuffer; - ByteBuffer longBuffer; - - //////////////////////// - //setup - //////////////////////// - latChannel = getReadChannel(latGridUrl); - latBuffer = fillBuffer(latChannel, HEADER_BYTES); - - longChannel = getReadChannel(longGridUrl); - longBuffer = fillBuffer(longChannel, HEADER_BYTES); - - //////////////////////// - //read header info - //////////////////////// - //skip the header description - latBuffer.position(latBuffer.position() + DESCRIPTION_LENGTH); - - int nc = latBuffer.getInt(); - int nr = latBuffer.getInt(); - int nz = latBuffer.getInt(); - - xmin = latBuffer.getFloat(); - dx = latBuffer.getFloat(); - ymin = latBuffer.getFloat(); - dy = latBuffer.getFloat(); - - float angle = latBuffer.getFloat(); - xmax = xmin + ((nc - 1) * dx); - ymax = ymin + ((nr - 1) * dy); - - //skip the longitude header description - longBuffer.position(longBuffer.position() + DESCRIPTION_LENGTH); - - //check that latitude grid header is the same as for latitude grid - if ( (nc != longBuffer.getInt()) - || (nr != longBuffer.getInt()) - || (nz != longBuffer.getInt()) - || (xmin != longBuffer.getFloat()) - || (dx != longBuffer.getFloat()) - || (ymin != longBuffer.getFloat()) - || (dy != longBuffer.getFloat()) - || (angle != longBuffer.getFloat())) { - throw new FactoryException(Errors.format(ErrorKeys.GRID_LOCATIONS_UNEQUAL)); - } - - //////////////////////// - //read grid shift data into LocalizationGrid - //////////////////////// - final int RECORD_LENGTH = (nc * 4) + SEPARATOR_BYTES; - final int NUM_BYTES_LEFT = ((nr + 1) * RECORD_LENGTH) - HEADER_BYTES; - final int START_OF_DATA = RECORD_LENGTH - HEADER_BYTES; - - latBuffer = fillBuffer(latChannel, NUM_BYTES_LEFT); - latBuffer.position(START_OF_DATA); //start of second record (data) - - longBuffer = fillBuffer(longChannel, NUM_BYTES_LEFT); - longBuffer.position(START_OF_DATA); - - gridShift = new LocalizationGrid(nc, nr); - - int i = 0; - int j = 0; - for (i = 0; i < nr; i++) { - latBuffer.position(latBuffer.position() + SEPARATOR_BYTES); //skip record separator - longBuffer.position(longBuffer.position() + SEPARATOR_BYTES); - - for (j = 0; j < nc; j++) { - gridShift.setLocalizationPoint(j, i, longBuffer.getFloat(), latBuffer.getFloat()); - } - } - - assert i == nr : i; - assert j == nc : j; - } - - /** - * Returns a new bytebuffer, of numBytes length and little endian byte - * order, filled from the channel. - * - * @param channel the channel to fill the buffer from - * @param numBytes number of bytes to read - * @return a new bytebuffer filled from the channel - * @throws IOException if there is a problem reading the channel - * @throws EOFException if the end of the channel is reached - */ - private ByteBuffer fillBuffer(ReadableByteChannel channel, int numBytes) - throws IOException { - ByteBuffer buf = ByteBuffer.allocate(numBytes); - - if (fill(buf, channel) == -1) { - throw new EOFException(Errors.format(ErrorKeys.END_OF_DATA_FILE)); - } - - buf.flip(); - buf.order(ByteOrder.LITTLE_ENDIAN); - - return buf; - } - - /** - * Fills the bytebuffer from the channel. Code was lifted from - * ShapefileDataStore. - * - * @param buffer bytebuffer to fill from the channel - * @param channel channel to fill the buffer from - * @return number of bytes read - * @throws IOException if there is a problem reading the channel - */ - private int fill(ByteBuffer buffer, ReadableByt... [truncated message content] |