Ticket #3987: NTV2Grid.patch
File NTV2Grid.patch, 38.2 KB (added by , 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 */ 20 package org.openstreetmap.josm.data.projection; 21 22 import java.io.Serializable; 23 24 import 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 */ 37 public 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 */ 20 package org.openstreetmap.josm.data.projection; 21 22 import java.io.InputStream; 23 import java.io.IOException; 24 import java.io.RandomAccessFile; 25 import java.io.Serializable; 26 import java.util.ArrayList; 27 import 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 */ 62 public 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 */ 20 package org.openstreetmap.josm.data.projection; 21 22 import java.io.InputStream; 23 import java.io.IOException; 24 import 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 */ 35 public 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 */ 20 package 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 */ 27 public 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 }