001/* 002 * Copyright (c) 2003 Objectix Pty Ltd All rights reserved. 003 * 004 * This library is free software; you can redistribute it and/or 005 * modify it under the terms of the GNU Lesser General Public 006 * License as published by the Free Software Foundation. 007 * 008 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED 009 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 010 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 011 * DISCLAIMED. IN NO EVENT SHALL OBJECTIX PTY LTD BE LIABLE FOR ANY 012 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 013 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 014 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 015 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 016 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 017 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 018 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 019 */ 020package org.openstreetmap.josm.data.projection.datum; 021 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.Serializable; 025import java.util.ArrayList; 026import java.util.HashMap; 027import java.util.List; 028 029import org.openstreetmap.josm.tools.Utils; 030 031/** 032 * Models the NTv2 format Grid Shift File and exposes methods to shift 033 * coordinate values using the Sub Grids contained in the file. 034 * <p>The principal reference for the alogrithms used is the 035 * 'GDAit Software Architecture Manual' produced by the <a 036 * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics 037 * Department of the University of Melbourne</a> 038 * <p>This library reads binary NTv2 Grid Shift files in Big Endian 039 * (Canadian standard) or Little Endian (Australian Standard) format. 040 * The older 'Australian' binary format is not supported, only the 041 * official Canadian format, which is now also used for the national 042 * Australian Grid. 043 * <p>Grid Shift files can be read as InputStreams or RandomAccessFiles. 044 * Loading an InputStream places all the required node information 045 * (accuracy data is optional) into heap based Java arrays. This is the 046 * highest perfomance option, and is useful for large volume transformations. 047 * Non-file data sources (eg using an SQL Blob) are also supported through 048 * InputStream. The RandonAccessFile option has a much smaller memory 049 * footprint as only the Sub Grid headers are stored in memory, but 050 * transformation is slower because the file must be read a number of 051 * times for each transformation. 052 * <p>Coordinates may be shifted Forward (ie from and to the Datums specified 053 * in the Grid Shift File header) or Reverse. The reverse transformation 054 * uses an iterative approach to approximate the Grid Shift, as the 055 * precise transformation is based on 'from' datum coordinates. 056 * <p>Coordinates may be specified 057 * either in Seconds using Positive West Longitude (the original NTv2 058 * arrangement) or in decimal Degrees using Positive East Longitude. 059 * 060 * @author Peter Yuill 061 * Modifified for JOSM : 062 * - removed the RandomAccessFile mode (Pieren) 063 */ 064public class NTV2GridShiftFile implements Serializable { 065 066 private int overviewHeaderCount; 067 private int subGridHeaderCount; 068 private int subGridCount; 069 private String shiftType; 070 private String version; 071 private String fromEllipsoid = ""; 072 private String toEllipsoid = ""; 073 private double fromSemiMajorAxis; 074 private double fromSemiMinorAxis; 075 private double toSemiMajorAxis; 076 private double toSemiMinorAxis; 077 078 private NTV2SubGrid[] topLevelSubGrid; 079 private NTV2SubGrid lastSubGrid; 080 081 /** 082 * Constructs a new {@code NTV2GridShiftFile}. 083 */ 084 public NTV2GridShiftFile() { 085 } 086 087 /** 088 * Load a Grid Shift File from an InputStream. The Grid Shift node 089 * data is stored in Java arrays, which will occupy about the same memory 090 * as the original file with accuracy data included, and about half that 091 * with accuracy data excluded. The size of the Australian national file 092 * is 4.5MB, and the Canadian national file is 13.5MB 093 * <p>The InputStream is closed by this method. 094 * 095 * @param in Grid Shift File InputStream 096 * @param loadAccuracy is Accuracy data to be loaded as well as shift data? 097 * @throws IOException 098 */ 099 public void loadGridShiftFile(InputStream in, boolean loadAccuracy ) throws IOException { 100 byte[] b8 = new byte[8]; 101 boolean bigEndian = true; 102 fromEllipsoid = ""; 103 toEllipsoid = ""; 104 topLevelSubGrid = null; 105 in.read(b8); 106 String overviewHeaderCountId = new String(b8); 107 if (!"NUM_OREC".equals(overviewHeaderCountId)) 108 throw new IllegalArgumentException("Input file is not an NTv2 grid shift file"); 109 in.read(b8); 110 overviewHeaderCount = NTV2Util.getIntBE(b8, 0); 111 if (overviewHeaderCount == 11) { 112 bigEndian = true; 113 } else { 114 overviewHeaderCount = NTV2Util.getIntLE(b8, 0); 115 if (overviewHeaderCount == 11) { 116 bigEndian = false; 117 } else 118 throw new IllegalArgumentException("Input file is not an NTv2 grid shift file"); 119 } 120 in.read(b8); 121 in.read(b8); 122 subGridHeaderCount = NTV2Util.getInt(b8, bigEndian); 123 in.read(b8); 124 in.read(b8); 125 subGridCount = NTV2Util.getInt(b8, bigEndian); 126 NTV2SubGrid[] subGrid = new NTV2SubGrid[subGridCount]; 127 in.read(b8); 128 in.read(b8); 129 shiftType = new String(b8); 130 in.read(b8); 131 in.read(b8); 132 version = new String(b8); 133 in.read(b8); 134 in.read(b8); 135 fromEllipsoid = new String(b8); 136 in.read(b8); 137 in.read(b8); 138 toEllipsoid = new String(b8); 139 in.read(b8); 140 in.read(b8); 141 fromSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian); 142 in.read(b8); 143 in.read(b8); 144 fromSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian); 145 in.read(b8); 146 in.read(b8); 147 toSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian); 148 in.read(b8); 149 in.read(b8); 150 toSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian); 151 152 for (int i = 0; i < subGridCount; i++) { 153 subGrid[i] = new NTV2SubGrid(in, bigEndian, loadAccuracy); 154 } 155 topLevelSubGrid = createSubGridTree(subGrid); 156 lastSubGrid = topLevelSubGrid[0]; 157 158 Utils.close(in); 159 } 160 161 /** 162 * Create a tree of Sub Grids by adding each Sub Grid to its parent (where 163 * it has one), and returning an array of the top level Sub Grids 164 * @param subGrid an array of all Sub Grids 165 * @return an array of top level Sub Grids with lower level Sub Grids set. 166 */ 167 private NTV2SubGrid[] createSubGridTree(NTV2SubGrid[] subGrid) { 168 int topLevelCount = 0; 169 HashMap<String, List<NTV2SubGrid>> subGridMap = new HashMap<String, List<NTV2SubGrid>>(); 170 for (int i = 0; i < subGrid.length; i++) { 171 if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) { 172 topLevelCount++; 173 } 174 subGridMap.put(subGrid[i].getSubGridName(), new ArrayList<NTV2SubGrid>()); 175 } 176 NTV2SubGrid[] topLevelSubGrid = new NTV2SubGrid[topLevelCount]; 177 topLevelCount = 0; 178 for (int i = 0; i < subGrid.length; i++) { 179 if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) { 180 topLevelSubGrid[topLevelCount++] = subGrid[i]; 181 } else { 182 List<NTV2SubGrid> parent = subGridMap.get(subGrid[i].getParentSubGridName()); 183 parent.add(subGrid[i]); 184 } 185 } 186 NTV2SubGrid[] nullArray = new NTV2SubGrid[0]; 187 for (int i = 0; i < subGrid.length; i++) { 188 List<NTV2SubGrid> subSubGrids = subGridMap.get(subGrid[i].getSubGridName()); 189 if (!subSubGrids.isEmpty()) { 190 NTV2SubGrid[] subGridArray = subSubGrids.toArray(nullArray); 191 subGrid[i].setSubGridArray(subGridArray); 192 } 193 } 194 return topLevelSubGrid; 195 } 196 197 /** 198 * Shift a coordinate in the Forward direction of the Grid Shift File. 199 * 200 * @param gs A GridShift object containing the coordinate to shift 201 * @return True if the coordinate is within a Sub Grid, false if not 202 * @throws IOException 203 */ 204 public boolean gridShiftForward(NTV2GridShift gs) { 205 // Try the last sub grid first, big chance the coord is still within it 206 NTV2SubGrid subGrid = lastSubGrid.getSubGridForCoord(gs.getLonPositiveWestSeconds(), gs.getLatSeconds()); 207 if (subGrid == null) { 208 subGrid = getSubGrid(gs.getLonPositiveWestSeconds(), gs.getLatSeconds()); 209 } 210 if (subGrid == null) 211 return false; 212 else { 213 subGrid.interpolateGridShift(gs); 214 gs.setSubGridName(subGrid.getSubGridName()); 215 lastSubGrid = subGrid; 216 return true; 217 } 218 } 219 220 /** 221 * Shift a coordinate in the Reverse direction of the Grid Shift File. 222 * 223 * @param gs A GridShift object containing the coordinate to shift 224 * @return True if the coordinate is within a Sub Grid, false if not 225 * @throws IOException 226 */ 227 public boolean gridShiftReverse(NTV2GridShift gs) { 228 // set up the first estimate 229 NTV2GridShift forwardGs = new NTV2GridShift(); 230 forwardGs.setLonPositiveWestSeconds(gs.getLonPositiveWestSeconds()); 231 forwardGs.setLatSeconds(gs.getLatSeconds()); 232 for (int i = 0; i < 4; i++) { 233 if (!gridShiftForward(forwardGs)) 234 return false; 235 forwardGs.setLonPositiveWestSeconds( 236 gs.getLonPositiveWestSeconds() - forwardGs.getLonShiftPositiveWestSeconds()); 237 forwardGs.setLatSeconds(gs.getLatSeconds() - forwardGs.getLatShiftSeconds()); 238 } 239 gs.setLonShiftPositiveWestSeconds(-forwardGs.getLonShiftPositiveWestSeconds()); 240 gs.setLatShiftSeconds(-forwardGs.getLatShiftSeconds()); 241 gs.setLonAccuracyAvailable(forwardGs.isLonAccuracyAvailable()); 242 if (forwardGs.isLonAccuracyAvailable()) { 243 gs.setLonAccuracySeconds(forwardGs.getLonAccuracySeconds()); 244 } 245 gs.setLatAccuracyAvailable(forwardGs.isLatAccuracyAvailable()); 246 if (forwardGs.isLatAccuracyAvailable()) { 247 gs.setLatAccuracySeconds(forwardGs.getLatAccuracySeconds()); 248 } 249 return true; 250 } 251 252 /** 253 * Find the finest SubGrid containing the coordinate, specified 254 * in Positive West Seconds 255 * 256 * @param lon Longitude in Positive West Seconds 257 * @param lat Latitude in Seconds 258 * @return The SubGrid found or null 259 */ 260 private NTV2SubGrid getSubGrid(double lon, double lat) { 261 NTV2SubGrid sub = null; 262 for (int i = 0; i < topLevelSubGrid.length; i++) { 263 sub = topLevelSubGrid[i].getSubGridForCoord(lon, lat); 264 if (sub != null) { 265 break; 266 } 267 } 268 return sub; 269 } 270 271 public boolean isLoaded() { 272 return (topLevelSubGrid != null); 273 } 274 275 public void unload() { 276 topLevelSubGrid = null; 277 } 278 279 @Override 280 public String toString() { 281 StringBuffer buf = new StringBuffer("Headers : "); 282 buf.append(overviewHeaderCount); 283 buf.append("\nSub Hdrs : "); 284 buf.append(subGridHeaderCount); 285 buf.append("\nSub Grids: "); 286 buf.append(subGridCount); 287 buf.append("\nType : "); 288 buf.append(shiftType); 289 buf.append("\nVersion : "); 290 buf.append(version); 291 buf.append("\nFr Ellpsd: "); 292 buf.append(fromEllipsoid); 293 buf.append("\nTo Ellpsd: "); 294 buf.append(toEllipsoid); 295 buf.append("\nFr Maj Ax: "); 296 buf.append(fromSemiMajorAxis); 297 buf.append("\nFr Min Ax: "); 298 buf.append(fromSemiMinorAxis); 299 buf.append("\nTo Maj Ax: "); 300 buf.append(toSemiMajorAxis); 301 buf.append("\nTo Min Ax: "); 302 buf.append(toSemiMinorAxis); 303 return buf.toString(); 304 } 305 306 /** 307 * Get a copy of the SubGrid tree for this file. 308 * 309 * @return a deep clone of the current SubGrid tree 310 */ 311 public NTV2SubGrid[] getSubGridTree() { 312 NTV2SubGrid[] clone = new NTV2SubGrid[topLevelSubGrid.length]; 313 for (int i = 0; i < topLevelSubGrid.length; i++) { 314 clone[i] = (NTV2SubGrid)topLevelSubGrid[i].clone(); 315 } 316 return clone; 317 } 318 319 public String getFromEllipsoid() { 320 return fromEllipsoid; 321 } 322 323 public String getToEllipsoid() { 324 return toEllipsoid; 325 } 326 327}