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}