source: josm/trunk/src/org/openstreetmap/josm/data/projection/NTV2GridShiftFile.java@ 3477

Last change on this file since 3477 was 3083, checked in by bastiK, 14 years ago

added svn:eol-style=native to source files

  • Property svn:eol-style set to native
File size: 12.3 KB
Line 
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 */
20package org.openstreetmap.josm.data.projection;
21
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.Serializable;
25import java.util.ArrayList;
26import java.util.HashMap;
27
28/**
29 * Models the NTv2 format Grid Shift File and exposes methods to shift
30 * coordinate values using the Sub Grids contained in the file.
31 * <p>The principal reference for the alogrithms used is the
32 * 'GDAit Software Architecture Manual' produced by the <a
33 * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
34 * Department of the University of Melbourne</a>
35 * <p>This library reads binary NTv2 Grid Shift files in Big Endian
36 * (Canadian standard) or Little Endian (Australian Standard) format.
37 * The older 'Australian' binary format is not supported, only the
38 * official Canadian format, which is now also used for the national
39 * Australian Grid.
40 * <p>Grid Shift files can be read as InputStreams or RandomAccessFiles.
41 * Loading an InputStream places all the required node information
42 * (accuracy data is optional) into heap based Java arrays. This is the
43 * highest perfomance option, and is useful for large volume transformations.
44 * Non-file data sources (eg using an SQL Blob) are also supported through
45 * InputStream. The RandonAccessFile option has a much smaller memory
46 * footprint as only the Sub Grid headers are stored in memory, but
47 * transformation is slower because the file must be read a number of
48 * times for each transformation.
49 * <p>Coordinates may be shifted Forward (ie from and to the Datums specified
50 * in the Grid Shift File header) or Reverse. The reverse transformation
51 * uses an iterative approach to approximate the Grid Shift, as the
52 * precise transformation is based on 'from' datum coordinates.
53 * <p>Coordinates may be specified
54 * either in Seconds using Positive West Longitude (the original NTv2
55 * arrangement) or in decimal Degrees using Positive East Longitude.
56 *
57 * @author Peter Yuill
58 * Modifified for JOSM :
59 * - removed the RandomAccessFile mode (Pieren)
60 */
61public class NTV2GridShiftFile implements Serializable {
62
63 private String overviewHeaderCountId;
64 private int overviewHeaderCount;
65 private int subGridHeaderCount;
66 private int subGridCount;
67 private String shiftType;
68 private String version;
69 private String fromEllipsoid = "";
70 private String toEllipsoid = "";
71 private double fromSemiMajorAxis;
72 private double fromSemiMinorAxis;
73 private double toSemiMajorAxis;
74 private double toSemiMinorAxis;
75
76 private NTV2SubGrid[] topLevelSubGrid;
77 private NTV2SubGrid lastSubGrid;
78
79 public NTV2GridShiftFile() {
80 }
81
82 /**
83 * Load a Grid Shift File from an InputStream. The Grid Shift node
84 * data is stored in Java arrays, which will occupy about the same memory
85 * as the original file with accuracy data included, and about half that
86 * with accuracy data excluded. The size of the Australian national file
87 * is 4.5MB, and the Canadian national file is 13.5MB
88 * <p>The InputStream is closed by this method.
89 *
90 * @param in Grid Shift File InputStream
91 * @param loadAccuracy is Accuracy data to be loaded as well as shift data?
92 * @throws Exception
93 */
94 public void loadGridShiftFile(InputStream in, boolean loadAccuracy ) throws IOException {
95 byte[] b8 = new byte[8];
96 boolean bigEndian = true;
97 fromEllipsoid = "";
98 toEllipsoid = "";
99 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}
Note: See TracBrowser for help on using the repository browser.