]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/function/srtm/LookupSrtmFunction.java
4d13348d3c4be742656f3b3690c7a9db09609ca8
[GpsPrune.git] / tim / prune / function / srtm / LookupSrtmFunction.java
1 package tim.prune.function.srtm;
2
3 import java.io.IOException;
4 import java.net.URL;
5 import java.util.ArrayList;
6 import java.util.zip.ZipEntry;
7 import java.util.zip.ZipInputStream;
8
9 import javax.swing.JOptionPane;
10
11 import tim.prune.App;
12 import tim.prune.DataSubscriber;
13 import tim.prune.GenericFunction;
14 import tim.prune.I18nManager;
15 import tim.prune.UpdateMessageBroker;
16 import tim.prune.data.DataPoint;
17 import tim.prune.data.Field;
18 import tim.prune.data.Track;
19 import tim.prune.gui.ProgressDialog;
20 import tim.prune.undo.UndoLookupSrtm;
21
22 /**
23  * Class to provide a lookup function for point altitudes
24  * using the Space Shuttle's SRTM data files.
25  * HGT files are downloaded into memory via HTTP and point altitudes
26  * can then be interpolated from the 3m grid data.
27  */
28 public class LookupSrtmFunction extends GenericFunction implements Runnable
29 {
30         /** Progress dialog */
31         ProgressDialog _progress = null;
32
33         /** Expected size of hgt file in bytes */
34         private static final long HGT_SIZE = 2884802L;
35         /** Altitude below which is considered void */
36         private static final int VOID_VAL = -32768;
37
38
39         /**
40          * Constructor
41          * @param inApp App object
42          */
43         public LookupSrtmFunction(App inApp)
44         {
45                 super(inApp);
46         }
47
48         /** @return name key */
49         public String getNameKey() {
50                 return "function.lookupsrtm";
51         }
52
53         /**
54          * Begin the lookup
55          */
56         public void begin()
57         {
58                 if (_progress == null)
59                 {
60                         _progress = new ProgressDialog(_parentFrame, getNameKey());
61                 }
62                 _progress.show();
63                 // start new thread for time-consuming part
64                 new Thread(this).start();
65         }
66
67
68         /**
69          * Run method using separate thread
70          */
71         public void run()
72         {
73                 // Compile list of tiles to get
74                 Track track = _app.getTrackInfo().getTrack();
75                 ArrayList<SrtmTile> tileList = new ArrayList<SrtmTile>();
76                 boolean hasZeroAltitudePoints = false;
77                 boolean hasNonZeroAltitudePoints = false;
78                 // First, loop to see what kind of points we have
79                 for (int i=0; i<track.getNumPoints(); i++)
80                 {
81                         if (track.getPoint(i).hasAltitude())
82                         {
83                                 if (track.getPoint(i).getAltitude().getValue() == 0) {
84                                         hasZeroAltitudePoints = true;
85                                 }
86                                 else {
87                                         hasNonZeroAltitudePoints = true;
88                                 }
89                         }
90                 }
91                 // Should we overwrite the zero altitude values?
92                 boolean overwriteZeros = hasZeroAltitudePoints && !hasNonZeroAltitudePoints;
93                 // If non-zero values present as well, ask user whether to overwrite the zeros or not
94                 if (hasNonZeroAltitudePoints && hasZeroAltitudePoints && JOptionPane.showConfirmDialog(_parentFrame,
95                         I18nManager.getText("dialog.lookupsrtm.overwritezeros"), I18nManager.getText(getNameKey()),
96                         JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
97                 {
98                         overwriteZeros = true;
99                 }
100
101                 // Now loop again to extract the required tiles
102                 for (int i=0; i<track.getNumPoints(); i++)
103                 {
104                         // Consider points which don't have altitudes or have zero values
105                         if (!track.getPoint(i).hasAltitude() || (overwriteZeros && track.getPoint(i).getAltitude().getValue() == 0))
106                         {
107                                 SrtmTile tile = new SrtmTile(track.getPoint(i));
108                                 boolean alreadyGot = false;
109                                 for (int t=0; t<tileList.size(); t++) {
110                                         if (tileList.get(t).equals(tile)) {
111                                                 alreadyGot = true;
112                                         }
113                                 }
114                                 if (!alreadyGot) {tileList.add(tile);}
115                         }
116                 }
117                 lookupValues(tileList, overwriteZeros);
118         }
119
120         /**
121          * Lookup the values from SRTM data
122          * @param inTileList list of tiles to get
123          * @param inOverwriteZeros true to overwrite zero altitude values
124          */
125         private void lookupValues(ArrayList<SrtmTile> inTileList, boolean inOverwriteZeros)
126         {
127                 Track track = _app.getTrackInfo().getTrack();
128                 UndoLookupSrtm undo = new UndoLookupSrtm(_app.getTrackInfo());
129                 int numAltitudesFound = 0;
130                 // Update progress bar
131                 _progress.setMaximum(inTileList.size());
132                 _progress.setValue(0);
133                 String errorMessage = null;
134                 // Get urls for each tile
135                 URL[] urls = TileFinder.getUrls(inTileList);
136                 for (int t=0; t<inTileList.size() && !_progress.isCancelled(); t++)
137                 {
138                         if (urls[t] != null)
139                         {
140                                 SrtmTile tile = inTileList.get(t);
141                                 try
142                                 {
143                                         _progress.setValue(t);
144                                         final int ARRLENGTH = 1201*1201;
145                                         int[] heights = new int[ARRLENGTH];
146                                         // Open zipinputstream on url and check size
147                                         ZipInputStream inStream = new ZipInputStream(urls[t].openStream());
148                                         ZipEntry entry = inStream.getNextEntry();
149                                         boolean entryOk = (entry.getSize() == HGT_SIZE);
150                                         if (entryOk)
151                                         {
152                                                 // Read entire file contents into one byte array
153                                                 for (int i=0; i<ARRLENGTH; i++) {
154                                                         heights[i] = inStream.read()*256 + inStream.read();
155                                                         if (heights[i] >= 32768) {heights[i] -= 65536;}
156                                                 }
157                                         }
158                                         //else {
159                                         //      System.out.println("length not ok: " + entry.getSize());
160                                         //}
161                                         // Close stream from url
162                                         inStream.close();
163
164                                         if (entryOk)
165                                         {
166                                                 // Loop over all points in track, try to apply altitude from array
167                                                 for (int p=0; p<track.getNumPoints(); p++)
168                                                 {
169                                                         DataPoint point = track.getPoint(p);
170                                                         if (!point.hasAltitude() || (inOverwriteZeros && point.getAltitude().getValue() == 0)) {
171                                                                 if (new SrtmTile(point).equals(tile))
172                                                                 {
173                                                                         double x = (point.getLongitude().getDouble() - tile.getLongitude()) * 1200;
174                                                                         double y = 1201 - (point.getLatitude().getDouble() - tile.getLatitude()) * 1200;
175                                                                         int idx1 = ((int)y)*1201 + (int)x;
176                                                                         try {
177                                                                                 int[] fouralts = {heights[idx1], heights[idx1+1], heights[idx1-1201], heights[idx1-1200]};
178                                                                                 int numVoids = (fouralts[0]==VOID_VAL?1:0) + (fouralts[1]==VOID_VAL?1:0)
179                                                                                         + (fouralts[2]==VOID_VAL?1:0) + (fouralts[3]==VOID_VAL?1:0);
180                                                                                 // if (numVoids > 0) System.out.println(numVoids + " voids found");
181                                                                                 double altitude = 0.0;
182                                                                                 switch (numVoids) {
183                                                                                         case 0: altitude = bilinearInterpolate(fouralts, x, y); break;
184                                                                                         case 1: altitude = bilinearInterpolate(fixVoid(fouralts), x, y); break;
185                                                                                         case 2:
186                                                                                         case 3: altitude = averageNonVoid(fouralts); break;
187                                                                                         default: altitude = VOID_VAL;
188                                                                                 }
189                                                                                 if (altitude != VOID_VAL) {
190                                                                                         point.setFieldValue(Field.ALTITUDE, ""+altitude, false);
191                                                                                         numAltitudesFound++;
192                                                                                 }
193                                                                         }
194                                                                         catch (ArrayIndexOutOfBoundsException obe) {
195                                                                                 //System.err.println("lat=" + point.getLatitude().getDouble() + ", x=" + x + ", y=" + y + ", idx=" + idx1);
196                                                                         }
197                                                                 }
198                                                         }
199                                                 }
200                                         }
201                                 }
202                                 catch (IOException ioe) {
203                                         errorMessage = ioe.getClass().getName() + " - " + ioe.getMessage();
204                                 }
205                         }
206                 }
207                 _progress.dispose();
208                 if (_progress.isCancelled()) {return;}
209                 if (numAltitudesFound > 0)
210                 {
211                         // Inform app including undo information
212                         track.requestRescale();
213                         UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_ADDED_OR_REMOVED);
214                         _app.completeFunction(undo, I18nManager.getText("confirm.lookupsrtm1") + " " + numAltitudesFound
215                                 + " " + I18nManager.getText("confirm.lookupsrtm2"));
216                 }
217                 else if (errorMessage != null) {
218                         _app.showErrorMessageNoLookup(getNameKey(), errorMessage);
219                 }
220                 else if (inTileList.size() > 0) {
221                         _app.showErrorMessage(getNameKey(), "error.lookupsrtm.nonefound");
222                 }
223                 else {
224                         _app.showErrorMessage(getNameKey(), "error.lookupsrtm.nonerequired");
225                 }
226         }
227
228         /**
229          * Perform a bilinear interpolation on the given altitude array
230          * @param inAltitudes array of four altitude values on corners of square (bl, br, tl, tr)
231          * @param inX x coordinate
232          * @param inY y coordinate
233          * @return interpolated altitude
234          */
235         private static double bilinearInterpolate(int[] inAltitudes, double inX, double inY)
236         {
237                 double alpha = inX - (int) inX;
238                 double beta  = 1 - (inY - (int) inY);
239                 double alt = (1-alpha)*(1-beta)*inAltitudes[0] + alpha*(1-beta)*inAltitudes[1]
240                         + (1-alpha)*beta*inAltitudes[2] + alpha*beta*inAltitudes[3];
241                 return alt;
242         }
243
244         /**
245          * Fix a single void in the given array by replacing it with the average of the others
246          * @param inAltitudes array of altitudes containing one void
247          * @return fixed array without voids
248          */
249         private static int[] fixVoid(int[] inAltitudes)
250         {
251                 int[] fixed = new int[inAltitudes.length];
252                 for (int i=0; i<inAltitudes.length; i++) {
253                         if (inAltitudes[i] == VOID_VAL) {
254                                 fixed[i] = (int) Math.round(averageNonVoid(inAltitudes));
255                         }
256                         else {
257                                 fixed[i] = inAltitudes[i];
258                         }
259                 }
260                 return fixed;
261         }
262
263         /**
264          * Calculate the average of the non-void altitudes in the given array
265          * @param inAltitudes array of altitudes with one or more voids
266          * @return average of non-void altitudes
267          */
268         private static final double averageNonVoid(int[] inAltitudes)
269         {
270                 double totalAltitude = 0.0;
271                 int numAlts = 0;
272                 for (int i=0; i<inAltitudes.length; i++) {
273                         if (inAltitudes[i] != VOID_VAL) {
274                                 totalAltitude += inAltitudes[i];
275                                 numAlts++;
276                         }
277                 }
278                 if (numAlts < 1) {return VOID_VAL;}
279                 return totalAltitude / numAlts;
280         }
281 }