Index: trunk/src/org/openstreetmap/josm/data/projection/AbstractProjection.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/AbstractProjection.java	(revision 5072)
+++ trunk/src/org/openstreetmap/josm/data/projection/AbstractProjection.java	(revision 5073)
@@ -20,6 +20,4 @@
  * Subclasses of AbstractProjection must set ellps and proj to a non-null value.
  * In addition, either datum or nadgrid has to be initialized to some value.
- *
- * FIXME: nadgrids should probably be implemented as a Datum
  */
 abstract public class AbstractProjection implements Projection {
@@ -32,6 +30,5 @@
     protected double lon_0 = 0.0;   /* central meridian */
     protected double k_0 = 1.0;     /* general scale factor */
-    protected NTV2GridShiftFile nadgrids = null;
-    
+
     public final Ellipsoid getEllipsoid() {
         return ellps;
@@ -64,11 +61,5 @@
     @Override
     public EastNorth latlon2eastNorth(LatLon ll) {
-        if (nadgrids != null) {
-            NTV2GridShift gs = new NTV2GridShift(ll);
-            nadgrids.gridShiftReverse(gs);
-            ll = new LatLon(ll.lat()+gs.getLatShiftDegrees(), ll.lon()+gs.getLonShiftPositiveEastDegrees());
-        } else {
-            ll = datum.fromWGS84(ll);
-        }
+        ll = datum.fromWGS84(ll);
         double[] en = proj.project(Math.toRadians(ll.lat()), Math.toRadians(ll.lon() - lon_0));
         return new EastNorth(ellps.a * k_0 * en[0] + x_0, ellps.a * k_0 * en[1] + y_0);
@@ -79,12 +70,5 @@
         double[] latlon_rad = proj.invproject((en.east() - x_0) / ellps.a / k_0, (en.north() - y_0) / ellps.a / k_0);
         LatLon ll = new LatLon(Math.toDegrees(latlon_rad[0]), Math.toDegrees(latlon_rad[1]) + lon_0);
-        if (nadgrids != null) {
-            NTV2GridShift gs = new NTV2GridShift(ll);
-            nadgrids.gridShiftForward(gs);
-            ll = new LatLon(ll.lat()+gs.getLatShiftDegrees(), ll.lon()+gs.getLonShiftPositiveEastDegrees());
-        } else {
-            ll = datum.toWGS84(ll);
-        }
-        return ll;
+        return datum.toWGS84(ll);
     }
 
@@ -94,5 +78,5 @@
         return 10;
     }
-    
+
     /**
      * @return The EPSG Code of this CRS, null if it doesn't have one.
@@ -108,9 +92,9 @@
         return "EPSG:" + getEpsgCode();
     }
-    
+
     protected static final double convertMinuteSecond(double minute, double second) {
         return (minute/60.0) + (second/3600.0);
     }
-    
+
     protected static final double convertDegreeMinuteSecond(double degree, double minute, double second) {
         return degree + convertMinuteSecond(minute, second);
Index: trunk/src/org/openstreetmap/josm/data/projection/GaussKrueger.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/GaussKrueger.java	(revision 5072)
+++ trunk/src/org/openstreetmap/josm/data/projection/GaussKrueger.java	(revision 5073)
@@ -17,4 +17,6 @@
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
+import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFile;
 import org.openstreetmap.josm.data.projection.proj.ProjParameters;
 import org.openstreetmap.josm.data.projection.proj.TransverseMercator;
@@ -60,5 +62,5 @@
         this.zone = zone;
         ellps = Ellipsoid.Bessel1841;
-        nadgrids = BETA2007;
+        datum = new NTV2Datum("BETA2007", null, ellps, BETA2007);
         ////less acurrate datum (errors up to 3m):
         //datum = new SevenParameterDatum(
Index: trunk/src/org/openstreetmap/josm/data/projection/Lambert.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/Lambert.java	(revision 5072)
+++ trunk/src/org/openstreetmap/josm/data/projection/Lambert.java	(revision 5073)
@@ -17,4 +17,6 @@
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
+import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFile;
 import org.openstreetmap.josm.data.projection.proj.LambertConformalConic;
 import org.openstreetmap.josm.data.projection.proj.ProjParameters;
@@ -101,6 +103,5 @@
         this.layoutZone = layoutZone;
         ellps = Ellipsoid.clarkeIGN;
-        datum = null; // no datum needed, we have a shift file
-        nadgrids = ntf_rgf93Grid;
+        datum = new NTV2Datum("ntf_rgf93Grid", null, ellps, ntf_rgf93Grid);
         x_0 = x_0s[layoutZone];
         lon_0 = 2.0 + 20.0 / 60 + 14.025 / 3600; // 0 grade Paris
Index: trunk/src/org/openstreetmap/josm/data/projection/NTV2GridShift.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/NTV2GridShift.java	(revision 5072)
+++ 	(revision )
@@ -1,311 +1,0 @@
-/*
- * 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: trunk/src/org/openstreetmap/josm/data/projection/NTV2GridShiftFile.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/NTV2GridShiftFile.java	(revision 5072)
+++ 	(revision )
@@ -1,322 +1,0 @@
-/*
- * 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.IOException;
-import java.io.InputStream;
-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 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<String, ArrayList<NTV2SubGrid>> subGridMap = new HashMap<String, ArrayList<NTV2SubGrid>>();
-        for (int i = 0; i < subGrid.length; i++) {
-            if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) {
-                topLevelCount++;
-            }
-            subGridMap.put(subGrid[i].getSubGridName(), new ArrayList<NTV2SubGrid>());
-        }
-        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<NTV2SubGrid> parent = subGridMap.get(subGrid[i].getParentSubGridName());
-                parent.add(subGrid[i]);
-            }
-        }
-        NTV2SubGrid[] nullArray = new NTV2SubGrid[0];
-        for (int i = 0; i < subGrid.length; i++) {
-            ArrayList<NTV2SubGrid> subSubGrids = subGridMap.get(subGrid[i].getSubGridName());
-            if (subSubGrids.size() > 0) {
-                NTV2SubGrid[] subGridArray = 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() {
-        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: trunk/src/org/openstreetmap/josm/data/projection/NTV2SubGrid.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/NTV2SubGrid.java	(revision 5072)
+++ 	(revision )
@@ -1,364 +1,0 @@
-/*
- * 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.IOException;
-import java.io.InputStream;
-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 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: trunk/src/org/openstreetmap/josm/data/projection/NTV2Util.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/NTV2Util.java	(revision 5072)
+++ 	(revision )
@@ -1,117 +1,0 @@
-/*
- * 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;
-    }
-}
Index: trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2Datum.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2Datum.java	(revision 5073)
+++ trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2Datum.java	(revision 5073)
@@ -0,0 +1,32 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.projection.datum;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.projection.Ellipsoid;
+
+/**
+ * Datum based of NTV2 grid shift file.
+ */
+public class NTV2Datum extends AbstractDatum {
+
+    protected NTV2GridShiftFile nadgrids;
+
+    public NTV2Datum(String name, String proj4Id, Ellipsoid ellps, NTV2GridShiftFile nadgrids) {
+        super(name, proj4Id, ellps);
+        this.nadgrids = nadgrids;
+    }
+
+    @Override
+    public LatLon toWGS84(LatLon ll) {
+        NTV2GridShift gs = new NTV2GridShift(ll);
+        nadgrids.gridShiftForward(gs);
+        return new LatLon(ll.lat() + gs.getLatShiftDegrees(), ll.lon() + gs.getLonShiftPositiveEastDegrees());
+    }
+
+    @Override
+    public LatLon fromWGS84(LatLon ll) {
+        NTV2GridShift gs = new NTV2GridShift(ll);
+        nadgrids.gridShiftReverse(gs);
+        return new LatLon(ll.lat() + gs.getLatShiftDegrees(), ll.lon() + gs.getLonShiftPositiveEastDegrees());
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShift.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShift.java	(revision 5073)
+++ trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShift.java	(revision 5073)
@@ -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.datum;
+
+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: trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShiftFile.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShiftFile.java	(revision 5073)
+++ trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShiftFile.java	(revision 5073)
@@ -0,0 +1,322 @@
+/*
+ * 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.datum;
+
+import java.io.IOException;
+import java.io.InputStream;
+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 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<String, ArrayList<NTV2SubGrid>> subGridMap = new HashMap<String, ArrayList<NTV2SubGrid>>();
+        for (int i = 0; i < subGrid.length; i++) {
+            if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) {
+                topLevelCount++;
+            }
+            subGridMap.put(subGrid[i].getSubGridName(), new ArrayList<NTV2SubGrid>());
+        }
+        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<NTV2SubGrid> parent = subGridMap.get(subGrid[i].getParentSubGridName());
+                parent.add(subGrid[i]);
+            }
+        }
+        NTV2SubGrid[] nullArray = new NTV2SubGrid[0];
+        for (int i = 0; i < subGrid.length; i++) {
+            ArrayList<NTV2SubGrid> subSubGrids = subGridMap.get(subGrid[i].getSubGridName());
+            if (subSubGrids.size() > 0) {
+                NTV2SubGrid[] subGridArray = 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() {
+        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: trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2SubGrid.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2SubGrid.java	(revision 5073)
+++ trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2SubGrid.java	(revision 5073)
@@ -0,0 +1,364 @@
+/*
+ * 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.datum;
+
+import java.io.IOException;
+import java.io.InputStream;
+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 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: trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2Util.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2Util.java	(revision 5073)
+++ trunk/src/org/openstreetmap/josm/data/projection/datum/NTV2Util.java	(revision 5073)
@@ -0,0 +1,117 @@
+/*
+ * 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.datum;
+
+/**
+ * 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;
+    }
+}
Index: trunk/test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java	(revision 5072)
+++ trunk/test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java	(revision 5073)
@@ -24,6 +24,9 @@
 import java.util.Set;
 
+import org.junit.BeforeClass;
 import org.junit.Test;
+import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.Preferences;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
@@ -54,4 +57,5 @@
 
     public static void main(String[] args) throws IOException, FileNotFoundException {
+        setUp();
         Map<String, Projection> allCodes = new LinkedHashMap<String, Projection>();
         List<Projection> projs = Projections.getProjections();
@@ -144,4 +148,9 @@
     }
 
+    @BeforeClass
+    public static void setUp() {
+        Main.pref = new Preferences();
+    }
+
     @Test
     public void regressionTest() throws IOException, FileNotFoundException {
