Ticket #3987: NTV2Grid.patch

File NTV2Grid.patch, 38.2 KB (added by pieren, 14 years ago)
  • NTV2GridShift.java

     
     1/*
     2 * Copyright (c) 2003 Objectix Pty Ltd  All rights reserved.
     3 *
     4 * This library is free software; you can redistribute it and/or
     5 * modify it under the terms of the GNU Lesser General Public
     6 * License as published by the Free Software Foundation.
     7 *
     8 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
     9 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     10 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     11 * DISCLAIMED.  IN NO EVENT SHALL OBJECTIX PTY LTD BE LIABLE FOR ANY
     12 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     13 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     14 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     15 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     16 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
     17 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
     18 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     19 */
     20package org.openstreetmap.josm.data.projection;
     21
     22import java.io.Serializable;
     23
     24import org.openstreetmap.josm.data.coor.LatLon;
     25
     26/**
     27 * A value object for storing Longitude and Latitude of a point, the
     28 * Lon and Lat shift values to get from one datum to another, and the
     29 * Lon and Lat accuracy of the shift values.
     30 * <p>All values are stored as Positive West Seconds, but accessors
     31 * are also provided for Positive East Degrees.
     32 *
     33 * @author Peter Yuill
     34 * Modifified for JOSM :
     35 * - add a constructor for JOSM LatLon (Pieren)
     36 */
     37public class NTV2GridShift implements Serializable {
     38
     39    private static final double METRE_PER_SECOND = 2.0 * Math.PI * 6378137.0 / 3600.0 / 360.0;
     40    private static final double RADIANS_PER_SECOND = 2.0 * Math.PI / 3600.0 / 360.0;
     41    private double lon;
     42    private double lat;
     43    private double lonShift;
     44    private double latShift;
     45    private double lonAccuracy;
     46    private double latAccuracy;
     47    boolean latAccuracyAvailable;
     48    boolean lonAccuracyAvailable;
     49    private String subGridName;
     50
     51    public NTV2GridShift() {
     52    }
     53
     54    public NTV2GridShift(LatLon p) {
     55        setLatDegrees(p.lat());
     56        setLonPositiveEastDegrees(p.lon());
     57    }
     58
     59    /**
     60     * @return
     61     */
     62    public double getLatSeconds() {
     63        return lat;
     64    }
     65
     66    /**
     67     * @return
     68     */
     69    public double getLatDegrees() {
     70        return lat / 3600.0;
     71    }
     72
     73    /**
     74     * @return
     75     */
     76    public double getLatShiftSeconds() {
     77        return latShift;
     78    }
     79
     80    /**
     81     * @return
     82     */
     83    public double getLatShiftDegrees() {
     84        return latShift / 3600.0;
     85    }
     86
     87    /**
     88     * @return
     89     */
     90    public double getShiftedLatSeconds() {
     91        return lat + latShift;
     92    }
     93
     94    /**
     95     * @return
     96     */
     97    public double getShiftedLatDegrees() {
     98        return (lat + latShift) / 3600.0;
     99    }
     100
     101    /**
     102     * @return
     103     */
     104    public boolean isLatAccuracyAvailable() {
     105        return latAccuracyAvailable;
     106    }
     107
     108    /**
     109     * @return
     110     */
     111    public double getLatAccuracySeconds() {
     112        if (!latAccuracyAvailable)
     113            throw new IllegalStateException("Latitude Accuracy not available");
     114        return latAccuracy;
     115    }
     116
     117    /**
     118     * @return
     119     */
     120    public double getLatAccuracyDegrees() {
     121        if (!latAccuracyAvailable)
     122            throw new IllegalStateException("Latitude Accuracy not available");
     123        return latAccuracy / 3600.0;
     124    }
     125
     126    /**
     127     * @return
     128     */
     129    public double getLatAccuracyMetres() {
     130        if (!latAccuracyAvailable)
     131            throw new IllegalStateException("Latitude Accuracy not available");
     132        return latAccuracy * METRE_PER_SECOND;
     133    }
     134
     135    /**
     136     * @return
     137     */
     138    public double getLonPositiveWestSeconds() {
     139        return lon;
     140    }
     141
     142    /**
     143     * @return
     144     */
     145    public double getLonPositiveEastDegrees() {
     146        return lon / -3600.0;
     147    }
     148
     149    /**
     150     * @return
     151     */
     152    public double getLonShiftPositiveWestSeconds() {
     153        return lonShift;
     154    }
     155
     156    /**
     157     * @return
     158     */
     159    public double getLonShiftPositiveEastDegrees() {
     160        return lonShift / -3600.0;
     161    }
     162
     163    /**
     164     * @return
     165     */
     166    public double getShiftedLonPositiveWestSeconds() {
     167        return lon + lonShift;
     168    }
     169
     170    /**
     171     * @return
     172     */
     173    public double getShiftedLonPositiveEastDegrees() {
     174        return (lon + lonShift) / -3600.0;
     175    }
     176
     177    /**
     178     * @return
     179     */
     180    public boolean isLonAccuracyAvailable() {
     181        return lonAccuracyAvailable;
     182    }
     183
     184    /**
     185     * @return
     186     */
     187    public double getLonAccuracySeconds() {
     188        if (!lonAccuracyAvailable)
     189            throw new IllegalStateException("Longitude Accuracy not available");
     190        return lonAccuracy;
     191    }
     192
     193    /**
     194     * @return
     195     */
     196    public double getLonAccuracyDegrees() {
     197        if (!lonAccuracyAvailable)
     198            throw new IllegalStateException("Longitude Accuracy not available");
     199        return lonAccuracy / 3600.0;
     200    }
     201
     202    /**
     203     * @return
     204     */
     205    public double getLonAccuracyMetres() {
     206        if (!lonAccuracyAvailable)
     207            throw new IllegalStateException("Longitude Accuracy not available");
     208        return lonAccuracy * METRE_PER_SECOND * Math.cos(RADIANS_PER_SECOND * lat);
     209    }
     210
     211    /**
     212     * @param d
     213     */
     214    public void setLatSeconds(double d) {
     215        lat = d;
     216    }
     217
     218    /**
     219     * @param d
     220     */
     221    public void setLatDegrees(double d) {
     222        lat = d * 3600.0;
     223    }
     224
     225    /**
     226     * @param b
     227     */
     228    public void setLatAccuracyAvailable(boolean b) {
     229        latAccuracyAvailable = b;
     230    }
     231
     232    /**
     233     * @param d
     234     */
     235    public void setLatAccuracySeconds(double d) {
     236        latAccuracy = d;
     237    }
     238
     239    /**
     240     * @param d
     241     */
     242    public void setLatShiftSeconds(double d) {
     243        latShift = d;
     244    }
     245
     246    /**
     247     * @param d
     248     */
     249    public void setLonPositiveWestSeconds(double d) {
     250        lon = d;
     251    }
     252
     253    /**
     254     * @param d
     255     */
     256    public void setLonPositiveEastDegrees(double d) {
     257        lon = d * -3600.0;
     258    }
     259
     260    /**
     261     * @param b
     262     */
     263    public void setLonAccuracyAvailable(boolean b) {
     264        lonAccuracyAvailable = b;
     265    }
     266
     267    /**
     268     * @param d
     269     */
     270    public void setLonAccuracySeconds(double d) {
     271        lonAccuracy = d;
     272    }
     273
     274    /**
     275     * @param d
     276     */
     277    public void setLonShiftPositiveWestSeconds(double d) {
     278        lonShift = d;
     279    }
     280
     281    /**
     282     * @return
     283     */
     284    public String getSubGridName() {
     285        return subGridName;
     286    }
     287
     288    /**
     289     * @param string
     290     */
     291    public void setSubGridName(String string) {
     292        subGridName = string;
     293    }
     294
     295    /**
     296     * Make this object a copy of the supplied GridShift
     297     * @param gs
     298     */
     299    public void copy(NTV2GridShift gs) {
     300        this.lon = gs.lon;
     301        this.lat = gs.lat;
     302        this.lonShift = gs.lonShift;
     303        this.latShift = gs.latShift;
     304        this.lonAccuracy = gs.lonAccuracy;
     305        this.latAccuracy = gs.latAccuracy;
     306        this.latAccuracyAvailable = gs.latAccuracyAvailable;
     307        this.lonAccuracyAvailable = gs.lonAccuracyAvailable;
     308        this.subGridName = gs.subGridName;
     309    }
     310
     311}
  • NTV2GridShiftFile.java

     
     1/*
     2 * Copyright (c) 2003 Objectix Pty Ltd  All rights reserved.
     3 *
     4 * This library is free software; you can redistribute it and/or
     5 * modify it under the terms of the GNU Lesser General Public
     6 * License as published by the Free Software Foundation.
     7 *
     8 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
     9 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     10 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     11 * DISCLAIMED.  IN NO EVENT SHALL OBJECTIX PTY LTD BE LIABLE FOR ANY
     12 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     13 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     14 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     15 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     16 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
     17 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
     18 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     19 */
     20package org.openstreetmap.josm.data.projection;
     21
     22import java.io.InputStream;
     23import java.io.IOException;
     24import java.io.RandomAccessFile;
     25import java.io.Serializable;
     26import java.util.ArrayList;
     27import java.util.HashMap;
     28
     29/**
     30 * Models the NTv2 format Grid Shift File and exposes methods to shift
     31 * coordinate values using the Sub Grids contained in the file.
     32 * <p>The principal reference for the alogrithms used is the
     33 * 'GDAit Software Architecture Manual' produced by the <a
     34 * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
     35 * Department of the University of Melbourne</a>
     36 * <p>This library reads binary NTv2 Grid Shift files in Big Endian
     37 * (Canadian standard) or Little Endian (Australian Standard) format.
     38 * The older 'Australian' binary format is not supported, only the
     39 * official Canadian format, which is now also used for the national
     40 * Australian Grid.
     41 * <p>Grid Shift files can be read as InputStreams or RandomAccessFiles.
     42 * Loading an InputStream places all the required node information
     43 * (accuracy data is optional) into heap based Java arrays. This is the
     44 * highest perfomance option, and is useful for large volume transformations.
     45 * Non-file data sources (eg using an SQL Blob) are also supported through
     46 * InputStream. The RandonAccessFile option has a much smaller memory
     47 * footprint as only the Sub Grid headers are stored in memory, but
     48 * transformation is slower because the file must be read a number of
     49 * times for each transformation.
     50 * <p>Coordinates may be shifted Forward (ie from and to the Datums specified
     51 * in the Grid Shift File header) or Reverse. The reverse transformation
     52 * uses an iterative approach to approximate the Grid Shift, as the
     53 * precise transformation is based on 'from' datum coordinates.
     54 * <p>Coordinates may be specified
     55 * either in Seconds using Positive West Longitude (the original NTv2
     56 * arrangement) or in decimal Degrees using Positive East Longitude.
     57 *
     58 * @author Peter Yuill
     59 * Modifified for JOSM :
     60 * - removed the RandomAccessFile mode (Pieren)
     61 */
     62public class NTV2GridShiftFile implements Serializable {
     63
     64    private static final int REC_SIZE = 16;
     65    private String overviewHeaderCountId;
     66    private int overviewHeaderCount;
     67    private int subGridHeaderCount;
     68    private int subGridCount;
     69    private String shiftType;
     70    private String version;
     71    private String fromEllipsoid = "";
     72    private String toEllipsoid = "";
     73    private double fromSemiMajorAxis;
     74    private double fromSemiMinorAxis;
     75    private double toSemiMajorAxis;
     76    private double toSemiMinorAxis;
     77
     78    private NTV2SubGrid[] topLevelSubGrid;
     79    private NTV2SubGrid lastSubGrid;
     80
     81    public NTV2GridShiftFile() {
     82    }
     83
     84    /**
     85     * Load a Grid Shift File from an InputStream. The Grid Shift node
     86     * data is stored in Java arrays, which will occupy about the same memory
     87     * as the original file with accuracy data included, and about half that
     88     * with accuracy data excluded. The size of the Australian national file
     89     * is 4.5MB, and the Canadian national file is 13.5MB
     90     * <p>The InputStream is closed by this method.
     91     *
     92     * @param in Grid Shift File InputStream
     93     * @param loadAccuracy is Accuracy data to be loaded as well as shift data?
     94     * @throws Exception
     95     */
     96    public void loadGridShiftFile(InputStream in, boolean loadAccuracy ) throws IOException {
     97        byte[] b8 = new byte[8];
     98        boolean bigEndian = true;
     99        fromEllipsoid = "";
     100        toEllipsoid = "";
     101        topLevelSubGrid = null;
     102        in.read(b8);
     103        overviewHeaderCountId = new String(b8);
     104        if (!"NUM_OREC".equals(overviewHeaderCountId))
     105            throw new IllegalArgumentException("Input file is not an NTv2 grid shift file");
     106        in.read(b8);
     107        overviewHeaderCount = NTV2Util.getIntBE(b8, 0);
     108        if (overviewHeaderCount == 11) {
     109            bigEndian = true;
     110        } else {
     111            overviewHeaderCount = NTV2Util.getIntLE(b8, 0);
     112            if (overviewHeaderCount == 11) {
     113                bigEndian = false;
     114            } else
     115                throw new IllegalArgumentException("Input file is not an NTv2 grid shift file");
     116        }
     117        in.read(b8);
     118        in.read(b8);
     119        subGridHeaderCount = NTV2Util.getInt(b8, bigEndian);
     120        in.read(b8);
     121        in.read(b8);
     122        subGridCount = NTV2Util.getInt(b8, bigEndian);
     123        NTV2SubGrid[] subGrid = new NTV2SubGrid[subGridCount];
     124        in.read(b8);
     125        in.read(b8);
     126        shiftType = new String(b8);
     127        in.read(b8);
     128        in.read(b8);
     129        version = new String(b8);
     130        in.read(b8);
     131        in.read(b8);
     132        fromEllipsoid = new String(b8);
     133        in.read(b8);
     134        in.read(b8);
     135        toEllipsoid = new String(b8);
     136        in.read(b8);
     137        in.read(b8);
     138        fromSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian);
     139        in.read(b8);
     140        in.read(b8);
     141        fromSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian);
     142        in.read(b8);
     143        in.read(b8);
     144        toSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian);
     145        in.read(b8);
     146        in.read(b8);
     147        toSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian);
     148
     149        for (int i = 0; i < subGridCount; i++) {
     150            subGrid[i] = new NTV2SubGrid(in, bigEndian, loadAccuracy);
     151        }
     152        topLevelSubGrid = createSubGridTree(subGrid);
     153        lastSubGrid = topLevelSubGrid[0];
     154
     155        in.close();
     156    }
     157
     158    /**
     159     * Create a tree of Sub Grids by adding each Sub Grid to its parent (where
     160     * it has one), and returning an array of the top level Sub Grids
     161     * @param subGrid an array of all Sub Grids
     162     * @return an array of top level Sub Grids with lower level Sub Grids set.
     163     */
     164    private NTV2SubGrid[] createSubGridTree(NTV2SubGrid[] subGrid) {
     165        int topLevelCount = 0;
     166        HashMap subGridMap = new HashMap();
     167        for (int i = 0; i < subGrid.length; i++) {
     168            if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) {
     169                topLevelCount++;
     170            }
     171            subGridMap.put(subGrid[i].getSubGridName(), new ArrayList());
     172        }
     173        NTV2SubGrid[] topLevelSubGrid = new NTV2SubGrid[topLevelCount];
     174        topLevelCount = 0;
     175        for (int i = 0; i < subGrid.length; i++) {
     176            if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) {
     177                topLevelSubGrid[topLevelCount++] = subGrid[i];
     178            } else {
     179                ArrayList parent = (ArrayList)subGridMap.get(subGrid[i].getParentSubGridName());
     180                parent.add(subGrid[i]);
     181            }
     182        }
     183        NTV2SubGrid[] nullArray = new NTV2SubGrid[0];
     184        for (int i = 0; i < subGrid.length; i++) {
     185            ArrayList subSubGrids = (ArrayList)subGridMap.get(subGrid[i].getSubGridName());
     186            if (subSubGrids.size() > 0) {
     187                NTV2SubGrid[] subGridArray = (NTV2SubGrid[])subSubGrids.toArray(nullArray);
     188                subGrid[i].setSubGridArray(subGridArray);
     189            }
     190        }
     191        return topLevelSubGrid;
     192    }
     193
     194    /**
     195     * Shift a coordinate in the Forward direction of the Grid Shift File.
     196     *
     197     * @param gs A GridShift object containing the coordinate to shift
     198     * @return True if the coordinate is within a Sub Grid, false if not
     199     * @throws IOException
     200     */
     201    public boolean gridShiftForward(NTV2GridShift gs) {
     202        // Try the last sub grid first, big chance the coord is still within it
     203        NTV2SubGrid subGrid = lastSubGrid.getSubGridForCoord(gs.getLonPositiveWestSeconds(), gs.getLatSeconds());
     204        if (subGrid == null) {
     205            subGrid = getSubGrid(gs.getLonPositiveWestSeconds(), gs.getLatSeconds());
     206        }
     207        if (subGrid == null)
     208            return false;
     209        else {
     210            subGrid.interpolateGridShift(gs);
     211            gs.setSubGridName(subGrid.getSubGridName());
     212            lastSubGrid = subGrid;
     213            return true;
     214        }
     215    }
     216
     217    /**
     218     * Shift a coordinate in the Reverse direction of the Grid Shift File.
     219     *
     220     * @param gs A GridShift object containing the coordinate to shift
     221     * @return True if the coordinate is within a Sub Grid, false if not
     222     * @throws IOException
     223     */
     224    public boolean gridShiftReverse(NTV2GridShift gs) {
     225        // set up the first estimate
     226        NTV2GridShift forwardGs = new NTV2GridShift();
     227        forwardGs.setLonPositiveWestSeconds(gs.getLonPositiveWestSeconds());
     228        forwardGs.setLatSeconds(gs.getLatSeconds());
     229        for (int i = 0; i < 4; i++) {
     230            if (!gridShiftForward(forwardGs))
     231                return false;
     232            forwardGs.setLonPositiveWestSeconds(
     233                    gs.getLonPositiveWestSeconds() - forwardGs.getLonShiftPositiveWestSeconds());
     234            forwardGs.setLatSeconds(gs.getLatSeconds() - forwardGs.getLatShiftSeconds());
     235        }
     236        gs.setLonShiftPositiveWestSeconds(-forwardGs.getLonShiftPositiveWestSeconds());
     237        gs.setLatShiftSeconds(-forwardGs.getLatShiftSeconds());
     238        gs.setLonAccuracyAvailable(forwardGs.isLonAccuracyAvailable());
     239        if (forwardGs.isLonAccuracyAvailable()) {
     240            gs.setLonAccuracySeconds(forwardGs.getLonAccuracySeconds());
     241        }
     242        gs.setLatAccuracyAvailable(forwardGs.isLatAccuracyAvailable());
     243        if (forwardGs.isLatAccuracyAvailable()) {
     244            gs.setLatAccuracySeconds(forwardGs.getLatAccuracySeconds());
     245        }
     246        return true;
     247    }
     248
     249    /**
     250     * Find the finest SubGrid containing the coordinate, specified
     251     * in Positive West Seconds
     252     *
     253     * @param lon Longitude in Positive West Seconds
     254     * @param lat Latitude in Seconds
     255     * @return The SubGrid found or null
     256     */
     257    private NTV2SubGrid getSubGrid(double lon, double lat) {
     258        NTV2SubGrid sub = null;
     259        for (int i = 0; i < topLevelSubGrid.length; i++) {
     260            sub = topLevelSubGrid[i].getSubGridForCoord(lon, lat);
     261            if (sub != null) {
     262                break;
     263            }
     264        }
     265        return sub;
     266    }
     267
     268    public boolean isLoaded() {
     269        return (topLevelSubGrid != null);
     270    }
     271
     272    public void unload() throws IOException {
     273        topLevelSubGrid = null;
     274    }
     275
     276    @Override
     277    public String toString() {
     278        StringBuffer buf = new StringBuffer("Headers  : ");
     279        buf.append(overviewHeaderCount);
     280        buf.append("\nSub Hdrs : ");
     281        buf.append(subGridHeaderCount);
     282        buf.append("\nSub Grids: ");
     283        buf.append(subGridCount);
     284        buf.append("\nType     : ");
     285        buf.append(shiftType);
     286        buf.append("\nVersion  : ");
     287        buf.append(version);
     288        buf.append("\nFr Ellpsd: ");
     289        buf.append(fromEllipsoid);
     290        buf.append("\nTo Ellpsd: ");
     291        buf.append(toEllipsoid);
     292        buf.append("\nFr Maj Ax: ");
     293        buf.append(fromSemiMajorAxis);
     294        buf.append("\nFr Min Ax: ");
     295        buf.append(fromSemiMinorAxis);
     296        buf.append("\nTo Maj Ax: ");
     297        buf.append(toSemiMajorAxis);
     298        buf.append("\nTo Min Ax: ");
     299        buf.append(toSemiMinorAxis);
     300        return buf.toString();
     301    }
     302
     303    /**
     304     * Get a copy of the SubGrid tree for this file.
     305     *
     306     * @return a deep clone of the current SubGrid tree
     307     */
     308    public NTV2SubGrid[] getSubGridTree() {
     309        NTV2SubGrid[] clone = new NTV2SubGrid[topLevelSubGrid.length];
     310        for (int i = 0; i < topLevelSubGrid.length; i++) {
     311            clone[i] = (NTV2SubGrid)topLevelSubGrid[i].clone();
     312        }
     313        return clone;
     314    }
     315
     316    public String getFromEllipsoid() {
     317        return fromEllipsoid;
     318    }
     319
     320    public String getToEllipsoid() {
     321        return toEllipsoid;
     322    }
     323
     324}
  • NTV2SubGrid.java

     
     1/*
     2 * Copyright (c) 2003 Objectix Pty Ltd  All rights reserved.
     3 *
     4 * This library is free software; you can redistribute it and/or
     5 * modify it under the terms of the GNU Lesser General Public
     6 * License as published by the Free Software Foundation.
     7 *
     8 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
     9 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     10 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     11 * DISCLAIMED.  IN NO EVENT SHALL OBJECTIX PTY LTD BE LIABLE FOR ANY
     12 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     13 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     14 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     15 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     16 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
     17 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
     18 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     19 */
     20package org.openstreetmap.josm.data.projection;
     21
     22import java.io.InputStream;
     23import java.io.IOException;
     24import java.io.Serializable;
     25
     26/**
     27 * Models the NTv2 Sub Grid within a Grid Shift File
     28 *
     29 * @author Peter Yuill
     30 * Modifified for JOSM :
     31 * - removed the RandomAccessFile mode (Pieren)
     32 * - read grid file by single bytes. Workaround for a bug in some VM not supporting
     33 *   file reading by group of 4 bytes from a jar file.
     34 */
     35public class NTV2SubGrid implements Cloneable, Serializable {
     36
     37    private static final int REC_SIZE = 16;
     38
     39    private String subGridName;
     40    private String parentSubGridName;
     41    private String created;
     42    private String updated;
     43    private double minLat;
     44    private double maxLat;
     45    private double minLon;
     46    private double maxLon;
     47    private double latInterval;
     48    private double lonInterval;
     49    private int nodeCount;
     50
     51    private int lonColumnCount;
     52    private int latRowCount;
     53    private float[] latShift;
     54    private float[] lonShift;
     55    private float[] latAccuracy;
     56    private float[] lonAccuracy;
     57
     58    boolean bigEndian;
     59    private NTV2SubGrid[] subGrid;
     60
     61    /**
     62     * Construct a Sub Grid from an InputStream, loading the node data into
     63     * arrays in this object.
     64     *
     65     * @param in GridShiftFile InputStream
     66     * @param bigEndian is the file bigEndian?
     67     * @param loadAccuracy is the node Accuracy data to be loaded?
     68     * @throws Exception
     69     */
     70    public NTV2SubGrid(InputStream in, boolean bigEndian, boolean loadAccuracy) throws IOException {
     71        byte[] b8 = new byte[8];
     72        byte[] b4 = new byte[4];
     73        byte[] b1 = new byte[1];
     74        in.read(b8);
     75        in.read(b8);
     76        subGridName = new String(b8).trim();
     77        in.read(b8);
     78        in.read(b8);
     79        parentSubGridName = new String(b8).trim();
     80        in.read(b8);
     81        in.read(b8);
     82        created = new String(b8);
     83        in.read(b8);
     84        in.read(b8);
     85        updated = new String(b8);
     86        in.read(b8);
     87        in.read(b8);
     88        minLat = NTV2Util.getDouble(b8, bigEndian);
     89        in.read(b8);
     90        in.read(b8);
     91        maxLat = NTV2Util.getDouble(b8, bigEndian);
     92        in.read(b8);
     93        in.read(b8);
     94        minLon = NTV2Util.getDouble(b8, bigEndian);
     95        in.read(b8);
     96        in.read(b8);
     97        maxLon = NTV2Util.getDouble(b8, bigEndian);
     98        in.read(b8);
     99        in.read(b8);
     100        latInterval = NTV2Util.getDouble(b8, bigEndian);
     101        in.read(b8);
     102        in.read(b8);
     103        lonInterval = NTV2Util.getDouble(b8, bigEndian);
     104        lonColumnCount = 1 + (int)((maxLon - minLon) / lonInterval);
     105        latRowCount = 1 + (int)((maxLat - minLat) / latInterval);
     106        in.read(b8);
     107        in.read(b8);
     108        nodeCount = NTV2Util.getInt(b8, bigEndian);
     109        if (nodeCount != lonColumnCount * latRowCount)
     110            throw new IllegalStateException("SubGrid " + subGridName + " has inconsistent grid dimesions");
     111        latShift = new float[nodeCount];
     112        lonShift = new float[nodeCount];
     113        if (loadAccuracy) {
     114            latAccuracy = new float[nodeCount];
     115            lonAccuracy = new float[nodeCount];
     116        }
     117
     118        for (int i = 0; i < nodeCount; i++) {
     119            // Read the grid file byte after byte. This is a workaround about a bug in
     120            // certain VM which are not able to read byte blocks when the resource file is
     121            // in a .jar file (Pieren)
     122            in.read(b1); b4[0] = b1[0];
     123            in.read(b1); b4[1] = b1[0];
     124            in.read(b1); b4[2] = b1[0];
     125            in.read(b1); b4[3] = b1[0];
     126            latShift[i] = NTV2Util.getFloat(b4, bigEndian);
     127            in.read(b1); b4[0] = b1[0];
     128            in.read(b1); b4[1] = b1[0];
     129            in.read(b1); b4[2] = b1[0];
     130            in.read(b1); b4[3] = b1[0];
     131            lonShift[i] = NTV2Util.getFloat(b4, bigEndian);
     132            in.read(b1); b4[0] = b1[0];
     133            in.read(b1); b4[1] = b1[0];
     134            in.read(b1); b4[2] = b1[0];
     135            in.read(b1); b4[3] = b1[0];
     136            if (loadAccuracy) {
     137                latAccuracy[i] = NTV2Util.getFloat(b4, bigEndian);
     138            }
     139            in.read(b1); b4[0] = b1[0];
     140            in.read(b1); b4[1] = b1[0];
     141            in.read(b1); b4[2] = b1[0];
     142            in.read(b1); b4[3] = b1[0];
     143            if (loadAccuracy) {
     144                lonAccuracy[i] = NTV2Util.getFloat(b4, bigEndian);
     145            }
     146        }
     147    }
     148
     149    /**
     150     * Tests if a specified coordinate is within this Sub Grid
     151     * or one of its Sub Grids. If the coordinate is outside
     152     * this Sub Grid, null is returned. If the coordinate is
     153     * within this Sub Grid, but not within any of its Sub Grids,
     154     * this Sub Grid is returned. If the coordinate is within
     155     * one of this Sub Grid's Sub Grids, the method is called
     156     * recursively on the child Sub Grid.
     157     *
     158     * @param lon Longitude in Positive West Seconds
     159     * @param lat Latitude in Seconds
     160     * @return the Sub Grid containing the Coordinate or null
     161     */
     162    public NTV2SubGrid getSubGridForCoord(double lon, double lat) {
     163        if (isCoordWithin(lon, lat)) {
     164            if (subGrid == null)
     165                return this;
     166            else {
     167                for (int i = 0; i < subGrid.length; i++) {
     168                    if (subGrid[i].isCoordWithin(lon, lat))
     169                        return subGrid[i].getSubGridForCoord(lon, lat);
     170                }
     171                return this;
     172            }
     173        } else
     174            return null;
     175    }
     176
     177    /**
     178     * Tests if a specified coordinate is within this Sub Grid.
     179     * A coordinate on either outer edge (maximum Latitude or
     180     * maximum Longitude) is deemed to be outside the grid.
     181     *
     182     * @param lon Longitude in Positive West Seconds
     183     * @param lat Latitude in Seconds
     184     * @return true or false
     185     */
     186    private boolean isCoordWithin(double lon, double lat) {
     187        if ((lon >= minLon) && (lon < maxLon) && (lat >= minLat) && (lat < maxLat))
     188            return true;
     189        else
     190            return false;
     191    }
     192
     193    /**
     194     * Bi-Linear interpolation of four nearest node values as described in
     195     * 'GDAit Software Architecture Manual' produced by the <a
     196     * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
     197     * Department of the University of Melbourne</a>
     198     * @param a value at the A node
     199     * @param b value at the B node
     200     * @param c value at the C node
     201     * @param d value at the D node
     202     * @param X Longitude factor
     203     * @param Y Latitude factor
     204     * @return interpolated value
     205     */
     206    private final double interpolate(float a, float b, float c, float d, double X, double Y) {
     207        return a + (((double)b - (double)a) * X) + (((double)c - (double)a) * Y) +
     208        (((double)a + (double)d - b - c) * X * Y);
     209    }
     210
     211    /**
     212     * Interpolate shift and accuracy values for a coordinate in the 'from' datum
     213     * of the GridShiftFile. The algorithm is described in
     214     * 'GDAit Software Architecture Manual' produced by the <a
     215     * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
     216     * Department of the University of Melbourne</a>
     217     * <p>This method is thread safe for both memory based and file based node data.
     218     * @param gs GridShift object containing the coordinate to shift and the shift values
     219     * @return the GridShift object supplied, with values updated.
     220     * @throws IOException
     221     */
     222    public NTV2GridShift interpolateGridShift(NTV2GridShift gs) {
     223        int lonIndex = (int)((gs.getLonPositiveWestSeconds() - minLon) / lonInterval);
     224        int latIndex = (int)((gs.getLatSeconds() - minLat) / latInterval);
     225
     226        double X = (gs.getLonPositiveWestSeconds() - (minLon + (lonInterval * lonIndex))) / lonInterval;
     227        double Y = (gs.getLatSeconds() - (minLat + (latInterval * latIndex))) / latInterval;
     228
     229        // Find the nodes at the four corners of the cell
     230
     231        int indexA = lonIndex + (latIndex * lonColumnCount);
     232        int indexB = indexA + 1;
     233        int indexC = indexA + lonColumnCount;
     234        int indexD = indexC + 1;
     235
     236        gs.setLonShiftPositiveWestSeconds(interpolate(
     237                lonShift[indexA], lonShift[indexB], lonShift[indexC], lonShift[indexD], X, Y));
     238
     239        gs.setLatShiftSeconds(interpolate(
     240                latShift[indexA], latShift[indexB], latShift[indexC], latShift[indexD], X, Y));
     241
     242        if (lonAccuracy == null) {
     243            gs.setLonAccuracyAvailable(false);
     244        } else {
     245            gs.setLonAccuracyAvailable(true);
     246            gs.setLonAccuracySeconds(interpolate(
     247                    lonAccuracy[indexA], lonAccuracy[indexB], lonAccuracy[indexC], lonAccuracy[indexD], X, Y));
     248        }
     249
     250        if (latAccuracy == null) {
     251            gs.setLatAccuracyAvailable(false);
     252        } else {
     253            gs.setLatAccuracyAvailable(true);
     254            gs.setLatAccuracySeconds(interpolate(
     255                    latAccuracy[indexA], latAccuracy[indexB], latAccuracy[indexC], latAccuracy[indexD], X, Y));
     256        }
     257        return gs;
     258    }
     259
     260    public String getParentSubGridName() {
     261        return parentSubGridName;
     262    }
     263
     264    public String getSubGridName() {
     265        return subGridName;
     266    }
     267
     268    public int getNodeCount() {
     269        return nodeCount;
     270    }
     271
     272    public int getSubGridCount() {
     273        return (subGrid == null) ? 0 : subGrid.length;
     274    }
     275
     276    public NTV2SubGrid getSubGrid(int index) {
     277        return (subGrid == null) ? null : subGrid[index];
     278    }
     279
     280    /**
     281     * Set an array of Sub Grids of this sub grid
     282     * @param subGrid
     283     */
     284    public void setSubGridArray(NTV2SubGrid[] subGrid) {
     285        this.subGrid = subGrid;
     286    }
     287
     288    @Override
     289    public String toString() {
     290        return subGridName;
     291    }
     292
     293    public String getDetails() {
     294        StringBuffer buf = new StringBuffer("Sub Grid : ");
     295        buf.append(subGridName);
     296        buf.append("\nParent   : ");
     297        buf.append(parentSubGridName);
     298        buf.append("\nCreated  : ");
     299        buf.append(created);
     300        buf.append("\nUpdated  : ");
     301        buf.append(updated);
     302        buf.append("\nMin Lat  : ");
     303        buf.append(minLat);
     304        buf.append("\nMax Lat  : ");
     305        buf.append(maxLat);
     306        buf.append("\nMin Lon  : ");
     307        buf.append(minLon);
     308        buf.append("\nMax Lon  : ");
     309        buf.append(maxLon);
     310        buf.append("\nLat Intvl: ");
     311        buf.append(latInterval);
     312        buf.append("\nLon Intvl: ");
     313        buf.append(lonInterval);
     314        buf.append("\nNode Cnt : ");
     315        buf.append(nodeCount);
     316        return buf.toString();
     317    }
     318
     319    /**
     320     * Make a deep clone of this Sub Grid
     321     */
     322    @Override
     323    public Object clone() {
     324        NTV2SubGrid clone = null;
     325        try {
     326            clone = (NTV2SubGrid)super.clone();
     327        } catch (CloneNotSupportedException cnse) {
     328        }
     329        // Do a deep clone of the sub grids
     330        if (subGrid != null) {
     331            clone.subGrid = new NTV2SubGrid[subGrid.length];
     332            for (int i = 0; i < subGrid.length; i++) {
     333                clone.subGrid[i] = (NTV2SubGrid)subGrid[i].clone();
     334            }
     335        }
     336        return clone;
     337    }
     338    /**
     339     * @return
     340     */
     341    public double getMaxLat() {
     342        return maxLat;
     343    }
     344
     345    /**
     346     * @return
     347     */
     348    public double getMaxLon() {
     349        return maxLon;
     350    }
     351
     352    /**
     353     * @return
     354     */
     355    public double getMinLat() {
     356        return minLat;
     357    }
     358
     359    /**
     360     * @return
     361     */
     362    public double getMinLon() {
     363        return minLon;
     364    }
     365
     366}
  • NTV2Util.java

     
     1/*
     2 * Copyright (c) 2003 Objectix Pty Ltd  All rights reserved.
     3 *
     4 * This library is free software; you can redistribute it and/or
     5 * modify it under the terms of the GNU Lesser General Public
     6 * License as published by the Free Software Foundation.
     7 *
     8 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
     9 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     10 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     11 * DISCLAIMED.  IN NO EVENT SHALL OBJECTIX PTY LTD BE LIABLE FOR ANY
     12 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     13 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     14 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     15 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     16 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
     17 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
     18 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     19 */
     20package org.openstreetmap.josm.data.projection;
     21
     22/**
     23 * A set of static utility methods for reading the NTv2 file format
     24 *
     25 * @author Peter Yuill
     26 */
     27public class NTV2Util {
     28
     29    private NTV2Util() {
     30    }
     31
     32    /**
     33     * Get a Little Endian int from four bytes of a byte array
     34     * @param b the byte array
     35     * @param i the index of the first data byte in the array
     36     * @return the int
     37     */
     38    public static final int getIntLE(byte[] b, int i) {
     39        return (b[i++] & 0x000000FF) | ((b[i++] << 8) & 0x0000FF00) | ((b[i++] << 16) & 0x00FF0000) | (b[i] << 24);
     40    }
     41
     42    /**
     43     * Get a Big Endian int from four bytes of a byte array
     44     * @param b the byte array
     45     * @param i the index of the first data byte in the array
     46     * @return the int
     47     */
     48    public static final int getIntBE(byte[] b, int i) {
     49        return (b[i++] << 24) | ((b[i++] << 16) & 0x00FF0000) | ((b[i++] << 8) & 0x0000FF00) | (b[i] & 0x000000FF);
     50    }
     51
     52    /**
     53     * Get an int from the first 4 bytes of a byte array,
     54     * in either Big Endian or Little Endian format.
     55     * @param b the byte array
     56     * @param bigEndian is the byte array Big Endian?
     57     * @return the int
     58     */
     59    public static final int getInt(byte[] b, boolean bigEndian) {
     60        if (bigEndian)
     61            return getIntBE(b, 0);
     62        else
     63            return getIntLE(b, 0);
     64    }
     65
     66    /**
     67     * Get a float from the first 4 bytes of a byte array,
     68     * in either Big Endian or Little Endian format.
     69     * @param b the byte array
     70     * @param bigEndian is the byte array Big Endian?
     71     * @return the float
     72     */
     73    public static final float getFloat(byte[] b, boolean bigEndian) {
     74        int i = 0;
     75        if (bigEndian) {
     76            i = getIntBE(b, 0);
     77        } else {
     78            i = getIntLE(b, 0);
     79        }
     80        return Float.intBitsToFloat(i);
     81    }
     82
     83
     84    /**
     85     * Get a double from the first 8 bytes of a byte array,
     86     * in either Big Endian or Little Endian format.
     87     * @param b the byte array
     88     * @param bigEndian is the byte array Big Endian?
     89     * @return the double
     90     */
     91    public static final double getDouble(byte[] b, boolean bigEndian) {
     92        int i = 0;
     93        int j = 0;
     94        if (bigEndian) {
     95            i = getIntBE(b, 0);
     96            j = getIntBE(b, 4);
     97        } else {
     98            i = getIntLE(b, 4);
     99            j = getIntLE(b, 0);
     100        }
     101        long l = ((long)i << 32) |
     102        (j & 0x00000000FFFFFFFFL);
     103        return Double.longBitsToDouble(l);
     104    }
     105
     106    /**
     107     * Does the current VM support the New IO api
     108     * @return true or false
     109     */
     110    public static boolean isNioAvailable() {
     111        boolean nioAvailable = false;
     112        try {
     113            Class.forName("java.nio.channels.FileChannel");
     114            nioAvailable = true;
     115        } catch (ClassNotFoundException cnfe) {}
     116        return nioAvailable;
     117    }
     118}