/* * 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. *

The principal reference for the alogrithms used is the * 'GDAit Software Architecture Manual' produced by the Geomatics * Department of the University of Melbourne *

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. *

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. *

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. *

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 *

The InputStream is closed by this method. * * @param in Grid Shift File InputStream * @param loadAccuracy is Accuracy data to be loaded as well as shift data? * @throws Exception */ public void loadGridShiftFile(InputStream in, boolean loadAccuracy ) throws IOException { byte[] b8 = new byte[8]; boolean bigEndian = true; fromEllipsoid = ""; toEllipsoid = ""; topLevelSubGrid = null; in.read(b8); overviewHeaderCountId = new String(b8); if (!"NUM_OREC".equals(overviewHeaderCountId)) throw new IllegalArgumentException("Input file is not an NTv2 grid shift file"); in.read(b8); overviewHeaderCount = NTV2Util.getIntBE(b8, 0); if (overviewHeaderCount == 11) { bigEndian = true; } else { overviewHeaderCount = NTV2Util.getIntLE(b8, 0); if (overviewHeaderCount == 11) { bigEndian = false; } else throw new IllegalArgumentException("Input file is not an NTv2 grid shift file"); } in.read(b8); in.read(b8); subGridHeaderCount = NTV2Util.getInt(b8, bigEndian); in.read(b8); in.read(b8); subGridCount = NTV2Util.getInt(b8, bigEndian); NTV2SubGrid[] subGrid = new NTV2SubGrid[subGridCount]; in.read(b8); in.read(b8); shiftType = new String(b8); in.read(b8); in.read(b8); version = new String(b8); in.read(b8); in.read(b8); fromEllipsoid = new String(b8); in.read(b8); in.read(b8); toEllipsoid = new String(b8); in.read(b8); in.read(b8); fromSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian); in.read(b8); in.read(b8); fromSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian); in.read(b8); in.read(b8); toSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian); in.read(b8); in.read(b8); toSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian); for (int i = 0; i < subGridCount; i++) { subGrid[i] = new NTV2SubGrid(in, bigEndian, loadAccuracy); } topLevelSubGrid = createSubGridTree(subGrid); lastSubGrid = topLevelSubGrid[0]; in.close(); } /** * Create a tree of Sub Grids by adding each Sub Grid to its parent (where * it has one), and returning an array of the top level Sub Grids * @param subGrid an array of all Sub Grids * @return an array of top level Sub Grids with lower level Sub Grids set. */ private NTV2SubGrid[] createSubGridTree(NTV2SubGrid[] subGrid) { int topLevelCount = 0; HashMap> subGridMap = new HashMap>(); for (int i = 0; i < subGrid.length; i++) { if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) { topLevelCount++; } subGridMap.put(subGrid[i].getSubGridName(), new ArrayList()); } NTV2SubGrid[] topLevelSubGrid = new NTV2SubGrid[topLevelCount]; topLevelCount = 0; for (int i = 0; i < subGrid.length; i++) { if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) { topLevelSubGrid[topLevelCount++] = subGrid[i]; } else { ArrayList parent = subGridMap.get(subGrid[i].getParentSubGridName()); parent.add(subGrid[i]); } } NTV2SubGrid[] nullArray = new NTV2SubGrid[0]; for (int i = 0; i < subGrid.length; i++) { ArrayList 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; } }