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