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}