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