Index: NTV2GridShift.java
===================================================================
--- NTV2GridShift.java	(revision 0)
+++ NTV2GridShift.java	(revision 0)
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2003 Objectix Pty Ltd  All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL OBJECTIX PTY LTD BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.openstreetmap.josm.data.projection;
+
+import java.io.Serializable;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+
+/**
+ * A value object for storing Longitude and Latitude of a point, the
+ * Lon and Lat shift values to get from one datum to another, and the
+ * Lon and Lat accuracy of the shift values.
+ * <p>All values are stored as Positive West Seconds, but accessors
+ * are also provided for Positive East Degrees.
+ * 
+ * @author Peter Yuill
+ * Modifified for JOSM :
+ * - add a constructor for JOSM LatLon (Pieren)
+ */
+public class NTV2GridShift implements Serializable {
+
+    private static final double METRE_PER_SECOND = 2.0 * Math.PI * 6378137.0 / 3600.0 / 360.0;
+    private static final double RADIANS_PER_SECOND = 2.0 * Math.PI / 3600.0 / 360.0;
+    private double lon;
+    private double lat;
+    private double lonShift;
+    private double latShift;
+    private double lonAccuracy;
+    private double latAccuracy;
+    boolean latAccuracyAvailable;
+    boolean lonAccuracyAvailable;
+    private String subGridName;
+
+    public NTV2GridShift() {
+    }
+
+    public NTV2GridShift(LatLon p) {
+        setLatDegrees(p.lat());
+        setLonPositiveEastDegrees(p.lon());
+    }
+
+    /**
+     * @return
+     */
+    public double getLatSeconds() {
+        return lat;
+    }
+
+    /**
+     * @return
+     */
+    public double getLatDegrees() {
+        return lat / 3600.0;
+    }
+
+    /**
+     * @return
+     */
+    public double getLatShiftSeconds() {
+        return latShift;
+    }
+
+    /**
+     * @return
+     */
+    public double getLatShiftDegrees() {
+        return latShift / 3600.0;
+    }
+
+    /**
+     * @return
+     */
+    public double getShiftedLatSeconds() {
+        return lat + latShift;
+    }
+
+    /**
+     * @return
+     */
+    public double getShiftedLatDegrees() {
+        return (lat + latShift) / 3600.0;
+    }
+
+    /**
+     * @return
+     */
+    public boolean isLatAccuracyAvailable() {
+        return latAccuracyAvailable;
+    }
+
+    /**
+     * @return
+     */
+    public double getLatAccuracySeconds() {
+        if (!latAccuracyAvailable)
+            throw new IllegalStateException("Latitude Accuracy not available");
+        return latAccuracy;
+    }
+
+    /**
+     * @return
+     */
+    public double getLatAccuracyDegrees() {
+        if (!latAccuracyAvailable)
+            throw new IllegalStateException("Latitude Accuracy not available");
+        return latAccuracy / 3600.0;
+    }
+
+    /**
+     * @return
+     */
+    public double getLatAccuracyMetres() {
+        if (!latAccuracyAvailable)
+            throw new IllegalStateException("Latitude Accuracy not available");
+        return latAccuracy * METRE_PER_SECOND;
+    }
+
+    /**
+     * @return
+     */
+    public double getLonPositiveWestSeconds() {
+        return lon;
+    }
+
+    /**
+     * @return
+     */
+    public double getLonPositiveEastDegrees() {
+        return lon / -3600.0;
+    }
+
+    /**
+     * @return
+     */
+    public double getLonShiftPositiveWestSeconds() {
+        return lonShift;
+    }
+
+    /**
+     * @return
+     */
+    public double getLonShiftPositiveEastDegrees() {
+        return lonShift / -3600.0;
+    }
+
+    /**
+     * @return
+     */
+    public double getShiftedLonPositiveWestSeconds() {
+        return lon + lonShift;
+    }
+
+    /**
+     * @return
+     */
+    public double getShiftedLonPositiveEastDegrees() {
+        return (lon + lonShift) / -3600.0;
+    }
+
+    /**
+     * @return
+     */
+    public boolean isLonAccuracyAvailable() {
+        return lonAccuracyAvailable;
+    }
+
+    /**
+     * @return
+     */
+    public double getLonAccuracySeconds() {
+        if (!lonAccuracyAvailable)
+            throw new IllegalStateException("Longitude Accuracy not available");
+        return lonAccuracy;
+    }
+
+    /**
+     * @return
+     */
+    public double getLonAccuracyDegrees() {
+        if (!lonAccuracyAvailable)
+            throw new IllegalStateException("Longitude Accuracy not available");
+        return lonAccuracy / 3600.0;
+    }
+
+    /**
+     * @return
+     */
+    public double getLonAccuracyMetres() {
+        if (!lonAccuracyAvailable)
+            throw new IllegalStateException("Longitude Accuracy not available");
+        return lonAccuracy * METRE_PER_SECOND * Math.cos(RADIANS_PER_SECOND * lat);
+    }
+
+    /**
+     * @param d
+     */
+    public void setLatSeconds(double d) {
+        lat = d;
+    }
+
+    /**
+     * @param d
+     */
+    public void setLatDegrees(double d) {
+        lat = d * 3600.0;
+    }
+
+    /**
+     * @param b
+     */
+    public void setLatAccuracyAvailable(boolean b) {
+        latAccuracyAvailable = b;
+    }
+
+    /**
+     * @param d
+     */
+    public void setLatAccuracySeconds(double d) {
+        latAccuracy = d;
+    }
+
+    /**
+     * @param d
+     */
+    public void setLatShiftSeconds(double d) {
+        latShift = d;
+    }
+
+    /**
+     * @param d
+     */
+    public void setLonPositiveWestSeconds(double d) {
+        lon = d;
+    }
+
+    /**
+     * @param d
+     */
+    public void setLonPositiveEastDegrees(double d) {
+        lon = d * -3600.0;
+    }
+
+    /**
+     * @param b
+     */
+    public void setLonAccuracyAvailable(boolean b) {
+        lonAccuracyAvailable = b;
+    }
+
+    /**
+     * @param d
+     */
+    public void setLonAccuracySeconds(double d) {
+        lonAccuracy = d;
+    }
+
+    /**
+     * @param d
+     */
+    public void setLonShiftPositiveWestSeconds(double d) {
+        lonShift = d;
+    }
+
+    /**
+     * @return
+     */
+    public String getSubGridName() {
+        return subGridName;
+    }
+
+    /**
+     * @param string
+     */
+    public void setSubGridName(String string) {
+        subGridName = string;
+    }
+
+    /**
+     * Make this object a copy of the supplied GridShift
+     * @param gs
+     */
+    public void copy(NTV2GridShift gs) {
+        this.lon = gs.lon;
+        this.lat = gs.lat;
+        this.lonShift = gs.lonShift;
+        this.latShift = gs.latShift;
+        this.lonAccuracy = gs.lonAccuracy;
+        this.latAccuracy = gs.latAccuracy;
+        this.latAccuracyAvailable = gs.latAccuracyAvailable;
+        this.lonAccuracyAvailable = gs.lonAccuracyAvailable;
+        this.subGridName = gs.subGridName;
+    }
+
+}
Index: NTV2GridShiftFile.java
===================================================================
--- NTV2GridShiftFile.java	(revision 0)
+++ NTV2GridShiftFile.java	(revision 0)
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2003 Objectix Pty Ltd  All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL OBJECTIX PTY LTD BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.openstreetmap.josm.data.projection;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Models the NTv2 format Grid Shift File and exposes methods to shift
+ * coordinate values using the Sub Grids contained in the file.
+ * <p>The principal reference for the alogrithms used is the
+ * 'GDAit Software Architecture Manual' produced by the <a
+ * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
+ * Department of the University of Melbourne</a>
+ * <p>This library reads binary NTv2 Grid Shift files in Big Endian
+ * (Canadian standard) or Little Endian (Australian Standard) format.
+ * The older 'Australian' binary format is not supported, only the
+ * official Canadian format, which is now also used for the national
+ * Australian Grid.
+ * <p>Grid Shift files can be read as InputStreams or RandomAccessFiles.
+ * Loading an InputStream places all the required node information
+ * (accuracy data is optional) into heap based Java arrays. This is the
+ * highest perfomance option, and is useful for large volume transformations.
+ * Non-file data sources (eg using an SQL Blob) are also supported through
+ * InputStream. The RandonAccessFile option has a much smaller memory
+ * footprint as only the Sub Grid headers are stored in memory, but
+ * transformation is slower because the file must be read a number of
+ * times for each transformation.
+ * <p>Coordinates may be shifted Forward (ie from and to the Datums specified
+ * in the Grid Shift File header) or Reverse. The reverse transformation
+ * uses an iterative approach to approximate the Grid Shift, as the
+ * precise transformation is based on 'from' datum coordinates.
+ * <p>Coordinates may be specified
+ * either in Seconds using Positive West Longitude (the original NTv2
+ * arrangement) or in decimal Degrees using Positive East Longitude.
+ * 
+ * @author Peter Yuill
+ * Modifified for JOSM :
+ * - removed the RandomAccessFile mode (Pieren)
+ */
+public class NTV2GridShiftFile implements Serializable {
+
+    private static final int REC_SIZE = 16;
+    private String overviewHeaderCountId;
+    private int overviewHeaderCount;
+    private int subGridHeaderCount;
+    private int subGridCount;
+    private String shiftType;
+    private String version;
+    private String fromEllipsoid = "";
+    private String toEllipsoid = "";
+    private double fromSemiMajorAxis;
+    private double fromSemiMinorAxis;
+    private double toSemiMajorAxis;
+    private double toSemiMinorAxis;
+
+    private NTV2SubGrid[] topLevelSubGrid;
+    private NTV2SubGrid lastSubGrid;
+
+    public NTV2GridShiftFile() {
+    }
+
+    /**
+     * Load a Grid Shift File from an InputStream. The Grid Shift node
+     * data is stored in Java arrays, which will occupy about the same memory
+     * as the original file with accuracy data included, and about half that
+     * with accuracy data excluded. The size of the Australian national file
+     * is 4.5MB, and the Canadian national file is 13.5MB
+     * <p>The InputStream is closed by this method.
+     * 
+     * @param in Grid Shift File InputStream
+     * @param loadAccuracy is Accuracy data to be loaded as well as shift data?
+     * @throws Exception
+     */
+    public void loadGridShiftFile(InputStream in, boolean loadAccuracy ) throws IOException {
+        byte[] b8 = new byte[8];
+        boolean bigEndian = true;
+        fromEllipsoid = "";
+        toEllipsoid = "";
+        topLevelSubGrid = null;
+        in.read(b8);
+        overviewHeaderCountId = new String(b8);
+        if (!"NUM_OREC".equals(overviewHeaderCountId))
+            throw new IllegalArgumentException("Input file is not an NTv2 grid shift file");
+        in.read(b8);
+        overviewHeaderCount = NTV2Util.getIntBE(b8, 0);
+        if (overviewHeaderCount == 11) {
+            bigEndian = true;
+        } else {
+            overviewHeaderCount = NTV2Util.getIntLE(b8, 0);
+            if (overviewHeaderCount == 11) {
+                bigEndian = false;
+            } else
+                throw new IllegalArgumentException("Input file is not an NTv2 grid shift file");
+        }
+        in.read(b8);
+        in.read(b8);
+        subGridHeaderCount = NTV2Util.getInt(b8, bigEndian);
+        in.read(b8);
+        in.read(b8);
+        subGridCount = NTV2Util.getInt(b8, bigEndian);
+        NTV2SubGrid[] subGrid = new NTV2SubGrid[subGridCount];
+        in.read(b8);
+        in.read(b8);
+        shiftType = new String(b8);
+        in.read(b8);
+        in.read(b8);
+        version = new String(b8);
+        in.read(b8);
+        in.read(b8);
+        fromEllipsoid = new String(b8);
+        in.read(b8);
+        in.read(b8);
+        toEllipsoid = new String(b8);
+        in.read(b8);
+        in.read(b8);
+        fromSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian);
+        in.read(b8);
+        in.read(b8);
+        fromSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian);
+        in.read(b8);
+        in.read(b8);
+        toSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian);
+        in.read(b8);
+        in.read(b8);
+        toSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian);
+
+        for (int i = 0; i < subGridCount; i++) {
+            subGrid[i] = new NTV2SubGrid(in, bigEndian, loadAccuracy);
+        }
+        topLevelSubGrid = createSubGridTree(subGrid);
+        lastSubGrid = topLevelSubGrid[0];
+
+        in.close();
+    }
+
+    /**
+     * Create a tree of Sub Grids by adding each Sub Grid to its parent (where
+     * it has one), and returning an array of the top level Sub Grids
+     * @param subGrid an array of all Sub Grids
+     * @return an array of top level Sub Grids with lower level Sub Grids set.
+     */
+    private NTV2SubGrid[] createSubGridTree(NTV2SubGrid[] subGrid) {
+        int topLevelCount = 0;
+        HashMap subGridMap = new HashMap();
+        for (int i = 0; i < subGrid.length; i++) {
+            if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) {
+                topLevelCount++;
+            }
+            subGridMap.put(subGrid[i].getSubGridName(), new ArrayList());
+        }
+        NTV2SubGrid[] topLevelSubGrid = new NTV2SubGrid[topLevelCount];
+        topLevelCount = 0;
+        for (int i = 0; i < subGrid.length; i++) {
+            if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) {
+                topLevelSubGrid[topLevelCount++] = subGrid[i];
+            } else {
+                ArrayList parent = (ArrayList)subGridMap.get(subGrid[i].getParentSubGridName());
+                parent.add(subGrid[i]);
+            }
+        }
+        NTV2SubGrid[] nullArray = new NTV2SubGrid[0];
+        for (int i = 0; i < subGrid.length; i++) {
+            ArrayList subSubGrids = (ArrayList)subGridMap.get(subGrid[i].getSubGridName());
+            if (subSubGrids.size() > 0) {
+                NTV2SubGrid[] subGridArray = (NTV2SubGrid[])subSubGrids.toArray(nullArray);
+                subGrid[i].setSubGridArray(subGridArray);
+            }
+        }
+        return topLevelSubGrid;
+    }
+
+    /**
+     * Shift a coordinate in the Forward direction of the Grid Shift File.
+     * 
+     * @param gs A GridShift object containing the coordinate to shift
+     * @return True if the coordinate is within a Sub Grid, false if not
+     * @throws IOException
+     */
+    public boolean gridShiftForward(NTV2GridShift gs) {
+        // Try the last sub grid first, big chance the coord is still within it
+        NTV2SubGrid subGrid = lastSubGrid.getSubGridForCoord(gs.getLonPositiveWestSeconds(), gs.getLatSeconds());
+        if (subGrid == null) {
+            subGrid = getSubGrid(gs.getLonPositiveWestSeconds(), gs.getLatSeconds());
+        }
+        if (subGrid == null)
+            return false;
+        else {
+            subGrid.interpolateGridShift(gs);
+            gs.setSubGridName(subGrid.getSubGridName());
+            lastSubGrid = subGrid;
+            return true;
+        }
+    }
+
+    /**
+     * Shift a coordinate in the Reverse direction of the Grid Shift File.
+     * 
+     * @param gs A GridShift object containing the coordinate to shift
+     * @return True if the coordinate is within a Sub Grid, false if not
+     * @throws IOException
+     */
+    public boolean gridShiftReverse(NTV2GridShift gs) {
+        // set up the first estimate
+        NTV2GridShift forwardGs = new NTV2GridShift();
+        forwardGs.setLonPositiveWestSeconds(gs.getLonPositiveWestSeconds());
+        forwardGs.setLatSeconds(gs.getLatSeconds());
+        for (int i = 0; i < 4; i++) {
+            if (!gridShiftForward(forwardGs))
+                return false;
+            forwardGs.setLonPositiveWestSeconds(
+                    gs.getLonPositiveWestSeconds() - forwardGs.getLonShiftPositiveWestSeconds());
+            forwardGs.setLatSeconds(gs.getLatSeconds() - forwardGs.getLatShiftSeconds());
+        }
+        gs.setLonShiftPositiveWestSeconds(-forwardGs.getLonShiftPositiveWestSeconds());
+        gs.setLatShiftSeconds(-forwardGs.getLatShiftSeconds());
+        gs.setLonAccuracyAvailable(forwardGs.isLonAccuracyAvailable());
+        if (forwardGs.isLonAccuracyAvailable()) {
+            gs.setLonAccuracySeconds(forwardGs.getLonAccuracySeconds());
+        }
+        gs.setLatAccuracyAvailable(forwardGs.isLatAccuracyAvailable());
+        if (forwardGs.isLatAccuracyAvailable()) {
+            gs.setLatAccuracySeconds(forwardGs.getLatAccuracySeconds());
+        }
+        return true;
+    }
+
+    /**
+     * Find the finest SubGrid containing the coordinate, specified
+     * in Positive West Seconds
+     * 
+     * @param lon Longitude in Positive West Seconds
+     * @param lat Latitude in Seconds
+     * @return The SubGrid found or null
+     */
+    private NTV2SubGrid getSubGrid(double lon, double lat) {
+        NTV2SubGrid sub = null;
+        for (int i = 0; i < topLevelSubGrid.length; i++) {
+            sub = topLevelSubGrid[i].getSubGridForCoord(lon, lat);
+            if (sub != null) {
+                break;
+            }
+        }
+        return sub;
+    }
+
+    public boolean isLoaded() {
+        return (topLevelSubGrid != null);
+    }
+
+    public void unload() throws IOException {
+        topLevelSubGrid = null;
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer buf = new StringBuffer("Headers  : ");
+        buf.append(overviewHeaderCount);
+        buf.append("\nSub Hdrs : ");
+        buf.append(subGridHeaderCount);
+        buf.append("\nSub Grids: ");
+        buf.append(subGridCount);
+        buf.append("\nType     : ");
+        buf.append(shiftType);
+        buf.append("\nVersion  : ");
+        buf.append(version);
+        buf.append("\nFr Ellpsd: ");
+        buf.append(fromEllipsoid);
+        buf.append("\nTo Ellpsd: ");
+        buf.append(toEllipsoid);
+        buf.append("\nFr Maj Ax: ");
+        buf.append(fromSemiMajorAxis);
+        buf.append("\nFr Min Ax: ");
+        buf.append(fromSemiMinorAxis);
+        buf.append("\nTo Maj Ax: ");
+        buf.append(toSemiMajorAxis);
+        buf.append("\nTo Min Ax: ");
+        buf.append(toSemiMinorAxis);
+        return buf.toString();
+    }
+
+    /**
+     * Get a copy of the SubGrid tree for this file.
+     * 
+     * @return a deep clone of the current SubGrid tree
+     */
+    public NTV2SubGrid[] getSubGridTree() {
+        NTV2SubGrid[] clone = new NTV2SubGrid[topLevelSubGrid.length];
+        for (int i = 0; i < topLevelSubGrid.length; i++) {
+            clone[i] = (NTV2SubGrid)topLevelSubGrid[i].clone();
+        }
+        return clone;
+    }
+
+    public String getFromEllipsoid() {
+        return fromEllipsoid;
+    }
+
+    public String getToEllipsoid() {
+        return toEllipsoid;
+    }
+
+}
Index: NTV2SubGrid.java
===================================================================
--- NTV2SubGrid.java	(revision 0)
+++ NTV2SubGrid.java	(revision 0)
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2003 Objectix Pty Ltd  All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL OBJECTIX PTY LTD BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.openstreetmap.josm.data.projection;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * Models the NTv2 Sub Grid within a Grid Shift File
+ * 
+ * @author Peter Yuill
+ * Modifified for JOSM :
+ * - removed the RandomAccessFile mode (Pieren)
+ * - read grid file by single bytes. Workaround for a bug in some VM not supporting
+ *   file reading by group of 4 bytes from a jar file.
+ */
+public class NTV2SubGrid implements Cloneable, Serializable {
+
+    private static final int REC_SIZE = 16;
+
+    private String subGridName;
+    private String parentSubGridName;
+    private String created;
+    private String updated;
+    private double minLat;
+    private double maxLat;
+    private double minLon;
+    private double maxLon;
+    private double latInterval;
+    private double lonInterval;
+    private int nodeCount;
+
+    private int lonColumnCount;
+    private int latRowCount;
+    private float[] latShift;
+    private float[] lonShift;
+    private float[] latAccuracy;
+    private float[] lonAccuracy;
+
+    boolean bigEndian;
+    private NTV2SubGrid[] subGrid;
+
+    /**
+     * Construct a Sub Grid from an InputStream, loading the node data into
+     * arrays in this object.
+     * 
+     * @param in GridShiftFile InputStream
+     * @param bigEndian is the file bigEndian?
+     * @param loadAccuracy is the node Accuracy data to be loaded?
+     * @throws Exception
+     */
+    public NTV2SubGrid(InputStream in, boolean bigEndian, boolean loadAccuracy) throws IOException {
+        byte[] b8 = new byte[8];
+        byte[] b4 = new byte[4];
+        byte[] b1 = new byte[1];
+        in.read(b8);
+        in.read(b8);
+        subGridName = new String(b8).trim();
+        in.read(b8);
+        in.read(b8);
+        parentSubGridName = new String(b8).trim();
+        in.read(b8);
+        in.read(b8);
+        created = new String(b8);
+        in.read(b8);
+        in.read(b8);
+        updated = new String(b8);
+        in.read(b8);
+        in.read(b8);
+        minLat = NTV2Util.getDouble(b8, bigEndian);
+        in.read(b8);
+        in.read(b8);
+        maxLat = NTV2Util.getDouble(b8, bigEndian);
+        in.read(b8);
+        in.read(b8);
+        minLon = NTV2Util.getDouble(b8, bigEndian);
+        in.read(b8);
+        in.read(b8);
+        maxLon = NTV2Util.getDouble(b8, bigEndian);
+        in.read(b8);
+        in.read(b8);
+        latInterval = NTV2Util.getDouble(b8, bigEndian);
+        in.read(b8);
+        in.read(b8);
+        lonInterval = NTV2Util.getDouble(b8, bigEndian);
+        lonColumnCount = 1 + (int)((maxLon - minLon) / lonInterval);
+        latRowCount = 1 + (int)((maxLat - minLat) / latInterval);
+        in.read(b8);
+        in.read(b8);
+        nodeCount = NTV2Util.getInt(b8, bigEndian);
+        if (nodeCount != lonColumnCount * latRowCount)
+            throw new IllegalStateException("SubGrid " + subGridName + " has inconsistent grid dimesions");
+        latShift = new float[nodeCount];
+        lonShift = new float[nodeCount];
+        if (loadAccuracy) {
+            latAccuracy = new float[nodeCount];
+            lonAccuracy = new float[nodeCount];
+        }
+
+        for (int i = 0; i < nodeCount; i++) {
+            // Read the grid file byte after byte. This is a workaround about a bug in
+            // certain VM which are not able to read byte blocks when the resource file is
+            // in a .jar file (Pieren)
+            in.read(b1); b4[0] = b1[0];
+            in.read(b1); b4[1] = b1[0];
+            in.read(b1); b4[2] = b1[0];
+            in.read(b1); b4[3] = b1[0];
+            latShift[i] = NTV2Util.getFloat(b4, bigEndian);
+            in.read(b1); b4[0] = b1[0];
+            in.read(b1); b4[1] = b1[0];
+            in.read(b1); b4[2] = b1[0];
+            in.read(b1); b4[3] = b1[0];
+            lonShift[i] = NTV2Util.getFloat(b4, bigEndian);
+            in.read(b1); b4[0] = b1[0];
+            in.read(b1); b4[1] = b1[0];
+            in.read(b1); b4[2] = b1[0];
+            in.read(b1); b4[3] = b1[0];
+            if (loadAccuracy) {
+                latAccuracy[i] = NTV2Util.getFloat(b4, bigEndian);
+            }
+            in.read(b1); b4[0] = b1[0];
+            in.read(b1); b4[1] = b1[0];
+            in.read(b1); b4[2] = b1[0];
+            in.read(b1); b4[3] = b1[0];
+            if (loadAccuracy) {
+                lonAccuracy[i] = NTV2Util.getFloat(b4, bigEndian);
+            }
+        }
+    }
+
+    /**
+     * Tests if a specified coordinate is within this Sub Grid
+     * or one of its Sub Grids. If the coordinate is outside
+     * this Sub Grid, null is returned. If the coordinate is
+     * within this Sub Grid, but not within any of its Sub Grids,
+     * this Sub Grid is returned. If the coordinate is within
+     * one of this Sub Grid's Sub Grids, the method is called
+     * recursively on the child Sub Grid.
+     * 
+     * @param lon Longitude in Positive West Seconds
+     * @param lat Latitude in Seconds
+     * @return the Sub Grid containing the Coordinate or null
+     */
+    public NTV2SubGrid getSubGridForCoord(double lon, double lat) {
+        if (isCoordWithin(lon, lat)) {
+            if (subGrid == null)
+                return this;
+            else {
+                for (int i = 0; i < subGrid.length; i++) {
+                    if (subGrid[i].isCoordWithin(lon, lat))
+                        return subGrid[i].getSubGridForCoord(lon, lat);
+                }
+                return this;
+            }
+        } else
+            return null;
+    }
+
+    /**
+     * Tests if a specified coordinate is within this Sub Grid.
+     * A coordinate on either outer edge (maximum Latitude or
+     * maximum Longitude) is deemed to be outside the grid.
+     * 
+     * @param lon Longitude in Positive West Seconds
+     * @param lat Latitude in Seconds
+     * @return true or false
+     */
+    private boolean isCoordWithin(double lon, double lat) {
+        if ((lon >= minLon) && (lon < maxLon) && (lat >= minLat) && (lat < maxLat))
+            return true;
+        else
+            return false;
+    }
+
+    /**
+     * Bi-Linear interpolation of four nearest node values as described in
+     * 'GDAit Software Architecture Manual' produced by the <a
+     * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
+     * Department of the University of Melbourne</a>
+     * @param a value at the A node
+     * @param b value at the B node
+     * @param c value at the C node
+     * @param d value at the D node
+     * @param X Longitude factor
+     * @param Y Latitude factor
+     * @return interpolated value
+     */
+    private final double interpolate(float a, float b, float c, float d, double X, double Y) {
+        return a + (((double)b - (double)a) * X) + (((double)c - (double)a) * Y) +
+        (((double)a + (double)d - b - c) * X * Y);
+    }
+
+    /**
+     * Interpolate shift and accuracy values for a coordinate in the 'from' datum
+     * of the GridShiftFile. The algorithm is described in
+     * 'GDAit Software Architecture Manual' produced by the <a
+     * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
+     * Department of the University of Melbourne</a>
+     * <p>This method is thread safe for both memory based and file based node data.
+     * @param gs GridShift object containing the coordinate to shift and the shift values
+     * @return the GridShift object supplied, with values updated.
+     * @throws IOException
+     */
+    public NTV2GridShift interpolateGridShift(NTV2GridShift gs) {
+        int lonIndex = (int)((gs.getLonPositiveWestSeconds() - minLon) / lonInterval);
+        int latIndex = (int)((gs.getLatSeconds() - minLat) / latInterval);
+
+        double X = (gs.getLonPositiveWestSeconds() - (minLon + (lonInterval * lonIndex))) / lonInterval;
+        double Y = (gs.getLatSeconds() - (minLat + (latInterval * latIndex))) / latInterval;
+
+        // Find the nodes at the four corners of the cell
+
+        int indexA = lonIndex + (latIndex * lonColumnCount);
+        int indexB = indexA + 1;
+        int indexC = indexA + lonColumnCount;
+        int indexD = indexC + 1;
+
+        gs.setLonShiftPositiveWestSeconds(interpolate(
+                lonShift[indexA], lonShift[indexB], lonShift[indexC], lonShift[indexD], X, Y));
+
+        gs.setLatShiftSeconds(interpolate(
+                latShift[indexA], latShift[indexB], latShift[indexC], latShift[indexD], X, Y));
+
+        if (lonAccuracy == null) {
+            gs.setLonAccuracyAvailable(false);
+        } else {
+            gs.setLonAccuracyAvailable(true);
+            gs.setLonAccuracySeconds(interpolate(
+                    lonAccuracy[indexA], lonAccuracy[indexB], lonAccuracy[indexC], lonAccuracy[indexD], X, Y));
+        }
+
+        if (latAccuracy == null) {
+            gs.setLatAccuracyAvailable(false);
+        } else {
+            gs.setLatAccuracyAvailable(true);
+            gs.setLatAccuracySeconds(interpolate(
+                    latAccuracy[indexA], latAccuracy[indexB], latAccuracy[indexC], latAccuracy[indexD], X, Y));
+        }
+        return gs;
+    }
+
+    public String getParentSubGridName() {
+        return parentSubGridName;
+    }
+
+    public String getSubGridName() {
+        return subGridName;
+    }
+
+    public int getNodeCount() {
+        return nodeCount;
+    }
+
+    public int getSubGridCount() {
+        return (subGrid == null) ? 0 : subGrid.length;
+    }
+
+    public NTV2SubGrid getSubGrid(int index) {
+        return (subGrid == null) ? null : subGrid[index];
+    }
+
+    /**
+     * Set an array of Sub Grids of this sub grid
+     * @param subGrid
+     */
+    public void setSubGridArray(NTV2SubGrid[] subGrid) {
+        this.subGrid = subGrid;
+    }
+
+    @Override
+    public String toString() {
+        return subGridName;
+    }
+
+    public String getDetails() {
+        StringBuffer buf = new StringBuffer("Sub Grid : ");
+        buf.append(subGridName);
+        buf.append("\nParent   : ");
+        buf.append(parentSubGridName);
+        buf.append("\nCreated  : ");
+        buf.append(created);
+        buf.append("\nUpdated  : ");
+        buf.append(updated);
+        buf.append("\nMin Lat  : ");
+        buf.append(minLat);
+        buf.append("\nMax Lat  : ");
+        buf.append(maxLat);
+        buf.append("\nMin Lon  : ");
+        buf.append(minLon);
+        buf.append("\nMax Lon  : ");
+        buf.append(maxLon);
+        buf.append("\nLat Intvl: ");
+        buf.append(latInterval);
+        buf.append("\nLon Intvl: ");
+        buf.append(lonInterval);
+        buf.append("\nNode Cnt : ");
+        buf.append(nodeCount);
+        return buf.toString();
+    }
+
+    /**
+     * Make a deep clone of this Sub Grid
+     */
+    @Override
+    public Object clone() {
+        NTV2SubGrid clone = null;
+        try {
+            clone = (NTV2SubGrid)super.clone();
+        } catch (CloneNotSupportedException cnse) {
+        }
+        // Do a deep clone of the sub grids
+        if (subGrid != null) {
+            clone.subGrid = new NTV2SubGrid[subGrid.length];
+            for (int i = 0; i < subGrid.length; i++) {
+                clone.subGrid[i] = (NTV2SubGrid)subGrid[i].clone();
+            }
+        }
+        return clone;
+    }
+    /**
+     * @return
+     */
+    public double getMaxLat() {
+        return maxLat;
+    }
+
+    /**
+     * @return
+     */
+    public double getMaxLon() {
+        return maxLon;
+    }
+
+    /**
+     * @return
+     */
+    public double getMinLat() {
+        return minLat;
+    }
+
+    /**
+     * @return
+     */
+    public double getMinLon() {
+        return minLon;
+    }
+
+}
Index: NTV2Util.java
===================================================================
--- NTV2Util.java	(revision 0)
+++ NTV2Util.java	(revision 0)
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2003 Objectix Pty Ltd  All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL OBJECTIX PTY LTD BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.openstreetmap.josm.data.projection;
+
+/**
+ * A set of static utility methods for reading the NTv2 file format
+ * 
+ * @author Peter Yuill
+ */
+public class NTV2Util {
+
+    private NTV2Util() {
+    }
+
+    /**
+     * Get a Little Endian int from four bytes of a byte array
+     * @param b the byte array
+     * @param i the index of the first data byte in the array
+     * @return the int
+     */
+    public static final int getIntLE(byte[] b, int i) {
+        return (b[i++] & 0x000000FF) | ((b[i++] << 8) & 0x0000FF00) | ((b[i++] << 16) & 0x00FF0000) | (b[i] << 24);
+    }
+
+    /**
+     * Get a Big Endian int from four bytes of a byte array
+     * @param b the byte array
+     * @param i the index of the first data byte in the array
+     * @return the int
+     */
+    public static final int getIntBE(byte[] b, int i) {
+        return (b[i++] << 24) | ((b[i++] << 16) & 0x00FF0000) | ((b[i++] << 8) & 0x0000FF00) | (b[i] & 0x000000FF);
+    }
+
+    /**
+     * Get an int from the first 4 bytes of a byte array,
+     * in either Big Endian or Little Endian format.
+     * @param b the byte array
+     * @param bigEndian is the byte array Big Endian?
+     * @return the int
+     */
+    public static final int getInt(byte[] b, boolean bigEndian) {
+        if (bigEndian)
+            return getIntBE(b, 0);
+        else
+            return getIntLE(b, 0);
+    }
+
+    /**
+     * Get a float from the first 4 bytes of a byte array,
+     * in either Big Endian or Little Endian format.
+     * @param b the byte array
+     * @param bigEndian is the byte array Big Endian?
+     * @return the float
+     */
+    public static final float getFloat(byte[] b, boolean bigEndian) {
+        int i = 0;
+        if (bigEndian) {
+            i = getIntBE(b, 0);
+        } else {
+            i = getIntLE(b, 0);
+        }
+        return Float.intBitsToFloat(i);
+    }
+
+
+    /**
+     * Get a double from the first 8 bytes of a byte array,
+     * in either Big Endian or Little Endian format.
+     * @param b the byte array
+     * @param bigEndian is the byte array Big Endian?
+     * @return the double
+     */
+    public static final double getDouble(byte[] b, boolean bigEndian) {
+        int i = 0;
+        int j = 0;
+        if (bigEndian) {
+            i = getIntBE(b, 0);
+            j = getIntBE(b, 4);
+        } else {
+            i = getIntLE(b, 4);
+            j = getIntLE(b, 0);
+        }
+        long l = ((long)i << 32) |
+        (j & 0x00000000FFFFFFFFL);
+        return Double.longBitsToDouble(l);
+    }
+
+    /**
+     * Does the current VM support the New IO api
+     * @return true or false
+     */
+    public static boolean isNioAvailable() {
+        boolean nioAvailable = false;
+        try {
+            Class.forName("java.nio.channels.FileChannel");
+            nioAvailable = true;
+        } catch (ClassNotFoundException cnfe) {}
+        return nioAvailable;
+    }
+}
