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 */ 020 package org.openstreetmap.josm.data.projection.datum; 021 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.io.Serializable; 025 026 /** 027 * Models the NTv2 Sub Grid within a Grid Shift File 028 * 029 * @author Peter Yuill 030 * Modifified for JOSM : 031 * - removed the RandomAccessFile mode (Pieren) 032 * - read grid file by single bytes. Workaround for a bug in some VM not supporting 033 * file reading by group of 4 bytes from a jar file. 034 */ 035 public class NTV2SubGrid implements Cloneable, Serializable { 036 037 private String subGridName; 038 private String parentSubGridName; 039 private String created; 040 private String updated; 041 private double minLat; 042 private double maxLat; 043 private double minLon; 044 private double maxLon; 045 private double latInterval; 046 private double lonInterval; 047 private int nodeCount; 048 049 private int lonColumnCount; 050 private int latRowCount; 051 private float[] latShift; 052 private float[] lonShift; 053 private float[] latAccuracy; 054 private float[] lonAccuracy; 055 056 boolean bigEndian; 057 private NTV2SubGrid[] subGrid; 058 059 /** 060 * Construct a Sub Grid from an InputStream, loading the node data into 061 * arrays in this object. 062 * 063 * @param in GridShiftFile InputStream 064 * @param bigEndian is the file bigEndian? 065 * @param loadAccuracy is the node Accuracy data to be loaded? 066 * @throws Exception 067 */ 068 public NTV2SubGrid(InputStream in, boolean bigEndian, boolean loadAccuracy) throws IOException { 069 byte[] b8 = new byte[8]; 070 byte[] b4 = new byte[4]; 071 byte[] b1 = new byte[1]; 072 in.read(b8); 073 in.read(b8); 074 subGridName = new String(b8).trim(); 075 in.read(b8); 076 in.read(b8); 077 parentSubGridName = new String(b8).trim(); 078 in.read(b8); 079 in.read(b8); 080 created = new String(b8); 081 in.read(b8); 082 in.read(b8); 083 updated = new String(b8); 084 in.read(b8); 085 in.read(b8); 086 minLat = NTV2Util.getDouble(b8, bigEndian); 087 in.read(b8); 088 in.read(b8); 089 maxLat = NTV2Util.getDouble(b8, bigEndian); 090 in.read(b8); 091 in.read(b8); 092 minLon = NTV2Util.getDouble(b8, bigEndian); 093 in.read(b8); 094 in.read(b8); 095 maxLon = NTV2Util.getDouble(b8, bigEndian); 096 in.read(b8); 097 in.read(b8); 098 latInterval = NTV2Util.getDouble(b8, bigEndian); 099 in.read(b8); 100 in.read(b8); 101 lonInterval = NTV2Util.getDouble(b8, bigEndian); 102 lonColumnCount = 1 + (int)((maxLon - minLon) / lonInterval); 103 latRowCount = 1 + (int)((maxLat - minLat) / latInterval); 104 in.read(b8); 105 in.read(b8); 106 nodeCount = NTV2Util.getInt(b8, bigEndian); 107 if (nodeCount != lonColumnCount * latRowCount) 108 throw new IllegalStateException("SubGrid " + subGridName + " has inconsistent grid dimesions"); 109 latShift = new float[nodeCount]; 110 lonShift = new float[nodeCount]; 111 if (loadAccuracy) { 112 latAccuracy = new float[nodeCount]; 113 lonAccuracy = new float[nodeCount]; 114 } 115 116 for (int i = 0; i < nodeCount; i++) { 117 // Read the grid file byte after byte. This is a workaround about a bug in 118 // certain VM which are not able to read byte blocks when the resource file is 119 // in a .jar file (Pieren) 120 in.read(b1); b4[0] = b1[0]; 121 in.read(b1); b4[1] = b1[0]; 122 in.read(b1); b4[2] = b1[0]; 123 in.read(b1); b4[3] = b1[0]; 124 latShift[i] = NTV2Util.getFloat(b4, bigEndian); 125 in.read(b1); b4[0] = b1[0]; 126 in.read(b1); b4[1] = b1[0]; 127 in.read(b1); b4[2] = b1[0]; 128 in.read(b1); b4[3] = b1[0]; 129 lonShift[i] = NTV2Util.getFloat(b4, bigEndian); 130 in.read(b1); b4[0] = b1[0]; 131 in.read(b1); b4[1] = b1[0]; 132 in.read(b1); b4[2] = b1[0]; 133 in.read(b1); b4[3] = b1[0]; 134 if (loadAccuracy) { 135 latAccuracy[i] = NTV2Util.getFloat(b4, bigEndian); 136 } 137 in.read(b1); b4[0] = b1[0]; 138 in.read(b1); b4[1] = b1[0]; 139 in.read(b1); b4[2] = b1[0]; 140 in.read(b1); b4[3] = b1[0]; 141 if (loadAccuracy) { 142 lonAccuracy[i] = NTV2Util.getFloat(b4, bigEndian); 143 } 144 } 145 } 146 147 /** 148 * Tests if a specified coordinate is within this Sub Grid 149 * or one of its Sub Grids. If the coordinate is outside 150 * this Sub Grid, null is returned. If the coordinate is 151 * within this Sub Grid, but not within any of its Sub Grids, 152 * this Sub Grid is returned. If the coordinate is within 153 * one of this Sub Grid's Sub Grids, the method is called 154 * recursively on the child Sub Grid. 155 * 156 * @param lon Longitude in Positive West Seconds 157 * @param lat Latitude in Seconds 158 * @return the Sub Grid containing the Coordinate or null 159 */ 160 public NTV2SubGrid getSubGridForCoord(double lon, double lat) { 161 if (isCoordWithin(lon, lat)) { 162 if (subGrid == null) 163 return this; 164 else { 165 for (int i = 0; i < subGrid.length; i++) { 166 if (subGrid[i].isCoordWithin(lon, lat)) 167 return subGrid[i].getSubGridForCoord(lon, lat); 168 } 169 return this; 170 } 171 } else 172 return null; 173 } 174 175 /** 176 * Tests if a specified coordinate is within this Sub Grid. 177 * A coordinate on either outer edge (maximum Latitude or 178 * maximum Longitude) is deemed to be outside the grid. 179 * 180 * @param lon Longitude in Positive West Seconds 181 * @param lat Latitude in Seconds 182 * @return true or false 183 */ 184 private boolean isCoordWithin(double lon, double lat) { 185 if ((lon >= minLon) && (lon < maxLon) && (lat >= minLat) && (lat < maxLat)) 186 return true; 187 else 188 return false; 189 } 190 191 /** 192 * Bi-Linear interpolation of four nearest node values as described in 193 * 'GDAit Software Architecture Manual' produced by the <a 194 * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics 195 * Department of the University of Melbourne</a> 196 * @param a value at the A node 197 * @param b value at the B node 198 * @param c value at the C node 199 * @param d value at the D node 200 * @param X Longitude factor 201 * @param Y Latitude factor 202 * @return interpolated value 203 */ 204 private final double interpolate(float a, float b, float c, float d, double X, double Y) { 205 return a + (((double)b - (double)a) * X) + (((double)c - (double)a) * Y) + 206 (((double)a + (double)d - b - c) * X * Y); 207 } 208 209 /** 210 * Interpolate shift and accuracy values for a coordinate in the 'from' datum 211 * of the GridShiftFile. The algorithm is described in 212 * 'GDAit Software Architecture Manual' produced by the <a 213 * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics 214 * Department of the University of Melbourne</a> 215 * <p>This method is thread safe for both memory based and file based node data. 216 * @param gs GridShift object containing the coordinate to shift and the shift values 217 * @return the GridShift object supplied, with values updated. 218 * @throws IOException 219 */ 220 public NTV2GridShift interpolateGridShift(NTV2GridShift gs) { 221 int lonIndex = (int)((gs.getLonPositiveWestSeconds() - minLon) / lonInterval); 222 int latIndex = (int)((gs.getLatSeconds() - minLat) / latInterval); 223 224 double X = (gs.getLonPositiveWestSeconds() - (minLon + (lonInterval * lonIndex))) / lonInterval; 225 double Y = (gs.getLatSeconds() - (minLat + (latInterval * latIndex))) / latInterval; 226 227 // Find the nodes at the four corners of the cell 228 229 int indexA = lonIndex + (latIndex * lonColumnCount); 230 int indexB = indexA + 1; 231 int indexC = indexA + lonColumnCount; 232 int indexD = indexC + 1; 233 234 gs.setLonShiftPositiveWestSeconds(interpolate( 235 lonShift[indexA], lonShift[indexB], lonShift[indexC], lonShift[indexD], X, Y)); 236 237 gs.setLatShiftSeconds(interpolate( 238 latShift[indexA], latShift[indexB], latShift[indexC], latShift[indexD], X, Y)); 239 240 if (lonAccuracy == null) { 241 gs.setLonAccuracyAvailable(false); 242 } else { 243 gs.setLonAccuracyAvailable(true); 244 gs.setLonAccuracySeconds(interpolate( 245 lonAccuracy[indexA], lonAccuracy[indexB], lonAccuracy[indexC], lonAccuracy[indexD], X, Y)); 246 } 247 248 if (latAccuracy == null) { 249 gs.setLatAccuracyAvailable(false); 250 } else { 251 gs.setLatAccuracyAvailable(true); 252 gs.setLatAccuracySeconds(interpolate( 253 latAccuracy[indexA], latAccuracy[indexB], latAccuracy[indexC], latAccuracy[indexD], X, Y)); 254 } 255 return gs; 256 } 257 258 public String getParentSubGridName() { 259 return parentSubGridName; 260 } 261 262 public String getSubGridName() { 263 return subGridName; 264 } 265 266 public int getNodeCount() { 267 return nodeCount; 268 } 269 270 public int getSubGridCount() { 271 return (subGrid == null) ? 0 : subGrid.length; 272 } 273 274 public NTV2SubGrid getSubGrid(int index) { 275 return (subGrid == null) ? null : subGrid[index]; 276 } 277 278 /** 279 * Set an array of Sub Grids of this sub grid 280 * @param subGrid 281 */ 282 public void setSubGridArray(NTV2SubGrid[] subGrid) { 283 this.subGrid = subGrid; 284 } 285 286 @Override 287 public String toString() { 288 return subGridName; 289 } 290 291 public String getDetails() { 292 StringBuffer buf = new StringBuffer("Sub Grid : "); 293 buf.append(subGridName); 294 buf.append("\nParent : "); 295 buf.append(parentSubGridName); 296 buf.append("\nCreated : "); 297 buf.append(created); 298 buf.append("\nUpdated : "); 299 buf.append(updated); 300 buf.append("\nMin Lat : "); 301 buf.append(minLat); 302 buf.append("\nMax Lat : "); 303 buf.append(maxLat); 304 buf.append("\nMin Lon : "); 305 buf.append(minLon); 306 buf.append("\nMax Lon : "); 307 buf.append(maxLon); 308 buf.append("\nLat Intvl: "); 309 buf.append(latInterval); 310 buf.append("\nLon Intvl: "); 311 buf.append(lonInterval); 312 buf.append("\nNode Cnt : "); 313 buf.append(nodeCount); 314 return buf.toString(); 315 } 316 317 /** 318 * Make a deep clone of this Sub Grid 319 */ 320 @Override 321 public Object clone() { 322 NTV2SubGrid clone = null; 323 try { 324 clone = (NTV2SubGrid)super.clone(); 325 } catch (CloneNotSupportedException cnse) { 326 } 327 // Do a deep clone of the sub grids 328 if (subGrid != null) { 329 clone.subGrid = new NTV2SubGrid[subGrid.length]; 330 for (int i = 0; i < subGrid.length; i++) { 331 clone.subGrid[i] = (NTV2SubGrid)subGrid[i].clone(); 332 } 333 } 334 return clone; 335 } 336 /** 337 * @return 338 */ 339 public double getMaxLat() { 340 return maxLat; 341 } 342 343 /** 344 * @return 345 */ 346 public double getMaxLon() { 347 return maxLon; 348 } 349 350 /** 351 * @return 352 */ 353 public double getMinLat() { 354 return minLat; 355 } 356 357 /** 358 * @return 359 */ 360 public double getMinLon() { 361 return minLon; 362 } 363 364 }