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     */
020    package org.openstreetmap.josm.data.projection.datum;
021    
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.Serializable;
025    
026    /**
027     * Models the NTv2 Sub Grid within a Grid Shift File
028     *
029     * @author Peter Yuill
030     * Modifified for JOSM :
031     * - removed the RandomAccessFile mode (Pieren)
032     * - read grid file by single bytes. Workaround for a bug in some VM not supporting
033     *   file reading by group of 4 bytes from a jar file.
034     */
035    public class NTV2SubGrid implements Cloneable, Serializable {
036    
037        private String subGridName;
038        private String parentSubGridName;
039        private String created;
040        private String updated;
041        private double minLat;
042        private double maxLat;
043        private double minLon;
044        private double maxLon;
045        private double latInterval;
046        private double lonInterval;
047        private int nodeCount;
048    
049        private int lonColumnCount;
050        private int latRowCount;
051        private float[] latShift;
052        private float[] lonShift;
053        private float[] latAccuracy;
054        private float[] lonAccuracy;
055    
056        boolean bigEndian;
057        private NTV2SubGrid[] subGrid;
058    
059        /**
060         * Construct a Sub Grid from an InputStream, loading the node data into
061         * arrays in this object.
062         *
063         * @param in GridShiftFile InputStream
064         * @param bigEndian is the file bigEndian?
065         * @param loadAccuracy is the node Accuracy data to be loaded?
066         * @throws Exception
067         */
068        public NTV2SubGrid(InputStream in, boolean bigEndian, boolean loadAccuracy) throws IOException {
069            byte[] b8 = new byte[8];
070            byte[] b4 = new byte[4];
071            byte[] b1 = new byte[1];
072            in.read(b8);
073            in.read(b8);
074            subGridName = new String(b8).trim();
075            in.read(b8);
076            in.read(b8);
077            parentSubGridName = new String(b8).trim();
078            in.read(b8);
079            in.read(b8);
080            created = new String(b8);
081            in.read(b8);
082            in.read(b8);
083            updated = new String(b8);
084            in.read(b8);
085            in.read(b8);
086            minLat = NTV2Util.getDouble(b8, bigEndian);
087            in.read(b8);
088            in.read(b8);
089            maxLat = NTV2Util.getDouble(b8, bigEndian);
090            in.read(b8);
091            in.read(b8);
092            minLon = NTV2Util.getDouble(b8, bigEndian);
093            in.read(b8);
094            in.read(b8);
095            maxLon = NTV2Util.getDouble(b8, bigEndian);
096            in.read(b8);
097            in.read(b8);
098            latInterval = NTV2Util.getDouble(b8, bigEndian);
099            in.read(b8);
100            in.read(b8);
101            lonInterval = NTV2Util.getDouble(b8, bigEndian);
102            lonColumnCount = 1 + (int)((maxLon - minLon) / lonInterval);
103            latRowCount = 1 + (int)((maxLat - minLat) / latInterval);
104            in.read(b8);
105            in.read(b8);
106            nodeCount = NTV2Util.getInt(b8, bigEndian);
107            if (nodeCount != lonColumnCount * latRowCount)
108                throw new IllegalStateException("SubGrid " + subGridName + " has inconsistent grid dimesions");
109            latShift = new float[nodeCount];
110            lonShift = new float[nodeCount];
111            if (loadAccuracy) {
112                latAccuracy = new float[nodeCount];
113                lonAccuracy = new float[nodeCount];
114            }
115    
116            for (int i = 0; i < nodeCount; i++) {
117                // Read the grid file byte after byte. This is a workaround about a bug in
118                // certain VM which are not able to read byte blocks when the resource file is
119                // in a .jar file (Pieren)
120                in.read(b1); b4[0] = b1[0];
121                in.read(b1); b4[1] = b1[0];
122                in.read(b1); b4[2] = b1[0];
123                in.read(b1); b4[3] = b1[0];
124                latShift[i] = NTV2Util.getFloat(b4, bigEndian);
125                in.read(b1); b4[0] = b1[0];
126                in.read(b1); b4[1] = b1[0];
127                in.read(b1); b4[2] = b1[0];
128                in.read(b1); b4[3] = b1[0];
129                lonShift[i] = NTV2Util.getFloat(b4, bigEndian);
130                in.read(b1); b4[0] = b1[0];
131                in.read(b1); b4[1] = b1[0];
132                in.read(b1); b4[2] = b1[0];
133                in.read(b1); b4[3] = b1[0];
134                if (loadAccuracy) {
135                    latAccuracy[i] = NTV2Util.getFloat(b4, bigEndian);
136                }
137                in.read(b1); b4[0] = b1[0];
138                in.read(b1); b4[1] = b1[0];
139                in.read(b1); b4[2] = b1[0];
140                in.read(b1); b4[3] = b1[0];
141                if (loadAccuracy) {
142                    lonAccuracy[i] = NTV2Util.getFloat(b4, bigEndian);
143                }
144            }
145        }
146    
147        /**
148         * Tests if a specified coordinate is within this Sub Grid
149         * or one of its Sub Grids. If the coordinate is outside
150         * this Sub Grid, null is returned. If the coordinate is
151         * within this Sub Grid, but not within any of its Sub Grids,
152         * this Sub Grid is returned. If the coordinate is within
153         * one of this Sub Grid's Sub Grids, the method is called
154         * recursively on the child Sub Grid.
155         *
156         * @param lon Longitude in Positive West Seconds
157         * @param lat Latitude in Seconds
158         * @return the Sub Grid containing the Coordinate or null
159         */
160        public NTV2SubGrid getSubGridForCoord(double lon, double lat) {
161            if (isCoordWithin(lon, lat)) {
162                if (subGrid == null)
163                    return this;
164                else {
165                    for (int i = 0; i < subGrid.length; i++) {
166                        if (subGrid[i].isCoordWithin(lon, lat))
167                            return subGrid[i].getSubGridForCoord(lon, lat);
168                    }
169                    return this;
170                }
171            } else
172                return null;
173        }
174    
175        /**
176         * Tests if a specified coordinate is within this Sub Grid.
177         * A coordinate on either outer edge (maximum Latitude or
178         * maximum Longitude) is deemed to be outside the grid.
179         *
180         * @param lon Longitude in Positive West Seconds
181         * @param lat Latitude in Seconds
182         * @return true or false
183         */
184        private boolean isCoordWithin(double lon, double lat) {
185            if ((lon >= minLon) && (lon < maxLon) && (lat >= minLat) && (lat < maxLat))
186                return true;
187            else
188                return false;
189        }
190    
191        /**
192         * Bi-Linear interpolation of four nearest node values as described in
193         * 'GDAit Software Architecture Manual' produced by the <a
194         * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
195         * Department of the University of Melbourne</a>
196         * @param a value at the A node
197         * @param b value at the B node
198         * @param c value at the C node
199         * @param d value at the D node
200         * @param X Longitude factor
201         * @param Y Latitude factor
202         * @return interpolated value
203         */
204        private final double interpolate(float a, float b, float c, float d, double X, double Y) {
205            return a + (((double)b - (double)a) * X) + (((double)c - (double)a) * Y) +
206            (((double)a + (double)d - b - c) * X * Y);
207        }
208    
209        /**
210         * Interpolate shift and accuracy values for a coordinate in the 'from' datum
211         * of the GridShiftFile. The algorithm is described in
212         * 'GDAit Software Architecture Manual' produced by the <a
213         * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
214         * Department of the University of Melbourne</a>
215         * <p>This method is thread safe for both memory based and file based node data.
216         * @param gs GridShift object containing the coordinate to shift and the shift values
217         * @return the GridShift object supplied, with values updated.
218         * @throws IOException
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 = subGrid;
284        }
285    
286        @Override
287        public String toString() {
288            return subGridName;
289        }
290    
291        public String getDetails() {
292            StringBuffer buf = new StringBuffer("Sub Grid : ");
293            buf.append(subGridName);
294            buf.append("\nParent   : ");
295            buf.append(parentSubGridName);
296            buf.append("\nCreated  : ");
297            buf.append(created);
298            buf.append("\nUpdated  : ");
299            buf.append(updated);
300            buf.append("\nMin Lat  : ");
301            buf.append(minLat);
302            buf.append("\nMax Lat  : ");
303            buf.append(maxLat);
304            buf.append("\nMin Lon  : ");
305            buf.append(minLon);
306            buf.append("\nMax Lon  : ");
307            buf.append(maxLon);
308            buf.append("\nLat Intvl: ");
309            buf.append(latInterval);
310            buf.append("\nLon Intvl: ");
311            buf.append(lonInterval);
312            buf.append("\nNode Cnt : ");
313            buf.append(nodeCount);
314            return buf.toString();
315        }
316    
317        /**
318         * Make a deep clone of this Sub Grid
319         */
320        @Override
321        public Object clone() {
322            NTV2SubGrid clone = null;
323            try {
324                clone = (NTV2SubGrid)super.clone();
325            } catch (CloneNotSupportedException cnse) {
326            }
327            // Do a deep clone of the sub grids
328            if (subGrid != null) {
329                clone.subGrid = new NTV2SubGrid[subGrid.length];
330                for (int i = 0; i < subGrid.length; i++) {
331                    clone.subGrid[i] = (NTV2SubGrid)subGrid[i].clone();
332                }
333            }
334            return clone;
335        }
336        /**
337         * @return
338         */
339        public double getMaxLat() {
340            return maxLat;
341        }
342    
343        /**
344         * @return
345         */
346        public double getMaxLon() {
347            return maxLon;
348        }
349    
350        /**
351         * @return
352         */
353        public double getMinLat() {
354            return minLat;
355        }
356    
357        /**
358         * @return
359         */
360        public double getMinLon() {
361            return minLon;
362        }
363    
364    }