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

Last change on this file since 9243 was 8870, checked in by Don-vip, 9 years ago

sonar - squid:S2325 - "private" methods that don't access instance data should be "static"

  • Property svn:eol-style set to native
File size: 12.8 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.datum;
21
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.Serializable;
25import java.nio.charset.StandardCharsets;
26import java.util.ArrayList;
27import java.util.HashMap;
28import java.util.List;
29import java.util.Map;
30
31import org.openstreetmap.josm.Main;
32
33/**
34 * Models the NTv2 format Grid Shift File and exposes methods to shift
35 * coordinate values using the Sub Grids contained in the file.
36 * <p>The principal reference for the alogrithms used is the
37 * 'GDAit Software Architecture Manual' produced by the <a
38 * href='http://www.sli.unimelb.edu.au/gda94'>Geomatics
39 * Department of the University of Melbourne</a>
40 * <p>This library reads binary NTv2 Grid Shift files in Big Endian
41 * (Canadian standard) or Little Endian (Australian Standard) format.
42 * The older 'Australian' binary format is not supported, only the
43 * official Canadian format, which is now also used for the national
44 * Australian Grid.
45 * <p>Grid Shift files can be read as InputStreams or RandomAccessFiles.
46 * Loading an InputStream places all the required node information
47 * (accuracy data is optional) into heap based Java arrays. This is the
48 * highest perfomance option, and is useful for large volume transformations.
49 * Non-file data sources (eg using an SQL Blob) are also supported through
50 * InputStream. The RandonAccessFile option has a much smaller memory
51 * footprint as only the Sub Grid headers are stored in memory, but
52 * transformation is slower because the file must be read a number of
53 * times for each transformation.
54 * <p>Coordinates may be shifted Forward (ie from and to the Datums specified
55 * in the Grid Shift File header) or Reverse. The reverse transformation
56 * uses an iterative approach to approximate the Grid Shift, as the
57 * precise transformation is based on 'from' datum coordinates.
58 * <p>Coordinates may be specified
59 * either in Seconds using Positive West Longitude (the original NTv2
60 * arrangement) or in decimal Degrees using Positive East Longitude.
61 *
62 * @author Peter Yuill
63 * Modifified for JOSM :
64 * - removed the RandomAccessFile mode (Pieren)
65 */
66public class NTV2GridShiftFile implements Serializable {
67
68 private static final long serialVersionUID = 1L;
69
70 private int overviewHeaderCount;
71 private int subGridHeaderCount;
72 private int subGridCount;
73 private String shiftType;
74 private String version;
75 private String fromEllipsoid = "";
76 private String toEllipsoid = "";
77 private double fromSemiMajorAxis;
78 private double fromSemiMinorAxis;
79 private double toSemiMajorAxis;
80 private double toSemiMinorAxis;
81
82 private NTV2SubGrid[] topLevelSubGrid;
83 private NTV2SubGrid lastSubGrid;
84
85 private static void readBytes(InputStream in, byte[] b) throws IOException {
86 if (in.read(b) < b.length) {
87 Main.error("Failed to read expected amount of bytes ("+ b.length +") from stream");
88 }
89 }
90
91 /**
92 * Load a Grid Shift File from an InputStream. The Grid Shift node
93 * data is stored in Java arrays, which will occupy about the same memory
94 * as the original file with accuracy data included, and about half that
95 * with accuracy data excluded. The size of the Australian national file
96 * is 4.5MB, and the Canadian national file is 13.5MB
97 * <p>The InputStream is closed by this method.
98 *
99 * @param in Grid Shift File InputStream
100 * @param loadAccuracy is Accuracy data to be loaded as well as shift data?
101 * @throws IOException if any I/O error occurs
102 */
103 public void loadGridShiftFile(InputStream in, boolean loadAccuracy) throws IOException {
104 byte[] b8 = new byte[8];
105 boolean bigEndian = true;
106 fromEllipsoid = "";
107 toEllipsoid = "";
108 topLevelSubGrid = null;
109 readBytes(in, b8);
110 String overviewHeaderCountId = new String(b8, StandardCharsets.UTF_8);
111 if (!"NUM_OREC".equals(overviewHeaderCountId))
112 throw new IllegalArgumentException("Input file is not an NTv2 grid shift file");
113 readBytes(in, b8);
114 overviewHeaderCount = NTV2Util.getIntBE(b8, 0);
115 if (overviewHeaderCount == 11) {
116 bigEndian = true;
117 } else {
118 overviewHeaderCount = NTV2Util.getIntLE(b8, 0);
119 if (overviewHeaderCount == 11) {
120 bigEndian = false;
121 } else
122 throw new IllegalArgumentException("Input file is not an NTv2 grid shift file");
123 }
124 readBytes(in, b8);
125 readBytes(in, b8);
126 subGridHeaderCount = NTV2Util.getInt(b8, bigEndian);
127 readBytes(in, b8);
128 readBytes(in, b8);
129 subGridCount = NTV2Util.getInt(b8, bigEndian);
130 NTV2SubGrid[] subGrid = new NTV2SubGrid[subGridCount];
131 readBytes(in, b8);
132 readBytes(in, b8);
133 shiftType = new String(b8, StandardCharsets.UTF_8);
134 readBytes(in, b8);
135 readBytes(in, b8);
136 version = new String(b8, StandardCharsets.UTF_8);
137 readBytes(in, b8);
138 readBytes(in, b8);
139 fromEllipsoid = new String(b8, StandardCharsets.UTF_8);
140 readBytes(in, b8);
141 readBytes(in, b8);
142 toEllipsoid = new String(b8, StandardCharsets.UTF_8);
143 readBytes(in, b8);
144 readBytes(in, b8);
145 fromSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian);
146 readBytes(in, b8);
147 readBytes(in, b8);
148 fromSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian);
149 readBytes(in, b8);
150 readBytes(in, b8);
151 toSemiMajorAxis = NTV2Util.getDouble(b8, bigEndian);
152 readBytes(in, b8);
153 readBytes(in, b8);
154 toSemiMinorAxis = NTV2Util.getDouble(b8, bigEndian);
155
156 for (int i = 0; i < subGridCount; i++) {
157 subGrid[i] = new NTV2SubGrid(in, bigEndian, loadAccuracy);
158 }
159 topLevelSubGrid = createSubGridTree(subGrid);
160 lastSubGrid = topLevelSubGrid[0];
161 }
162
163 /**
164 * Create a tree of Sub Grids by adding each Sub Grid to its parent (where
165 * it has one), and returning an array of the top level Sub Grids
166 * @param subGrid an array of all Sub Grids
167 * @return an array of top level Sub Grids with lower level Sub Grids set.
168 */
169 private static NTV2SubGrid[] createSubGridTree(NTV2SubGrid[] subGrid) {
170 int topLevelCount = 0;
171 Map<String, List<NTV2SubGrid>> subGridMap = new HashMap<>();
172 for (int i = 0; i < subGrid.length; i++) {
173 if ("NONE".equalsIgnoreCase(subGrid[i].getParentSubGridName())) {
174 topLevelCount++;
175 }
176 subGridMap.put(subGrid[i].getSubGridName(), new ArrayList<NTV2SubGrid>());
177 }
178 NTV2SubGrid[] topLevelSubGrid = new NTV2SubGrid[topLevelCount];
179 topLevelCount = 0;
180 for (int i = 0; i < subGrid.length; i++) {
181 if ("NONE".equalsIgnoreCase(subGrid[i].getParentSubGridName())) {
182 topLevelSubGrid[topLevelCount++] = subGrid[i];
183 } else {
184 List<NTV2SubGrid> parent = subGridMap.get(subGrid[i].getParentSubGridName());
185 parent.add(subGrid[i]);
186 }
187 }
188 NTV2SubGrid[] nullArray = new NTV2SubGrid[0];
189 for (int i = 0; i < subGrid.length; i++) {
190 List<NTV2SubGrid> subSubGrids = subGridMap.get(subGrid[i].getSubGridName());
191 if (!subSubGrids.isEmpty()) {
192 NTV2SubGrid[] subGridArray = subSubGrids.toArray(nullArray);
193 subGrid[i].setSubGridArray(subGridArray);
194 }
195 }
196 return topLevelSubGrid;
197 }
198
199 /**
200 * Shift a coordinate in the Forward direction of the Grid Shift File.
201 *
202 * @param gs A GridShift object containing the coordinate to shift
203 * @return True if the coordinate is within a Sub Grid, false if not
204 */
205 public boolean gridShiftForward(NTV2GridShift gs) {
206 // Try the last sub grid first, big chance the coord is still within it
207 NTV2SubGrid subGrid = lastSubGrid.getSubGridForCoord(gs.getLonPositiveWestSeconds(), gs.getLatSeconds());
208 if (subGrid == null) {
209 subGrid = getSubGrid(gs.getLonPositiveWestSeconds(), gs.getLatSeconds());
210 }
211 if (subGrid == null)
212 return false;
213 else {
214 subGrid.interpolateGridShift(gs);
215 gs.setSubGridName(subGrid.getSubGridName());
216 lastSubGrid = subGrid;
217 return true;
218 }
219 }
220
221 /**
222 * Shift a coordinate in the Reverse direction of the Grid Shift File.
223 *
224 * @param gs A GridShift object containing the coordinate to shift
225 * @return True if the coordinate is within a Sub Grid, false if not
226 */
227 public boolean gridShiftReverse(NTV2GridShift gs) {
228 // set up the first estimate
229 NTV2GridShift forwardGs = new NTV2GridShift();
230 forwardGs.setLonPositiveWestSeconds(gs.getLonPositiveWestSeconds());
231 forwardGs.setLatSeconds(gs.getLatSeconds());
232 for (int i = 0; i < 4; i++) {
233 if (!gridShiftForward(forwardGs))
234 return false;
235 forwardGs.setLonPositiveWestSeconds(
236 gs.getLonPositiveWestSeconds() - forwardGs.getLonShiftPositiveWestSeconds());
237 forwardGs.setLatSeconds(gs.getLatSeconds() - forwardGs.getLatShiftSeconds());
238 }
239 gs.setLonShiftPositiveWestSeconds(-forwardGs.getLonShiftPositiveWestSeconds());
240 gs.setLatShiftSeconds(-forwardGs.getLatShiftSeconds());
241 gs.setLonAccuracyAvailable(forwardGs.isLonAccuracyAvailable());
242 if (forwardGs.isLonAccuracyAvailable()) {
243 gs.setLonAccuracySeconds(forwardGs.getLonAccuracySeconds());
244 }
245 gs.setLatAccuracyAvailable(forwardGs.isLatAccuracyAvailable());
246 if (forwardGs.isLatAccuracyAvailable()) {
247 gs.setLatAccuracySeconds(forwardGs.getLatAccuracySeconds());
248 }
249 return true;
250 }
251
252 /**
253 * Find the finest SubGrid containing the coordinate, specified
254 * in Positive West Seconds
255 *
256 * @param lon Longitude in Positive West Seconds
257 * @param lat Latitude in Seconds
258 * @return The SubGrid found or null
259 */
260 private NTV2SubGrid getSubGrid(double lon, double lat) {
261 NTV2SubGrid sub = null;
262 for (int i = 0; i < topLevelSubGrid.length; i++) {
263 sub = topLevelSubGrid[i].getSubGridForCoord(lon, lat);
264 if (sub != null) {
265 break;
266 }
267 }
268 return sub;
269 }
270
271 public boolean isLoaded() {
272 return topLevelSubGrid != null;
273 }
274
275 public void unload() {
276 topLevelSubGrid = null;
277 }
278
279 @Override
280 public String toString() {
281 StringBuilder buff = new StringBuilder("Headers : ");
282 buff.append(overviewHeaderCount)
283 .append("\nSub Hdrs : ")
284 .append(subGridHeaderCount)
285 .append("\nSub Grids: ")
286 .append(subGridCount)
287 .append("\nType : ")
288 .append(shiftType)
289 .append("\nVersion : ")
290 .append(version)
291 .append("\nFr Ellpsd: ")
292 .append(fromEllipsoid)
293 .append("\nTo Ellpsd: ")
294 .append(toEllipsoid)
295 .append("\nFr Maj Ax: ")
296 .append(fromSemiMajorAxis)
297 .append("\nFr Min Ax: ")
298 .append(fromSemiMinorAxis)
299 .append("\nTo Maj Ax: ")
300 .append(toSemiMajorAxis)
301 .append("\nTo Min Ax: ")
302 .append(toSemiMinorAxis);
303 return buff.toString();
304 }
305
306 /**
307 * Get a copy of the SubGrid tree for this file.
308 *
309 * @return a deep clone of the current SubGrid tree
310 */
311 public NTV2SubGrid[] getSubGridTree() {
312 NTV2SubGrid[] clone = new NTV2SubGrid[topLevelSubGrid.length];
313 for (int i = 0; i < topLevelSubGrid.length; i++) {
314 clone[i] = (NTV2SubGrid) topLevelSubGrid[i].clone();
315 }
316 return clone;
317 }
318
319 public String getFromEllipsoid() {
320 return fromEllipsoid;
321 }
322
323 public String getToEllipsoid() {
324 return toEllipsoid;
325 }
326}
Note: See TracBrowser for help on using the repository browser.