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