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    import java.util.ArrayList;
026    import java.util.HashMap;
027    
028    /**
029     * Models the NTv2 format Grid Shift File and exposes methods to shift
030     * coordinate values using the Sub Grids contained in the file.
031     * <p>The principal reference for the alogrithms used is the
032     * 'GDAit Software Architecture Manual' produced by the <a
033     * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
034     * Department of the University of Melbourne</a>
035     * <p>This library reads binary NTv2 Grid Shift files in Big Endian
036     * (Canadian standard) or Little Endian (Australian Standard) format.
037     * The older 'Australian' binary format is not supported, only the
038     * official Canadian format, which is now also used for the national
039     * Australian Grid.
040     * <p>Grid Shift files can be read as InputStreams or RandomAccessFiles.
041     * Loading an InputStream places all the required node information
042     * (accuracy data is optional) into heap based Java arrays. This is the
043     * highest perfomance option, and is useful for large volume transformations.
044     * Non-file data sources (eg using an SQL Blob) are also supported through
045     * InputStream. The RandonAccessFile option has a much smaller memory
046     * footprint as only the Sub Grid headers are stored in memory, but
047     * transformation is slower because the file must be read a number of
048     * times for each transformation.
049     * <p>Coordinates may be shifted Forward (ie from and to the Datums specified
050     * in the Grid Shift File header) or Reverse. The reverse transformation
051     * uses an iterative approach to approximate the Grid Shift, as the
052     * precise transformation is based on 'from' datum coordinates.
053     * <p>Coordinates may be specified
054     * either in Seconds using Positive West Longitude (the original NTv2
055     * arrangement) or in decimal Degrees using Positive East Longitude.
056     *
057     * @author Peter Yuill
058     * Modifified for JOSM :
059     * - removed the RandomAccessFile mode (Pieren)
060     */
061    public class NTV2GridShiftFile implements Serializable {
062    
063        private String overviewHeaderCountId;
064        private int overviewHeaderCount;
065        private int subGridHeaderCount;
066        private int subGridCount;
067        private String shiftType;
068        private String version;
069        private String fromEllipsoid = "";
070        private String toEllipsoid = "";
071        private double fromSemiMajorAxis;
072        private double fromSemiMinorAxis;
073        private double toSemiMajorAxis;
074        private double toSemiMinorAxis;
075    
076        private NTV2SubGrid[] topLevelSubGrid;
077        private NTV2SubGrid lastSubGrid;
078    
079        public NTV2GridShiftFile() {
080        }
081    
082        /**
083         * Load a Grid Shift File from an InputStream. The Grid Shift node
084         * data is stored in Java arrays, which will occupy about the same memory
085         * as the original file with accuracy data included, and about half that
086         * with accuracy data excluded. The size of the Australian national file
087         * is 4.5MB, and the Canadian national file is 13.5MB
088         * <p>The InputStream is closed by this method.
089         *
090         * @param in Grid Shift File InputStream
091         * @param loadAccuracy is Accuracy data to be loaded as well as shift data?
092         * @throws Exception
093         */
094        public void loadGridShiftFile(InputStream in, boolean loadAccuracy ) throws IOException {
095            byte[] b8 = new byte[8];
096            boolean bigEndian = true;
097            fromEllipsoid = "";
098            toEllipsoid = "";
099            topLevelSubGrid = null;
100            in.read(b8);
101            overviewHeaderCountId = new String(b8);
102            if (!"NUM_OREC".equals(overviewHeaderCountId))
103                throw new IllegalArgumentException("Input file is not an NTv2 grid shift file");
104            in.read(b8);
105            overviewHeaderCount = NTV2Util.getIntBE(b8, 0);
106            if (overviewHeaderCount == 11) {
107                bigEndian = true;
108            } else {
109                overviewHeaderCount = NTV2Util.getIntLE(b8, 0);
110                if (overviewHeaderCount == 11) {
111                    bigEndian = false;
112                } else
113                    throw new IllegalArgumentException("Input file is not an NTv2 grid shift file");
114            }
115            in.read(b8);
116            in.read(b8);
117            subGridHeaderCount = NTV2Util.getInt(b8, bigEndian);
118            in.read(b8);
119            in.read(b8);
120            subGridCount = NTV2Util.getInt(b8, bigEndian);
121            NTV2SubGrid[] subGrid = new NTV2SubGrid[subGridCount];
122            in.read(b8);
123            in.read(b8);
124            shiftType = new String(b8);
125            in.read(b8);
126            in.read(b8);
127            version = new String(b8);
128            in.read(b8);
129            in.read(b8);
130            fromEllipsoid = new String(b8);
131            in.read(b8);
132            in.read(b8);
133            toEllipsoid = new String(b8);
134            in.read(b8);
135            in.read(b8);
136            fromSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian);
137            in.read(b8);
138            in.read(b8);
139            fromSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian);
140            in.read(b8);
141            in.read(b8);
142            toSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian);
143            in.read(b8);
144            in.read(b8);
145            toSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian);
146    
147            for (int i = 0; i < subGridCount; i++) {
148                subGrid[i] = new NTV2SubGrid(in, bigEndian, loadAccuracy);
149            }
150            topLevelSubGrid = createSubGridTree(subGrid);
151            lastSubGrid = topLevelSubGrid[0];
152    
153            in.close();
154        }
155    
156        /**
157         * Create a tree of Sub Grids by adding each Sub Grid to its parent (where
158         * it has one), and returning an array of the top level Sub Grids
159         * @param subGrid an array of all Sub Grids
160         * @return an array of top level Sub Grids with lower level Sub Grids set.
161         */
162        private NTV2SubGrid[] createSubGridTree(NTV2SubGrid[] subGrid) {
163            int topLevelCount = 0;
164            HashMap<String, ArrayList<NTV2SubGrid>> subGridMap = new HashMap<String, ArrayList<NTV2SubGrid>>();
165            for (int i = 0; i < subGrid.length; i++) {
166                if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) {
167                    topLevelCount++;
168                }
169                subGridMap.put(subGrid[i].getSubGridName(), new ArrayList<NTV2SubGrid>());
170            }
171            NTV2SubGrid[] topLevelSubGrid = new NTV2SubGrid[topLevelCount];
172            topLevelCount = 0;
173            for (int i = 0; i < subGrid.length; i++) {
174                if (subGrid[i].getParentSubGridName().equalsIgnoreCase("NONE")) {
175                    topLevelSubGrid[topLevelCount++] = subGrid[i];
176                } else {
177                    ArrayList<NTV2SubGrid> parent = subGridMap.get(subGrid[i].getParentSubGridName());
178                    parent.add(subGrid[i]);
179                }
180            }
181            NTV2SubGrid[] nullArray = new NTV2SubGrid[0];
182            for (int i = 0; i < subGrid.length; i++) {
183                ArrayList<NTV2SubGrid> subSubGrids = subGridMap.get(subGrid[i].getSubGridName());
184                if (subSubGrids.size() > 0) {
185                    NTV2SubGrid[] subGridArray = subSubGrids.toArray(nullArray);
186                    subGrid[i].setSubGridArray(subGridArray);
187                }
188            }
189            return topLevelSubGrid;
190        }
191    
192        /**
193         * Shift a coordinate in the Forward direction of the Grid Shift File.
194         *
195         * @param gs A GridShift object containing the coordinate to shift
196         * @return True if the coordinate is within a Sub Grid, false if not
197         * @throws IOException
198         */
199        public boolean gridShiftForward(NTV2GridShift gs) {
200            // Try the last sub grid first, big chance the coord is still within it
201            NTV2SubGrid subGrid = lastSubGrid.getSubGridForCoord(gs.getLonPositiveWestSeconds(), gs.getLatSeconds());
202            if (subGrid == null) {
203                subGrid = getSubGrid(gs.getLonPositiveWestSeconds(), gs.getLatSeconds());
204            }
205            if (subGrid == null)
206                return false;
207            else {
208                subGrid.interpolateGridShift(gs);
209                gs.setSubGridName(subGrid.getSubGridName());
210                lastSubGrid = subGrid;
211                return true;
212            }
213        }
214    
215        /**
216         * Shift a coordinate in the Reverse direction of the Grid Shift File.
217         *
218         * @param gs A GridShift object containing the coordinate to shift
219         * @return True if the coordinate is within a Sub Grid, false if not
220         * @throws IOException
221         */
222        public boolean gridShiftReverse(NTV2GridShift gs) {
223            // set up the first estimate
224            NTV2GridShift forwardGs = new NTV2GridShift();
225            forwardGs.setLonPositiveWestSeconds(gs.getLonPositiveWestSeconds());
226            forwardGs.setLatSeconds(gs.getLatSeconds());
227            for (int i = 0; i < 4; i++) {
228                if (!gridShiftForward(forwardGs))
229                    return false;
230                forwardGs.setLonPositiveWestSeconds(
231                        gs.getLonPositiveWestSeconds() - forwardGs.getLonShiftPositiveWestSeconds());
232                forwardGs.setLatSeconds(gs.getLatSeconds() - forwardGs.getLatShiftSeconds());
233            }
234            gs.setLonShiftPositiveWestSeconds(-forwardGs.getLonShiftPositiveWestSeconds());
235            gs.setLatShiftSeconds(-forwardGs.getLatShiftSeconds());
236            gs.setLonAccuracyAvailable(forwardGs.isLonAccuracyAvailable());
237            if (forwardGs.isLonAccuracyAvailable()) {
238                gs.setLonAccuracySeconds(forwardGs.getLonAccuracySeconds());
239            }
240            gs.setLatAccuracyAvailable(forwardGs.isLatAccuracyAvailable());
241            if (forwardGs.isLatAccuracyAvailable()) {
242                gs.setLatAccuracySeconds(forwardGs.getLatAccuracySeconds());
243            }
244            return true;
245        }
246    
247        /**
248         * Find the finest SubGrid containing the coordinate, specified
249         * in Positive West Seconds
250         *
251         * @param lon Longitude in Positive West Seconds
252         * @param lat Latitude in Seconds
253         * @return The SubGrid found or null
254         */
255        private NTV2SubGrid getSubGrid(double lon, double lat) {
256            NTV2SubGrid sub = null;
257            for (int i = 0; i < topLevelSubGrid.length; i++) {
258                sub = topLevelSubGrid[i].getSubGridForCoord(lon, lat);
259                if (sub != null) {
260                    break;
261                }
262            }
263            return sub;
264        }
265    
266        public boolean isLoaded() {
267            return (topLevelSubGrid != null);
268        }
269    
270        public void unload() {
271            topLevelSubGrid = null;
272        }
273    
274        @Override
275        public String toString() {
276            StringBuffer buf = new StringBuffer("Headers  : ");
277            buf.append(overviewHeaderCount);
278            buf.append("\nSub Hdrs : ");
279            buf.append(subGridHeaderCount);
280            buf.append("\nSub Grids: ");
281            buf.append(subGridCount);
282            buf.append("\nType     : ");
283            buf.append(shiftType);
284            buf.append("\nVersion  : ");
285            buf.append(version);
286            buf.append("\nFr Ellpsd: ");
287            buf.append(fromEllipsoid);
288            buf.append("\nTo Ellpsd: ");
289            buf.append(toEllipsoid);
290            buf.append("\nFr Maj Ax: ");
291            buf.append(fromSemiMajorAxis);
292            buf.append("\nFr Min Ax: ");
293            buf.append(fromSemiMinorAxis);
294            buf.append("\nTo Maj Ax: ");
295            buf.append(toSemiMajorAxis);
296            buf.append("\nTo Min Ax: ");
297            buf.append(toSemiMinorAxis);
298            return buf.toString();
299        }
300    
301        /**
302         * Get a copy of the SubGrid tree for this file.
303         *
304         * @return a deep clone of the current SubGrid tree
305         */
306        public NTV2SubGrid[] getSubGridTree() {
307            NTV2SubGrid[] clone = new NTV2SubGrid[topLevelSubGrid.length];
308            for (int i = 0; i < topLevelSubGrid.length; i++) {
309                clone[i] = (NTV2SubGrid)topLevelSubGrid[i].clone();
310            }
311            return clone;
312        }
313    
314        public String getFromEllipsoid() {
315            return fromEllipsoid;
316        }
317    
318        public String getToEllipsoid() {
319            return toEllipsoid;
320        }
321    
322    }