[2507] | 1 | /*
|
---|
| 2 | * Copyright (c) 2003 Objectix Pty Ltd All rights reserved.
|
---|
| 3 | *
|
---|
| 4 | * This library is free software; you can redistribute it and/or
|
---|
| 5 | * modify it under the terms of the GNU Lesser General Public
|
---|
| 6 | * License as published by the Free Software Foundation.
|
---|
| 7 | *
|
---|
| 8 | * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
|
---|
| 9 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
---|
| 10 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
---|
| 11 | * DISCLAIMED. IN NO EVENT SHALL OBJECTIX PTY LTD BE LIABLE FOR ANY
|
---|
| 12 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
---|
| 13 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
---|
| 14 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
---|
| 15 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
---|
| 16 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
---|
| 17 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
---|
| 18 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
| 19 | */
|
---|
[5073] | 20 | package org.openstreetmap.josm.data.projection.datum;
|
---|
[2507] | 21 |
|
---|
[2990] | 22 | import java.io.IOException;
|
---|
[2507] | 23 | import java.io.InputStream;
|
---|
| 24 | import java.io.Serializable;
|
---|
[7082] | 25 | import java.nio.charset.StandardCharsets;
|
---|
[2507] | 26 |
|
---|
[6310] | 27 | import org.openstreetmap.josm.Main;
|
---|
[6222] | 28 | import org.openstreetmap.josm.tools.Utils;
|
---|
| 29 |
|
---|
[2507] | 30 | /**
|
---|
| 31 | * Models the NTv2 Sub Grid within a Grid Shift File
|
---|
| 32 | *
|
---|
| 33 | * @author Peter Yuill
|
---|
[6135] | 34 | * Modified for JOSM :
|
---|
[2507] | 35 | * - removed the RandomAccessFile mode (Pieren)
|
---|
| 36 | * - read grid file by single bytes. Workaround for a bug in some VM not supporting
|
---|
| 37 | * file reading by group of 4 bytes from a jar file.
|
---|
| 38 | */
|
---|
| 39 | public class NTV2SubGrid implements Cloneable, Serializable {
|
---|
| 40 |
|
---|
[8308] | 41 | private static final long serialVersionUID = 1L;
|
---|
| 42 |
|
---|
[2507] | 43 | private String subGridName;
|
---|
| 44 | private String parentSubGridName;
|
---|
| 45 | private String created;
|
---|
| 46 | private String updated;
|
---|
| 47 | private double minLat;
|
---|
| 48 | private double maxLat;
|
---|
| 49 | private double minLon;
|
---|
| 50 | private double maxLon;
|
---|
| 51 | private double latInterval;
|
---|
| 52 | private double lonInterval;
|
---|
| 53 | private int nodeCount;
|
---|
| 54 |
|
---|
| 55 | private int lonColumnCount;
|
---|
| 56 | private int latRowCount;
|
---|
| 57 | private float[] latShift;
|
---|
| 58 | private float[] lonShift;
|
---|
| 59 | private float[] latAccuracy;
|
---|
| 60 | private float[] lonAccuracy;
|
---|
| 61 |
|
---|
| 62 | private NTV2SubGrid[] subGrid;
|
---|
| 63 |
|
---|
| 64 | /**
|
---|
| 65 | * Construct a Sub Grid from an InputStream, loading the node data into
|
---|
| 66 | * arrays in this object.
|
---|
| 67 | *
|
---|
| 68 | * @param in GridShiftFile InputStream
|
---|
| 69 | * @param bigEndian is the file bigEndian?
|
---|
| 70 | * @param loadAccuracy is the node Accuracy data to be loaded?
|
---|
[8470] | 71 | * @throws IOException if any I/O error occurs
|
---|
[2507] | 72 | */
|
---|
| 73 | public NTV2SubGrid(InputStream in, boolean bigEndian, boolean loadAccuracy) throws IOException {
|
---|
| 74 | byte[] b8 = new byte[8];
|
---|
| 75 | byte[] b4 = new byte[4];
|
---|
| 76 | byte[] b1 = new byte[1];
|
---|
[8332] | 77 | readBytes(in, b8);
|
---|
| 78 | readBytes(in, b8);
|
---|
[7082] | 79 | subGridName = new String(b8, StandardCharsets.UTF_8).trim();
|
---|
[8332] | 80 | readBytes(in, b8);
|
---|
| 81 | readBytes(in, b8);
|
---|
[7082] | 82 | parentSubGridName = new String(b8, StandardCharsets.UTF_8).trim();
|
---|
[8332] | 83 | readBytes(in, b8);
|
---|
| 84 | readBytes(in, b8);
|
---|
[7082] | 85 | created = new String(b8, StandardCharsets.UTF_8);
|
---|
[8332] | 86 | readBytes(in, b8);
|
---|
| 87 | readBytes(in, b8);
|
---|
[7082] | 88 | updated = new String(b8, StandardCharsets.UTF_8);
|
---|
[8332] | 89 | readBytes(in, b8);
|
---|
| 90 | readBytes(in, b8);
|
---|
[2507] | 91 | minLat = NTV2Util.getDouble(b8, bigEndian);
|
---|
[8332] | 92 | readBytes(in, b8);
|
---|
| 93 | readBytes(in, b8);
|
---|
[2507] | 94 | maxLat = NTV2Util.getDouble(b8, bigEndian);
|
---|
[8332] | 95 | readBytes(in, b8);
|
---|
| 96 | readBytes(in, b8);
|
---|
[2507] | 97 | minLon = NTV2Util.getDouble(b8, bigEndian);
|
---|
[8332] | 98 | readBytes(in, b8);
|
---|
| 99 | readBytes(in, b8);
|
---|
[2507] | 100 | maxLon = NTV2Util.getDouble(b8, bigEndian);
|
---|
[8332] | 101 | readBytes(in, b8);
|
---|
| 102 | readBytes(in, b8);
|
---|
[2507] | 103 | latInterval = NTV2Util.getDouble(b8, bigEndian);
|
---|
[8332] | 104 | readBytes(in, b8);
|
---|
| 105 | readBytes(in, b8);
|
---|
[2507] | 106 | lonInterval = NTV2Util.getDouble(b8, bigEndian);
|
---|
[8510] | 107 | lonColumnCount = 1 + (int) ((maxLon - minLon) / lonInterval);
|
---|
| 108 | latRowCount = 1 + (int) ((maxLat - minLat) / latInterval);
|
---|
[8332] | 109 | readBytes(in, b8);
|
---|
| 110 | readBytes(in, b8);
|
---|
[2507] | 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
|
---|
[8510] | 123 | // certain VM which are not able to read byte blocks when the resource file is in a .jar file (Pieren)
|
---|
[8332] | 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];
|
---|
[2507] | 128 | latShift[i] = NTV2Util.getFloat(b4, bigEndian);
|
---|
[8332] | 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];
|
---|
[2507] | 133 | lonShift[i] = NTV2Util.getFloat(b4, bigEndian);
|
---|
[8332] | 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];
|
---|
[2507] | 138 | if (loadAccuracy) {
|
---|
| 139 | latAccuracy[i] = NTV2Util.getFloat(b4, bigEndian);
|
---|
| 140 | }
|
---|
[8332] | 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];
|
---|
[2507] | 145 | if (loadAccuracy) {
|
---|
| 146 | lonAccuracy[i] = NTV2Util.getFloat(b4, bigEndian);
|
---|
| 147 | }
|
---|
| 148 | }
|
---|
| 149 | }
|
---|
| 150 |
|
---|
[8870] | 151 | private static void readBytes(InputStream in, byte[] b) throws IOException {
|
---|
[8332] | 152 | if (in.read(b) < b.length) {
|
---|
| 153 | Main.error("Failed to read expected amount of bytes ("+ b.length +") from stream");
|
---|
| 154 | }
|
---|
| 155 | }
|
---|
| 156 |
|
---|
[2507] | 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 {
|
---|
[6104] | 175 | for (NTV2SubGrid aSubGrid : subGrid) {
|
---|
| 176 | if (aSubGrid.isCoordWithin(lon, lat))
|
---|
| 177 | return aSubGrid.getSubGridForCoord(lon, lat);
|
---|
[2507] | 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) {
|
---|
[7025] | 195 | return (lon >= minLon) && (lon < maxLon) && (lat >= minLat) && (lat < maxLat);
|
---|
[2507] | 196 | }
|
---|
| 197 |
|
---|
| 198 | /**
|
---|
| 199 | * Bi-Linear interpolation of four nearest node values as described in
|
---|
| 200 | * 'GDAit Software Architecture Manual' produced by the <a
|
---|
[8332] | 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>
|
---|
[2507] | 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
|
---|
[8332] | 207 | * @param x Longitude factor
|
---|
| 208 | * @param y Latitude factor
|
---|
[2507] | 209 | * @return interpolated value
|
---|
| 210 | */
|
---|
[8870] | 211 | private static double interpolate(float a, float b, float c, float d, double x, double y) {
|
---|
[8510] | 212 | return a + (((double) b - (double) a) * x) + (((double) c - (double) a) * y) +
|
---|
| 213 | (((double) a + (double) d - b - c) * x * y);
|
---|
[2507] | 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
|
---|
[8332] | 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>
|
---|
[2507] | 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 | */
|
---|
[8406] | 225 | public void interpolateGridShift(NTV2GridShift gs) {
|
---|
[8510] | 226 | int lonIndex = (int) ((gs.getLonPositiveWestSeconds() - minLon) / lonInterval);
|
---|
| 227 | int latIndex = (int) ((gs.getLatSeconds() - minLat) / latInterval);
|
---|
[2507] | 228 |
|
---|
[8406] | 229 | double x = (gs.getLonPositiveWestSeconds() - (minLon + (lonInterval * lonIndex))) / lonInterval;
|
---|
| 230 | double y = (gs.getLatSeconds() - (minLat + (latInterval * latIndex))) / latInterval;
|
---|
[2507] | 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(
|
---|
[8406] | 240 | lonShift[indexA], lonShift[indexB], lonShift[indexC], lonShift[indexD], x, y));
|
---|
[2507] | 241 |
|
---|
| 242 | gs.setLatShiftSeconds(interpolate(
|
---|
[8406] | 243 | latShift[indexA], latShift[indexB], latShift[indexC], latShift[indexD], x, y));
|
---|
[2507] | 244 |
|
---|
| 245 | if (lonAccuracy == null) {
|
---|
| 246 | gs.setLonAccuracyAvailable(false);
|
---|
| 247 | } else {
|
---|
| 248 | gs.setLonAccuracyAvailable(true);
|
---|
| 249 | gs.setLonAccuracySeconds(interpolate(
|
---|
[8406] | 250 | lonAccuracy[indexA], lonAccuracy[indexB], lonAccuracy[indexC], lonAccuracy[indexD], x, y));
|
---|
[2507] | 251 | }
|
---|
| 252 |
|
---|
| 253 | if (latAccuracy == null) {
|
---|
| 254 | gs.setLatAccuracyAvailable(false);
|
---|
| 255 | } else {
|
---|
| 256 | gs.setLatAccuracyAvailable(true);
|
---|
| 257 | gs.setLatAccuracySeconds(interpolate(
|
---|
[8406] | 258 | latAccuracy[indexA], latAccuracy[indexB], latAccuracy[indexC], latAccuracy[indexD], x, y));
|
---|
[2507] | 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
|
---|
[8470] | 284 | * @param subGrid subgrids
|
---|
[2507] | 285 | */
|
---|
| 286 | public void setSubGridArray(NTV2SubGrid[] subGrid) {
|
---|
[6222] | 287 | this.subGrid = Utils.copyArray(subGrid);
|
---|
[2507] | 288 | }
|
---|
| 289 |
|
---|
| 290 | @Override
|
---|
| 291 | public String toString() {
|
---|
| 292 | return subGridName;
|
---|
| 293 | }
|
---|
| 294 |
|
---|
[6135] | 295 | /**
|
---|
| 296 | * Returns textual details about the sub grid.
|
---|
| 297 | * @return textual details about the sub grid
|
---|
| 298 | */
|
---|
[2507] | 299 | public String getDetails() {
|
---|
[8379] | 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();
|
---|
[2507] | 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 {
|
---|
[8510] | 332 | clone = (NTV2SubGrid) super.clone();
|
---|
[6135] | 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++) {
|
---|
[8510] | 337 | clone.subGrid[i] = (NTV2SubGrid) subGrid[i].clone();
|
---|
[6135] | 338 | }
|
---|
| 339 | }
|
---|
[2507] | 340 | } catch (CloneNotSupportedException cnse) {
|
---|
[6310] | 341 | Main.warn(cnse);
|
---|
[2507] | 342 | }
|
---|
| 343 | return clone;
|
---|
| 344 | }
|
---|
[8510] | 345 |
|
---|
[2507] | 346 | /**
|
---|
[5909] | 347 | * Get maximum latitude value
|
---|
| 348 | * @return maximum latitude
|
---|
[2507] | 349 | */
|
---|
| 350 | public double getMaxLat() {
|
---|
| 351 | return maxLat;
|
---|
| 352 | }
|
---|
| 353 |
|
---|
| 354 | /**
|
---|
[5909] | 355 | * Get maximum longitude value
|
---|
| 356 | * @return maximum longitude
|
---|
[2507] | 357 | */
|
---|
| 358 | public double getMaxLon() {
|
---|
| 359 | return maxLon;
|
---|
| 360 | }
|
---|
| 361 |
|
---|
| 362 | /**
|
---|
[5909] | 363 | * Get minimum latitude value
|
---|
| 364 | * @return minimum latitude
|
---|
[2507] | 365 | */
|
---|
| 366 | public double getMinLat() {
|
---|
| 367 | return minLat;
|
---|
| 368 | }
|
---|
| 369 |
|
---|
| 370 | /**
|
---|
[5909] | 371 | * Get minimum longitude value
|
---|
| 372 | * @return minimum longitude
|
---|
[2507] | 373 | */
|
---|
| 374 | public double getMinLon() {
|
---|
| 375 | return minLon;
|
---|
| 376 | }
|
---|
| 377 | }
|
---|