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