]> gitweb.fperrin.net Git - GpsPrune.git/commitdiff
Merge branch 'not-ready-for-upstreaming' into fp-integration v19.2.fp1
authorFrédéric Perrin <fred@fperrin.net>
Sat, 30 Nov 2019 20:50:38 +0000 (20:50 +0000)
committerFrédéric Perrin <fred@fperrin.net>
Sat, 30 Nov 2019 20:50:38 +0000 (20:50 +0000)
28 files changed:
.gitignore [new file with mode: 0644]
buildtools/build.sh [changed mode: 0644->0755]
src/tim/prune/App.java
src/tim/prune/FunctionLibrary.java
src/tim/prune/config/Config.java
src/tim/prune/data/DataPoint.java
src/tim/prune/data/Track.java
src/tim/prune/function/RemoveAltitudes.java [new file with mode: 0644]
src/tim/prune/function/edit/PointEditor.java
src/tim/prune/function/edit/PointNameEditor.java
src/tim/prune/function/settings/SetEarthdataAuthentication.java [new file with mode: 0644]
src/tim/prune/function/srtm/DownloadSrtmFunction.java
src/tim/prune/function/srtm/LookupSrtmFunction.java
src/tim/prune/function/srtm/Srtm3Source.java [new file with mode: 0644]
src/tim/prune/function/srtm/SrtmDiskCache.java [new file with mode: 0644]
src/tim/prune/function/srtm/SrtmGl1Source.java [new file with mode: 0644]
src/tim/prune/function/srtm/SrtmSource.java [new file with mode: 0644]
src/tim/prune/function/srtm/SrtmSourceException.java [new file with mode: 0644]
src/tim/prune/function/srtm/SrtmTile.java
src/tim/prune/function/srtm/TileFinder.java [deleted file]
src/tim/prune/gui/DisplayUtils.java
src/tim/prune/gui/MenuManager.java
src/tim/prune/gui/Viewport.java
src/tim/prune/gui/map/MapCanvas.java
src/tim/prune/gui/map/MapPosition.java
src/tim/prune/gui/map/MapTileManager.java
src/tim/prune/lang/prune-texts_en.properties
src/tim/prune/undo/UndoRemoveAltitudes.java [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..8e9e794
--- /dev/null
@@ -0,0 +1,2 @@
+*.class
+*.jar
old mode 100644 (file)
new mode 100755 (executable)
index b38e2ce..c7fcf3d
@@ -1,3 +1,4 @@
+set -e
 # Build script
 # Version number
 PRUNENAME=gpsprune_19.2
index 247a1332fc6f931c0836ab0e6fdfc36a563cec07..317064d39984acb566cee90940236c52c32fd58f 100644 (file)
@@ -456,6 +456,22 @@ public class App
        }
 
 
+       /**
+        * Remove altitudes from selected points
+        */
+       public void removeAltitudes(int selStart, int selEnd)
+       {
+               UndoRemoveAltitudes undo = new UndoRemoveAltitudes(_trackInfo, selStart, selEnd);
+               if (_trackInfo.getTrack().removeAltitudes(selStart, selEnd))
+               {
+                       _undoStack.add(undo);
+                       _trackInfo.getSelection().markInvalid();
+                       UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
+                       UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.removealtitudes"));
+               }
+       }
+
+
        /**
         * Merge the track segments within the current selection
         */
@@ -761,6 +777,8 @@ public class App
                        + " '" + inSourceInfo.getName() + "'");
                // update menu
                _menuManager.informFileLoaded();
+               // recentre viewport on new file data
+               _viewport.recentreViewport();
                // update main window title
                updateTitle();
                // Remove busy lock
index 0441826164d930de8b79acb7d45fd84a742d33ce..4be286e479d036a8b37cabe8223f495872a2f361 100644 (file)
@@ -28,6 +28,7 @@ import tim.prune.function.PhotoPopupFunction;
 import tim.prune.function.PlayAudioFunction;
 import tim.prune.function.RearrangePhotosFunction;
 import tim.prune.function.RearrangeWaypointsFunction;
+import tim.prune.function.RemoveAltitudes;
 import tim.prune.function.RemoveAudioFunction;
 import tim.prune.function.RemovePhotoFunction;
 import tim.prune.function.RotatePhoto;
@@ -56,12 +57,12 @@ import tim.prune.function.settings.SaveConfig;
 import tim.prune.function.settings.SetAltitudeTolerance;
 import tim.prune.function.settings.SetColours;
 import tim.prune.function.settings.SetDisplaySettings;
+import tim.prune.function.settings.SetEarthdataAuthentication;
 import tim.prune.function.settings.SetLanguage;
 import tim.prune.function.settings.SetMapBgFunction;
 import tim.prune.function.settings.SetPathsFunction;
 import tim.prune.function.sew.SewTrackSegmentsFunction;
 import tim.prune.function.sew.SplitSegmentsFunction;
-import tim.prune.function.srtm.DownloadSrtmFunction;
 import tim.prune.function.srtm.LookupSrtmFunction;
 import tim.prune.function.weather.GetWeatherForecastFunction;
 import tim.prune.load.AudioLoader;
@@ -103,13 +104,13 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_DELETE_BY_DATE = null;
        public static SingleNumericParameterFunction FUNCTION_INTERPOLATE = null;
        public static GenericFunction FUNCTION_LOOKUP_SRTM = null;
-       public static GenericFunction FUNCTION_DOWNLOAD_SRTM = null;
        public static GenericFunction FUNCTION_NEARBY_WIKIPEDIA = null;
        public static GenericFunction FUNCTION_SEARCH_WIKIPEDIA = null;
        public static GenericFunction FUNCTION_SEARCH_OSMPOIS = null;
        public static GenericFunction FUNCTION_DOWNLOAD_OSM = null;
        public static GenericFunction FUNCTION_ADD_TIME_OFFSET  = null;
        public static GenericFunction FUNCTION_ADD_ALTITUDE_OFFSET  = null;
+       public static GenericFunction FUNCTION_REMOVE_ALTITUDES = null;
        public static GenericFunction FUNCTION_CONVERT_NAMES_TO_TIMES  = null;
        public static GenericFunction FUNCTION_DELETE_FIELD_VALUES  = null;
        public static GenericFunction FUNCTION_PASTE_COORDINATES = null;
@@ -146,6 +147,7 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_SET_COLOURS = null;
        public static GenericFunction FUNCTION_SET_LANGUAGE = null;
        public static SingleNumericParameterFunction FUNCTION_SET_ALTITUDE_TOLERANCE = null;
+       public static GenericFunction FUNCTION_SET_EARTHDATA_AUTH = null;
        public static GenericFunction FUNCTION_SET_TIMEZONE = null;
        public static GenericFunction FUNCTION_HELP   = null;
        public static GenericFunction FUNCTION_SHOW_KEYS = null;
@@ -183,13 +185,13 @@ public abstract class FunctionLibrary
                FUNCTION_DELETE_BY_DATE = new DeleteByDateFunction(inApp);
                FUNCTION_INTERPOLATE = new InterpolateFunction(inApp);
                FUNCTION_LOOKUP_SRTM = new LookupSrtmFunction(inApp);
-               FUNCTION_DOWNLOAD_SRTM = new DownloadSrtmFunction(inApp);
                FUNCTION_NEARBY_WIKIPEDIA = new GetWikipediaFunction(inApp);
                FUNCTION_SEARCH_WIKIPEDIA = new SearchWikipediaNames(inApp);
                FUNCTION_SEARCH_OSMPOIS = new SearchOsmPoisFunction(inApp);
                FUNCTION_DOWNLOAD_OSM = new DownloadOsmFunction(inApp);
                FUNCTION_ADD_TIME_OFFSET = new AddTimeOffset(inApp);
                FUNCTION_ADD_ALTITUDE_OFFSET = new AddAltitudeOffset(inApp);
+               FUNCTION_REMOVE_ALTITUDES = new RemoveAltitudes(inApp);
                FUNCTION_CONVERT_NAMES_TO_TIMES = new ConvertNamesToTimes(inApp);
                FUNCTION_DELETE_FIELD_VALUES = new DeleteFieldValues(inApp);
                FUNCTION_PASTE_COORDINATES = new PasteCoordinates(inApp);
@@ -227,6 +229,7 @@ public abstract class FunctionLibrary
                FUNCTION_SET_LANGUAGE = new SetLanguage(inApp);
                FUNCTION_SET_ALTITUDE_TOLERANCE = new SetAltitudeTolerance(inApp);
                FUNCTION_SET_TIMEZONE = new SelectTimezoneFunction(inApp);
+               FUNCTION_SET_EARTHDATA_AUTH = new SetEarthdataAuthentication(inApp);
                FUNCTION_HELP   = new HelpScreen(inApp);
                FUNCTION_SHOW_KEYS = new ShowKeysScreen(inApp);
                FUNCTION_ABOUT  = new AboutScreen(inApp);
index 84e5fd7dc6b4a248bb1d2bef51f18be225f90425..3939c004ae6a68d92f9f667bb0533b84abadf0c0 100644 (file)
@@ -105,6 +105,8 @@ public abstract class Config
        public static final String KEY_WAYPOINT_ICON_SIZE = "prune.waypointiconsize";
        /** Id of selected timezone */
        public static final String KEY_TIMEZONE_ID = "prune.timezoneid";
+       /** Username/password to the Earthdata server for SRTM 1-arcsecond tiles */
+       public static final String KEY_EARTHDATA_AUTH = "prune.earthdataauth";
 
 
        /** Initialise the default properties */
index 84f4800bcd2c0a0d546da8a83a87868810bf3a54..01e9975786139a1736653d68dd1fd747109b3788 100644 (file)
@@ -354,6 +354,16 @@ public class DataPoint
                }
        }
 
+       /**
+        * Remove altitude from point
+        */
+       public void removeAltitude()
+       {
+               _altitude = Altitude.NONE;
+               _fieldValues[_fieldList.getFieldIndex(Field.ALTITUDE)] = _altitude.getStringValue(null);
+               setModified(false);
+       }
+
        /**
         * Reset the altitude to the previous value (by an undo)
         * @param inClone altitude object cloned from earlier
index f45854a0e3d47a1fd5fc0ee6924732b3294decc0..ef099d4c171d1651bfd627a3beb71bd271943a15 100644 (file)
@@ -377,6 +377,31 @@ public class Track
        }
 
 
+       /**
+        * Remove altitudes from the specified range
+        * @param inStart start of range
+        * @param inEnd end of range
+        */
+       public boolean removeAltitudes(int inStart, int inEnd)
+       {
+               // sanity check
+               if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints) {
+                       return false;
+               }
+
+               boolean anyRemoved = false;
+               for (int i=inStart; i<=inEnd; i++)
+               {
+                       DataPoint p = _dataPoints[i];
+                       if (p != null && p.hasAltitude())
+                       {
+                               p.removeAltitude();
+                               anyRemoved = true;
+                       }
+               }
+               return anyRemoved;
+       }
+
        /**
         * Interleave all waypoints by each nearest track point
         * @return true if successful, false if no change
diff --git a/src/tim/prune/function/RemoveAltitudes.java b/src/tim/prune/function/RemoveAltitudes.java
new file mode 100644 (file)
index 0000000..1c8b05c
--- /dev/null
@@ -0,0 +1,60 @@
+package tim.prune.function;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.Field;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+
+/**
+ * Class to provide the function to add remove the altitude from points in a
+ * track range
+ */
+public class RemoveAltitudes extends GenericFunction
+{
+       /**
+        * Constructor
+        * @param inApp application object for callback
+        */
+       public RemoveAltitudes(App inApp)
+       {
+               super(inApp);
+       }
+
+       /** Get the name key */
+       public String getNameKey() {
+               return "function.removealtitudes";
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               int selStart = _app.getTrackInfo().getSelection().getStart();
+               int selEnd = _app.getTrackInfo().getSelection().getEnd();
+               if (!_app.getTrackInfo().getTrack().hasData(Field.ALTITUDE, selStart, selEnd))
+               {
+                       _app.showErrorMessage(getNameKey(), "dialog.addaltitude.noaltitudes");
+                       return;
+               }
+               _app.removeAltitudes(selStart, selEnd);
+       }
+}
index c66201b8d6f48ebcdf56105daa126077d0b85486..546a65e57e4cec6cf19be1ba0c477d598a7aeb4d 100644 (file)
@@ -307,10 +307,23 @@ public class PointEditor
                        if (_model.getChanged(i))
                        {
                                Field field = fieldList.getField(i);
+                               if (field == field.WAYPT_NAME) {
+                                       if (wasNameAdded(_model.getValue(i))) {
+                                               _app.createPoint(_point.clonePoint());
+                                       }
+                               }
                                editList.addEdit(new FieldEdit(field, _model.getValue(i)));
                                undoList.addEdit(new FieldEdit(field, _point.getFieldValue(field)));
                        }
                }
                _app.completePointEdit(editList, undoList);
        }
+
+       private boolean wasNameAdded(String newName)
+       {
+               String prevName = _point.getWaypointName();
+               boolean prevNull = (prevName == null || prevName.equals(""));
+               boolean newNull = (newName == null || newName.equals(""));
+               return (prevNull && !newNull);
+       }
 }
index 53e1f834b962fa8a098a9fc9345934dbdac3003f..9e6e343dc1357e3f0b9c753b4fd514cb21f20e1e 100644 (file)
@@ -206,7 +206,11 @@ public class PointNameEditor extends GenericFunction
                // Check whether name has really changed
                if (hasNameChanged())
                {
-                       // Make lists for edit and undo, and add the changed field
+                       // If a new name has been added, changing the point
+                       // from trackpoint to waypoint, duplicate it
+                       _app.createPoint(_point.clonePoint());
+                       
+                       // make lists for edit and undo, and add the changed field
                        FieldEditList editList = new FieldEditList();
                        FieldEditList undoList = new FieldEditList();
                        editList.addEdit(new FieldEdit(Field.WAYPT_NAME, _nameField.getText().trim()));
@@ -231,4 +235,17 @@ public class PointNameEditor extends GenericFunction
                        || (!prevNull && newNull)
                        || (!prevNull && !newNull && !prevName.equals(newName));
        }
+
+       /**
+        * Check whether a new name has been added
+        * @return true if it has indeed
+        */
+       private boolean wasNameAdded()
+       {
+               String prevName = _point.getWaypointName();
+               String newName = _nameField.getText().trim();
+               boolean prevNull = (prevName == null || prevName.equals(""));
+               boolean newNull = (newName == null || newName.equals(""));
+               return (prevNull && !newNull);
+       }
 }
diff --git a/src/tim/prune/function/settings/SetEarthdataAuthentication.java b/src/tim/prune/function/settings/SetEarthdataAuthentication.java
new file mode 100644 (file)
index 0000000..d4a166a
--- /dev/null
@@ -0,0 +1,183 @@
+package tim.prune.function.settings;
+
+import java.awt.Component;
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Base64;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.function.srtm.SrtmGl1Source;
+import tim.prune.function.srtm.SrtmSourceException;
+
+/**
+ * Set authentication data for the NASA Earthdata systems
+ */
+public class SetEarthdataAuthentication extends GenericFunction
+{
+       private JDialog _dialog = null;
+       private JTextField _usernameField = null;
+       private JPasswordField _passwordField = null;
+       private JLabel _authAccepted = null;
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public SetEarthdataAuthentication(App inApp) {
+               super(inApp);
+       }
+
+       /** @return name key */
+       public String getNameKey() {
+               return "function.setearthdataauthentication";
+       }
+
+       public void begin()
+       {
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       // Create Gui and show it
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+               prefillCurrentAuth();
+               _dialog.setVisible(true);
+       }
+
+       /**
+        * Make the dialog components
+        * @return the GUI components for the dialog
+        */
+       private JPanel makeDialogComponents()
+       {
+               // Blurb to explain to the user
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout());
+               dialogPanel.add(new JLabel("<html>"+I18nManager.getText("dialog.earthdataauth.intro")+"</html>"), BorderLayout.NORTH);
+
+               // username and password fields
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BorderLayout());
+               JPanel usernamePasswordPanel = new JPanel();
+               usernamePasswordPanel.setLayout(new GridLayout(2, 2));
+
+               JLabel usernameLabel = new JLabel(I18nManager.getText("dialog.earthdataauth.user"));
+               usernamePasswordPanel.add(usernameLabel);
+               _usernameField = new JTextField("");
+               usernamePasswordPanel.add(_usernameField);
+
+               JLabel passwordLabel = new JLabel(I18nManager.getText("dialog.earthdataauth.password"));
+               usernamePasswordPanel.add(passwordLabel);
+               _passwordField = new JPasswordField("");
+               usernamePasswordPanel.add(_passwordField);
+               mainPanel.add(usernamePasswordPanel, BorderLayout.CENTER);
+
+               JPanel authStatusPanel = new JPanel();
+               authStatusPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
+               _authAccepted = new JLabel(" ");
+               authStatusPanel.add(_authAccepted);
+               mainPanel.add(authStatusPanel, BorderLayout.SOUTH);
+
+               mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+               dialogPanel.add(mainPanel, BorderLayout.CENTER);
+
+               // ok / cancel buttons at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               JButton checkButton = new JButton(I18nManager.getText("button.check"));
+               checkButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               testUsernameAndPassword();
+                       }
+               });
+               buttonPanel.add(checkButton);
+               JButton okButton = new JButton(I18nManager.getText("button.ok"));
+               okButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               finish();
+                       }
+               });
+               buttonPanel.add(okButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+               return dialogPanel;
+       }
+
+       private String getNewAuthString()
+       {
+               String username = _usernameField.getText();
+               String password = _passwordField.getText();
+               return Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
+       }
+
+       private void finish()
+       {
+               Config.setConfigString(Config.KEY_EARTHDATA_AUTH, getNewAuthString());
+               _dialog.dispose();
+       }
+
+       private void prefillCurrentAuth()
+       {
+               String authString = Config.getConfigString(Config.KEY_EARTHDATA_AUTH);
+               if (authString == null)
+               {
+                       _usernameField.setText("");
+                       _passwordField.setText("");
+               }
+               String decoded = new String(Base64.getDecoder().decode(authString));
+               if (decoded.contains(":"))
+               {
+                       _usernameField.setText(decoded.split(":", 2)[0]);
+                       _passwordField.setText(decoded.split(":", 2)[1]);
+               }
+               else
+               {
+                       _usernameField.setText("");
+                       _passwordField.setText("");
+               }
+
+               _authAccepted.setText(" ");
+       }
+
+       private void testUsernameAndPassword()
+       {
+               String username = _usernameField.getText();
+               String password = _passwordField.getText();
+               SrtmGl1Source srtmGL1 = new SrtmGl1Source();
+               try
+               {
+                       _authAccepted.setText("...");
+                       srtmGL1.testAuth(getNewAuthString());
+                       _authAccepted.setText(I18nManager.getText("dialog.earthdataauth.authaccepted"));
+               }
+               catch (SrtmSourceException e)
+               {
+                       _authAccepted.setText(I18nManager.getText("dialog.earthdataauth.authrejected") + ": " + e.getMessage());
+               }
+       }
+}
index 0350ce38ddc7af88508523fa9b2f9f7ef14d41be..311ff9290408adebd9c65ab0c2fe027337114477 100644 (file)
@@ -1,11 +1,5 @@
 package tim.prune.function.srtm;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
 import java.util.ArrayList;
 
 import javax.swing.JOptionPane;
@@ -14,7 +8,6 @@ import tim.prune.App;
 import tim.prune.GenericFunction;
 import tim.prune.GpsPrune;
 import tim.prune.I18nManager;
-import tim.prune.config.Config;
 import tim.prune.data.DoubleRange;
 import tim.prune.gui.ProgressDialog;
 
@@ -28,19 +21,20 @@ public class DownloadSrtmFunction extends GenericFunction implements Runnable
        private ProgressDialog _progress = null;
        /** Flag to check whether this function is currently running or not */
        private boolean _running = false;
-
+       private SrtmSource _srtmSource = null;
 
        /**
         * Constructor
         * @param inApp  App object
         */
-       public DownloadSrtmFunction(App inApp) {
+       public DownloadSrtmFunction(App inApp, SrtmSource inSrtmSource) {
                super(inApp);
+               _srtmSource = inSrtmSource;
        }
 
        /** @return name key */
        public String getNameKey() {
-               return "function.downloadsrtm";
+               return "function.downloadsrtm."+_srtmSource.getName();
        }
 
        /**
@@ -48,6 +42,17 @@ public class DownloadSrtmFunction extends GenericFunction implements Runnable
         */
        public void begin()
        {
+               if (! SrtmDiskCache.ensureCacheIsUsable())
+               {
+                       _app.showErrorMessage(getNameKey(), "error.downloadsrtm.nocache");
+                       return;
+               }
+               if (! _srtmSource.isReadyToUse())
+               {
+                       _app.showErrorMessage(getNameKey(), getNameKey() + ".needsetup");
+                       return;
+               }
+
                _running = true;
                if (_progress == null) {
                        _progress = new ProgressDialog(_parentFrame, getNameKey());
@@ -61,6 +66,14 @@ public class DownloadSrtmFunction extends GenericFunction implements Runnable
         * Run method using separate thread
         */
        public void run()
+       {
+               ArrayList<SrtmTile> tileList = buildCoveringTiles();
+               downloadTiles(tileList);
+               // Finished
+               _running = false;
+       }
+
+       private ArrayList<SrtmTile> buildCoveringTiles()
        {
                // Compile list of tiles to get
                ArrayList<SrtmTile> tileList = new ArrayList<SrtmTile>();
@@ -89,18 +102,16 @@ public class DownloadSrtmFunction extends GenericFunction implements Runnable
                        }
                }
 
-               downloadTiles(tileList);
-               // Finished
-               _running = false;
+               return tileList;
        }
 
-
        /**
         * Download the tiles of SRTM data
         * @param inTileList list of tiles to get
         */
        private void downloadTiles(ArrayList<SrtmTile> inTileList)
        {
+               String errorMessage = "";
                // Update progress bar
                if (_progress != null)
                {
@@ -108,62 +119,29 @@ public class DownloadSrtmFunction extends GenericFunction implements Runnable
                        _progress.setValue(0);
                }
 
-               String errorMessage = null;
-
-               // Check the cache is ok
-               final String diskCachePath = Config.getConfigString(Config.KEY_DISK_CACHE);
-               if (diskCachePath != null)
-               {
-                       File srtmDir = new File(diskCachePath, "srtm");
-                       if (!srtmDir.exists() && !srtmDir.mkdir()) {
-                               // can't create the srtm directory
-                               errorMessage = I18nManager.getText("error.downloadsrtm.nocache");
-                       }
-               }
-               else {
-                       // no cache set up
-                       errorMessage = I18nManager.getText("error.downloadsrtm.nocache");
-               }
-
-               // Get urls for each tile
-               URL[] urls = TileFinder.getUrls(inTileList);
                int numDownloaded = 0;
                for (int t=0; t<inTileList.size() && !_progress.isCancelled(); t++)
                {
-                       if (urls[t] != null)
+                       if (_srtmSource.isCached(inTileList.get(t)))
                        {
-                               // Define streams
-                               FileOutputStream outStream = null;
-                               InputStream inStream = null;
-                               try
+                               System.out.println(inTileList.get(t).getTileName()+" already in cache, nothing to do");
+                               continue;
+                       }
+
+                       boolean success;
+                       try
+                       {
+                               success = _srtmSource.downloadTile(inTileList.get(t));
+                               if (success)
                                {
-                                       // Set progress
-                                       _progress.setValue(t);
-                                       // See if we've already got this tile or not
-                                       File outputFile = getFileToWrite(urls[t]);
-                                       if (outputFile != null)
-                                       {
-                                               // System.out.println("Download: Need to download: " + urls[t]);
-                                               outStream = new FileOutputStream(outputFile);
-                                               URLConnection conn = urls[t].openConnection();
-                                               conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
-                                               inStream = conn.getInputStream();
-                                               // Copy all the bytes to the file
-                                               int c;
-                                               while ((c = inStream.read()) != -1)
-                                               {
-                                                       outStream.write(c);
-                                               }
-
-                                               numDownloaded++;
-                                       }
-                                       // else System.out.println("Don't need to download: " + urls[t].getFile());
-                               }
-                               catch (IOException ioe) {errorMessage = ioe.getClass().getName() + " - " + ioe.getMessage();
+                                       numDownloaded++;
                                }
-                               // Make sure streams are closed
-                               try {inStream.close();} catch (Exception e) {}
-                               try {outStream.close();} catch (Exception e) {}
+                               // Set progress
+                               _progress.setValue(t + 1);
+                       }
+                       catch (SrtmSourceException e)
+                       {
+                               errorMessage += e.getMessage();
                        }
                }
 
@@ -172,10 +150,11 @@ public class DownloadSrtmFunction extends GenericFunction implements Runnable
                        return;
                }
 
-               if (errorMessage != null) {
+               if (! errorMessage.equals("")) {
                        _app.showErrorMessageNoLookup(getNameKey(), errorMessage);
+                       return;
                }
-               else if (numDownloaded == 1)
+               if (numDownloaded == 1)
                {
                        JOptionPane.showMessageDialog(_parentFrame, I18nManager.getTextWithNumber("confirm.downloadsrtm.1", numDownloaded),
                                I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
@@ -186,37 +165,7 @@ public class DownloadSrtmFunction extends GenericFunction implements Runnable
                                I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
                }
                else if (inTileList.size() > 0) {
-                       _app.showErrorMessage(getNameKey(), "confirm.downloadsrtm.none");
+                       JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("confirm.downloadsrtm.none"));
                }
        }
-
-       /**
-        * See whether the SRTM file is already available locally
-        * @param inUrl URL for online resource
-        * @return file object to write to, or null if already there
-        */
-       private static File getFileToWrite(URL inUrl)
-       {
-               String diskCachePath = Config.getConfigString(Config.KEY_DISK_CACHE);
-               if (diskCachePath != null)
-               {
-                       File srtmDir = new File(diskCachePath, "srtm");
-                       if (srtmDir.exists() && srtmDir.isDirectory() && srtmDir.canRead())
-                       {
-                               File srtmFile = new File(srtmDir, new File(inUrl.getFile()).getName());
-                               if (!srtmFile.exists() || !srtmFile.canRead() || srtmFile.length() <= 400) {
-                                       return srtmFile;
-                               }
-                       }
-               }
-               return null;
-       }
-
-       /**
-        * @return true if a thread is currently running
-        */
-       public boolean isRunning()
-       {
-               return _running;
-       }
 }
index 15997448e038e6f4bb6e9f4b9437c99491b18a6e..927d2cd7bc013c4a9744e17fbb12797afdc73685 100644 (file)
@@ -1,12 +1,6 @@
 package tim.prune.function.srtm;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.net.URL;
 import java.util.ArrayList;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
 
 import javax.swing.JOptionPane;
 
@@ -15,14 +9,12 @@ import tim.prune.DataSubscriber;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
-import tim.prune.config.Config;
 import tim.prune.data.Altitude;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.Track;
 import tim.prune.data.UnitSetLibrary;
 import tim.prune.gui.ProgressDialog;
-import tim.prune.tips.TipManager;
 import tim.prune.undo.UndoLookupSrtm;
 
 /**
@@ -38,13 +30,9 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
        private Track _track = null;
        /** Flag for whether this is a real track or a terrain one */
        private boolean _normalTrack = true;
-       /** Flag set when any tiles had to be downloaded (rather than just loaded locally) */
-       private boolean _hadToDownload = false;
        /** Flag to check whether this function is currently running or not */
        private boolean _running = false;
 
-       /** Expected size of hgt file in bytes */
-       private static final long HGT_SIZE = 2884802L;
        /** Altitude below which is considered void */
        private static final int VOID_VAL = -32768;
 
@@ -84,7 +72,10 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
        private void begin(Track inTrack, boolean inNormalTrack)
        {
                _running = true;
-               _hadToDownload = false;
+               if (! SrtmDiskCache.ensureCacheIsUsable())
+               {
+                       _app.showErrorMessage(getNameKey(), "error.cache.notthere");
+               }
                if (_progress == null) {
                        _progress = new ProgressDialog(_parentFrame, getNameKey());
                }
@@ -131,8 +122,7 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
                for (int i = 0; i < _track.getNumPoints(); i++)
                {
                        // Consider points which don't have altitudes or have zero values
-                       if (!_track.getPoint(i).hasAltitude()
-                               || (overwriteZeros && _track.getPoint(i).getAltitude().getValue() == 0))
+                       if (needsAltitude(_track.getPoint(i), overwriteZeros))
                        {
                                SrtmTile tile = new SrtmTile(_track.getPoint(i));
                                boolean alreadyGot = false;
@@ -148,12 +138,23 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
                lookupValues(tileList, overwriteZeros);
                // Finished
                _running = false;
-               // Show tip if lots of online lookups were necessary
-               if (_hadToDownload) {
-                       _app.showTip(TipManager.Tip_DownloadSrtm);
-               }
        }
 
+       /**
+        * true if we need to set the altitude of this point
+        */
+       private boolean needsAltitude(DataPoint point, boolean overwriteZeros)
+       {
+               if (!point.hasAltitude())
+               {
+                       return true;
+               }
+               if (overwriteZeros && point.getAltitude().getValue() == 0)
+               {
+                       return true;
+               }
+               return false;
+       }
 
        /**
         * Lookup the values from SRTM data
@@ -170,94 +171,79 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
                        _progress.setMaximum(inTileList.size());
                        _progress.setValue(0);
                }
-               String errorMessage = null;
-               // Get urls for each tile
-               URL[] urls = TileFinder.getUrls(inTileList);
+               String errorMessage = "";
                for (int t=0; t<inTileList.size() && !_progress.isCancelled(); t++)
                {
-                       if (urls[t] != null)
+                       SrtmTile tile = inTileList.get(t);
+                       SrtmSource srtmSource = tile.findBestCachedSource();
+
+                       if (srtmSource == null)
                        {
-                               SrtmTile tile = inTileList.get(t);
-                               try
+                               errorMessage += "Tile "+tile.getTileName()+" not in cache!\n";
+                               continue;
+                       }
+
+                       // Set progress
+                       _progress.setValue(t);
+
+                       int[] heights;
+                       try {
+                               heights = srtmSource.getTileHeights(tile);
+                       }
+                       catch (SrtmSourceException e)
+                       {
+                               errorMessage += e.getMessage();
+                               continue;
+                       }
+                       int rowSize = srtmSource.getRowSize(tile);
+                       if (rowSize <= 0)
+                       {
+                               errorMessage += "Tile "+tile.getTileName()+" is corrupted";
+                       }
+
+                       // Loop over all points in track, try to apply altitude from array
+                       for (int p = 0; p < _track.getNumPoints(); p++)
+                       {
+                               DataPoint point = _track.getPoint(p);
+                               if (needsAltitude(point, inOverwriteZeros))
                                {
-                                       // Set progress
-                                       _progress.setValue(t);
-                                       final int ARRLENGTH = 1201 * 1201;
-                                       int[] heights = new int[ARRLENGTH];
-                                       // Open zipinputstream on url and check size
-                                       ZipInputStream inStream = getStreamToHgtFile(urls[t]);
-                                       boolean entryOk = false;
-                                       if (inStream != null)
+                                       if (new SrtmTile(point).equals(tile))
                                        {
-                                               ZipEntry entry = inStream.getNextEntry();
-                                               entryOk = (entry != null && entry.getSize() == HGT_SIZE);
-                                               if (entryOk)
+                                               double x = (point.getLongitude().getDouble() - tile.getLongitude()) * (rowSize - 1);
+                                               double y = rowSize - (point.getLatitude().getDouble() - tile.getLatitude()) * (rowSize - 1);
+                                               int idx1 = ((int)y)*rowSize + (int)x;
+                                               try
                                                {
-                                                       // Read entire file contents into one byte array
-                                                       for (int i = 0; i < ARRLENGTH; i++)
+                                                       int[] fouralts = {heights[idx1], heights[idx1+1], heights[idx1-rowSize], heights[idx1-rowSize+1]};
+                                                       int numVoids = (fouralts[0]==VOID_VAL?1:0) + (fouralts[1]==VOID_VAL?1:0)
+                                                               + (fouralts[2]==VOID_VAL?1:0) + (fouralts[3]==VOID_VAL?1:0);
+                                                       // if (numVoids > 0) System.out.println(numVoids + " voids found");
+                                                       double altitude = 0.0;
+                                                       switch (numVoids)
                                                        {
-                                                               heights[i] = inStream.read() * 256 + inStream.read();
-                                                               if (heights[i] >= 32768) {heights[i] -= 65536;}
+                                                       case 0: altitude = bilinearInterpolate(fouralts, x, y); break;
+                                                       case 1: altitude = bilinearInterpolate(fixVoid(fouralts), x, y); break;
+                                                       case 2:
+                                                       case 3: altitude = averageNonVoid(fouralts); break;
+                                                       default: altitude = VOID_VAL;
                                                        }
-                                               }
-                                               // else {
-                                               //      System.out.println("length not ok: " + entry.getSize());
-                                               // }
-                                               // Close stream from url
-                                               inStream.close();
-                                       }
-
-                                       if (entryOk)
-                                       {
-                                               // Loop over all points in track, try to apply altitude from array
-                                               for (int p = 0; p < _track.getNumPoints(); p++)
-                                               {
-                                                       DataPoint point = _track.getPoint(p);
-                                                       if (!point.hasAltitude()
-                                                               || (inOverwriteZeros && point.getAltitude().getValue() == 0))
+                                                       // Special case for terrain tracks, don't interpolate voids yet
+                                                       if (!_normalTrack && numVoids > 0) {
+                                                               altitude = VOID_VAL;
+                                                       }
+                                                       if (altitude != VOID_VAL)
                                                        {
-                                                               if (new SrtmTile(point).equals(tile))
-                                                               {
-                                                                       double x = (point.getLongitude().getDouble() - tile.getLongitude()) * 1200;
-                                                                       double y = 1201 - (point.getLatitude().getDouble() - tile.getLatitude()) * 1200;
-                                                                       int idx1 = ((int)y)*1201 + (int)x;
-                                                                       try
-                                                                       {
-                                                                               int[] fouralts = {heights[idx1], heights[idx1+1], heights[idx1-1201], heights[idx1-1200]};
-                                                                               int numVoids = (fouralts[0]==VOID_VAL?1:0) + (fouralts[1]==VOID_VAL?1:0)
-                                                                                       + (fouralts[2]==VOID_VAL?1:0) + (fouralts[3]==VOID_VAL?1:0);
-                                                                               // if (numVoids > 0) System.out.println(numVoids + " voids found");
-                                                                               double altitude = 0.0;
-                                                                               switch (numVoids)
-                                                                               {
-                                                                                       case 0: altitude = bilinearInterpolate(fouralts, x, y); break;
-                                                                                       case 1: altitude = bilinearInterpolate(fixVoid(fouralts), x, y); break;
-                                                                                       case 2:
-                                                                                       case 3: altitude = averageNonVoid(fouralts); break;
-                                                                                       default: altitude = VOID_VAL;
-                                                                               }
-                                                                               // Special case for terrain tracks, don't interpolate voids yet
-                                                                               if (!_normalTrack && numVoids > 0) {
-                                                                                       altitude = VOID_VAL;
-                                                                               }
-                                                                               if (altitude != VOID_VAL)
-                                                                               {
-                                                                                       point.setFieldValue(Field.ALTITUDE, ""+altitude, false);
-                                                                                       // depending on settings, this value may have been added as feet, we need to force metres
-                                                                                       point.getAltitude().reset(new Altitude((int)altitude, UnitSetLibrary.UNITS_METRES));
-                                                                                       numAltitudesFound++;
-                                                                               }
-                                                                       }
-                                                                       catch (ArrayIndexOutOfBoundsException obe) {
-                                                                               // System.err.println("lat=" + point.getLatitude().getDouble() + ", x=" + x + ", y=" + y + ", idx=" + idx1);
-                                                                       }
-                                                               }
+                                                               point.setFieldValue(Field.ALTITUDE, ""+altitude, false);
+                                                               // depending on settings, this value may have been added as feet, we need to force metres
+                                                               point.getAltitude().reset(new Altitude((int)altitude, UnitSetLibrary.UNITS_METRES));
+                                                               numAltitudesFound++;
                                                        }
                                                }
+                                               catch (ArrayIndexOutOfBoundsException obe) {
+                                                       errorMessage += "Point not in tile? lat=" + point.getLatitude().getDouble() + ", x=" + x + ", y=" + y + ", idx=" + idx1+"\n";
+                                               }
                                        }
                                }
-                               catch (IOException ioe) {errorMessage = ioe.getClass().getName() + " - " + ioe.getMessage();
-                               }
                        }
                }
 
@@ -266,6 +252,10 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
                        return;
                }
 
+               if (! errorMessage.equals("")) {
+                       _app.showErrorMessageNoLookup(getNameKey(), errorMessage);
+                       return;
+               }
                if (numAltitudesFound > 0)
                {
                        // Inform app including undo information
@@ -278,9 +268,6 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
                                        I18nManager.getTextWithNumber("confirm.lookupsrtm", numAltitudesFound));
                        }
                }
-               else if (errorMessage != null) {
-                       _app.showErrorMessageNoLookup(getNameKey(), errorMessage);
-               }
                else if (inTileList.size() > 0) {
                        _app.showErrorMessage(getNameKey(), "error.lookupsrtm.nonefound");
                }
@@ -289,36 +276,6 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
                }
        }
 
-       /**
-        * See whether the SRTM file is already available locally first, then try online
-        * @param inUrl URL for online resource
-        * @return ZipInputStream either on the local file or on the downloaded zip file
-        */
-       private ZipInputStream getStreamToHgtFile(URL inUrl)
-       throws IOException
-       {
-               String diskCachePath = Config.getConfigString(Config.KEY_DISK_CACHE);
-               if (diskCachePath != null)
-               {
-                       File srtmDir = new File(diskCachePath, "srtm");
-                       if (srtmDir.exists() && srtmDir.isDirectory() && srtmDir.canRead())
-                       {
-                               File srtmFile = new File(srtmDir, new File(inUrl.getFile()).getName());
-                               if (srtmFile.exists() && srtmFile.isFile() && srtmFile.canRead()
-                                       && srtmFile.length() > 400)
-                               {
-                                       // System.out.println("Lookup: Using file " + srtmFile.getAbsolutePath());
-                                       // File found, use this one
-                                       return new ZipInputStream(new FileInputStream(srtmFile));
-                               }
-                       }
-               }
-               // System.out.println("Lookup: Trying online: " + inUrl.toString());
-               _hadToDownload = true;
-               // MAYBE: Only download if we're in online mode?
-               return new ZipInputStream(inUrl.openStream());
-       }
-
        /**
         * Perform a bilinear interpolation on the given altitude array
         * @param inAltitudes array of four altitude values on corners of square (bl, br, tl, tr)
diff --git a/src/tim/prune/function/srtm/Srtm3Source.java b/src/tim/prune/function/srtm/Srtm3Source.java
new file mode 100644 (file)
index 0000000..14fad59
--- /dev/null
@@ -0,0 +1,164 @@
+package tim.prune.function.srtm;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.HttpURLConnection;
+
+import tim.prune.GpsPrune;
+import tim.prune.I18nManager;
+
+public class Srtm3Source extends SrtmSource {
+       /** URL prefix for all tiles */
+       private static final String URL_PREFIX = "https://dds.cr.usgs.gov/srtm/version2_1/SRTM3/";
+       /** Directory names for each continent */
+       private static final String[] CONTINENTS = {"", "Eurasia", "North_America", "Australia",
+                                                   "Islands", "South_America", "Africa"};
+       private byte[] _continents_lookup;
+
+
+       public Srtm3Source()
+       {
+               _continents_lookup = populateContinents();
+       }
+
+       public String getNameKey()
+       {
+               return "function.downloadsrtm." + getName();
+       }
+
+       public String getName()
+       {
+               return "SRTM3_v21";
+       }
+
+       protected String getSourceExtension()
+       {
+               return ".hgt.zip";
+       }
+
+       /**
+        * Read the dat file and get the contents
+        * @return byte array containing file contents
+        */
+       private static byte[] populateContinents()
+       {
+               InputStream in = null;
+               try
+               {
+                       // Need absolute path to dat file
+                       in = Srtm3Source.class.getResourceAsStream("/tim/prune/function/srtm/srtmtiles.dat");
+                       if (in != null)
+                       {
+                               byte[] buffer = new byte[in.available()];
+                               in.read(buffer);
+                               in.close();
+                               return buffer;
+                       }
+               }
+               catch (java.io.IOException e) {
+                       System.err.println("Exception trying to read srtmtiles.dat : " + e.getMessage());
+               }
+               finally
+               {
+                       try {
+                               in.close();
+                       }
+                       catch (Exception e) {} // ignore
+               }
+               return null;
+       }
+
+       /**
+        * Get the Url for the given tile
+        * @param inTile Tile to get
+        * @return URL
+        */
+       private URL buildUrl(SrtmTile inTile)
+               throws SrtmSourceException
+       {
+               
+               // Get byte from lookup array
+               int idx = (inTile.getLatitude() + 59)*360 + (inTile.getLongitude() + 180);
+               int dir;
+               try
+               {
+                       dir = _continents_lookup[idx];
+               }
+               catch (ArrayIndexOutOfBoundsException e)
+               {
+                       throw new SrtmSourceException("Could not find continent for tile "+inTile.getTileName());
+               }
+               try
+               {
+                       return new URL(URL_PREFIX + CONTINENTS[dir] + "/" + inTile.getTileName() + getSourceExtension());
+               }
+               catch (MalformedURLException e)
+               {
+                       throw new SrtmSourceException("Could not build URL for tile "+inTile.getTileName());
+               }
+       }
+
+       public boolean isReadyToUse()
+       {
+               return true;
+       }
+
+       public boolean downloadTile(SrtmTile inTile)
+               throws SrtmSourceException
+       {
+               int redirects = 5;
+               URL tileUrl = buildUrl(inTile);
+               File outputFile = getCacheFileName(inTile);
+               System.out.println("Download: Need to download: " + tileUrl);
+
+               try
+               {
+                       HttpURLConnection conn = (HttpURLConnection) tileUrl.openConnection();
+
+                       // Define streams
+                       FileOutputStream outStream = null;
+                       InputStream inStream = null;
+
+                       conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
+
+                       int status = conn.getResponseCode();
+                       if (status == 200)
+                       {
+                               inStream = conn.getInputStream();
+                       }
+                       else if (status == 404)
+                       {
+                               throw new SrtmSourceException("Tile not found: "+conn.getURL());
+                       }
+                       else
+                       {
+                               throw new SrtmSourceException("Invalid response from server: " +status+conn.getContent());
+                       }
+
+                       outStream = new FileOutputStream(outputFile);
+
+                       int c;
+                       while ((c = inStream.read()) != -1)
+                       {
+                               outStream.write(c);
+                       }
+                       // Make sure streams are closed
+                       try {inStream.close();} catch (Exception e) {}
+                       try {outStream.close();} catch (Exception e) {}
+                       return true;
+               }
+               catch (IOException e)
+               {
+                       throw new SrtmSourceException("Error while downloading tile "+inTile.getTileName()+": "+e.getMessage());
+               }
+       }
+
+       public int getRowSize(SrtmTile inTile)
+       {
+               return 1201;
+       }
+}
diff --git a/src/tim/prune/function/srtm/SrtmDiskCache.java b/src/tim/prune/function/srtm/SrtmDiskCache.java
new file mode 100644 (file)
index 0000000..80159e0
--- /dev/null
@@ -0,0 +1,49 @@
+package tim.prune.function.srtm;
+
+import java.io.File;
+
+import tim.prune.config.Config;
+import tim.prune.I18nManager;
+
+public class SrtmDiskCache {
+
+       private static boolean _cacheIsUsable = false;
+       private static File _cacheDir = null;
+
+       public static boolean ensureCacheIsUsable()
+       {
+
+               if (_cacheIsUsable)
+               {
+                       return true;
+               }
+               // Check the cache is ok
+               String diskCachePath = Config.getConfigString(Config.KEY_DISK_CACHE);
+               if (diskCachePath == null)
+               {
+                       return false;
+               }
+               File srtmDir = new File(diskCachePath, "srtm");
+               if (!srtmDir.exists() && !srtmDir.mkdir()) {
+                       // can't create the srtm directory
+                       return false;
+               }
+               _cacheIsUsable = true;
+               _cacheDir = srtmDir;
+               return true;
+       }
+
+       public static File getCacheDir(String inSourceName)
+       {
+               if (_cacheDir == null)
+               {
+                       ensureCacheIsUsable();
+               }
+               File cacheDir = new File(_cacheDir, inSourceName);
+               if (!cacheDir.exists() && !cacheDir.mkdir()) {
+                       // can't create the srtm directory
+                       return null;
+               }
+               return cacheDir;
+       }
+}
diff --git a/src/tim/prune/function/srtm/SrtmGl1Source.java b/src/tim/prune/function/srtm/SrtmGl1Source.java
new file mode 100644 (file)
index 0000000..51e6d77
--- /dev/null
@@ -0,0 +1,176 @@
+package tim.prune.function.srtm;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.CookiePolicy;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.HttpURLConnection;
+
+import tim.prune.App;
+import tim.prune.GpsPrune;
+import tim.prune.config.Config;
+
+/**
+ * Create an account at: https://urs.earthdata.nasa.gov/users/new
+ * Data policy: https://lpdaac.usgs.gov/data/data-citation-and-policies/
+ *
+ */
+
+public class SrtmGl1Source extends SrtmSource {
+       /** URL prefix for all tiles */
+       private static final String URL_PREFIX = "https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL1.003/2000.02.11/";
+       /** Auth URL */
+       private static final String AUTH_URL = "urs.earthdata.nasa.gov";
+
+
+       public SrtmGl1Source()
+       {
+               CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
+       }
+
+       public String getName()
+       {
+               return "SRTMGL1_v003";
+       }
+
+       protected String getSourceExtension()
+       {
+               return ".SRTMGL1.hgt.zip";
+       }
+
+       private URL buildUrl(SrtmTile inTile)
+               throws SrtmSourceException
+       {
+               try {
+                       return new URL(URL_PREFIX + inTile.getTileName() + getSourceExtension());
+               }
+               catch (MalformedURLException e)
+               {
+                       throw new SrtmSourceException(e.getMessage());
+               }
+       }
+
+       public boolean isReadyToUse()
+       {
+               return getAuth() != null;
+       }
+
+       private String getAuth()
+       {
+               String authString = Config.getConfigString(Config.KEY_EARTHDATA_AUTH);
+               if (authString != null)
+               {
+                       return "Basic " + authString; 
+               }
+               else
+               {
+                       return null;
+               }
+       }
+
+       public boolean downloadTile(SrtmTile inTile)
+               throws SrtmSourceException
+       {
+               return downloadTile(inTile, getAuth());
+       }
+
+       private boolean downloadTile(SrtmTile inTile, String auth)
+               throws SrtmSourceException
+       {
+               URL tileUrl = buildUrl(inTile);
+               File outputFile = getCacheFileName(inTile);
+               System.out.println("Download: Need to download: " + tileUrl);
+               try
+               {
+                       HttpURLConnection conn = (HttpURLConnection) tileUrl.openConnection();
+                       long fileLength = 0L;
+
+                       // Define streams
+                       FileOutputStream outStream = null;
+                       InputStream inStream = null;
+
+                       // Documentation about HTTP interface at:
+                       // https://wiki.earthdata.nasa.gov/display/EL/How+To+Access+Data+With+Java
+                       int redirects = 0;
+
+                       while (redirects < 10) {
+                               redirects++;
+
+                               conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
+                               conn.setInstanceFollowRedirects(false);
+                               conn.setUseCaches(false);
+                               if (conn.getURL().getHost().equals(AUTH_URL))
+                               {
+                                       conn.setRequestProperty("Authorization", auth);
+                               }
+
+                               int status = conn.getResponseCode();
+                               if (status == 200)
+                               {
+                                       // Found the tile, we're good
+                                       inStream = conn.getInputStream();
+                                       fileLength = conn.getContentLengthLong();
+                                       break;
+                               }
+                               else if (status == 302)
+                               {
+                                       // redirected to SSO server then back to original resource
+                                       String newUrl = conn.getHeaderField("Location");
+                                       conn = (HttpURLConnection) (new URL(newUrl)).openConnection();
+                               }
+                               else if (status == 404)
+                               {
+                                       throw new SrtmSourceException("Tile " + inTile.getTileName() + " not found at " + conn.getURL());
+                               }
+                               else
+                               {
+                                       throw new SrtmSourceException("Invalid response from server: " + status + conn.getResponseMessage());
+                               }
+                       }
+
+                       // _progress.setValue(t * 10 + 1);
+                       outStream = new FileOutputStream(outputFile);
+
+                       // Copy all the bytes to the file
+                       int c;
+                       long written = 0L;
+                       while ((c = inStream.read()) != -1)
+                       {
+                               outStream.write(c);
+                               written++;
+                               // _progress.setValue(t * 10 + 1 + (int) ((10 * written) / fileLength));
+                       }
+                       // Make sure streams are closed
+                       try {inStream.close();} catch (Exception e) {}
+                       try {outStream.close();} catch (Exception e) {}
+                       return true;
+               }
+               catch (IOException e)
+               {
+                       throw new SrtmSourceException("Error while downloading tile " + inTile.getTileName() + ": "+e.getMessage());
+               }
+       }
+
+       public boolean testAuth(String auth)
+               throws SrtmSourceException
+       {
+               // The only thing special about this tile is that it's the smallest tile
+               // It covers small islands in Malaysia
+               SrtmTile testTile = new SrtmTile(7, 117);
+               if (isCached(testTile))
+               {
+                       getCacheFileName(testTile).delete();
+               }
+               return downloadTile(testTile, auth);
+       }
+
+       public int getRowSize(SrtmTile inTile)
+       {
+               return 3601;
+       }
+}
diff --git a/src/tim/prune/function/srtm/SrtmSource.java b/src/tim/prune/function/srtm/SrtmSource.java
new file mode 100644 (file)
index 0000000..5651a3f
--- /dev/null
@@ -0,0 +1,69 @@
+package tim.prune.function.srtm;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public abstract class SrtmSource {
+       public abstract String getName();
+       public abstract boolean isReadyToUse();
+       public abstract boolean downloadTile(SrtmTile inTile)
+               throws SrtmSourceException;
+       public abstract int getRowSize(SrtmTile inTile);
+       protected abstract String getSourceExtension();
+
+       public int[] getTileHeights(SrtmTile inTile)
+               throws SrtmSourceException
+       {
+               File cacheFileName = getCacheFileName(inTile);
+               if (cacheFileName == null)
+               {
+                       throw new SrtmSourceException("Tile "+inTile.getTileName()+" not in cache");
+               }
+               try
+               {
+                       ZipInputStream inStream = new ZipInputStream(new FileInputStream(cacheFileName));
+                       ZipEntry entry = inStream.getNextEntry();
+                       int rowSize = getRowSize(inTile);
+                       int tileSize = rowSize * rowSize;
+                       if (entry.getSize() != 2 * tileSize)
+                       {
+                               throw new SrtmSourceException("Tile file "+cacheFileName+" does not have the expected size");
+                       }
+                       int[] heights = new int[tileSize];
+                       // Read entire file contents into one byte array
+                       for (int i = 0; i < heights.length; i++)
+                       {
+                               heights[i] = inStream.read() * 256 + inStream.read();
+                               if (heights[i] >= 32768) {heights[i] -= 65536;}
+                       }
+                       // Close stream
+                       inStream.close();
+                       return heights;
+               }
+               catch (IOException e)
+               {
+                       throw new SrtmSourceException("Failure opening "+cacheFileName+" for reading:"+e.getMessage());
+               }
+
+       }
+
+       protected File getCacheDir()
+       {
+               return SrtmDiskCache.getCacheDir(getName());
+       }
+
+       protected File getCacheFileName(SrtmTile inTile)
+       {
+               String fileName = inTile.getTileName() + getSourceExtension();
+               return new File(getCacheDir(), fileName);
+       }
+
+       public boolean isCached(SrtmTile inTile)
+       {
+               return getCacheFileName(inTile).exists();
+       }
+}
diff --git a/src/tim/prune/function/srtm/SrtmSourceException.java b/src/tim/prune/function/srtm/SrtmSourceException.java
new file mode 100644 (file)
index 0000000..37b2db5
--- /dev/null
@@ -0,0 +1,7 @@
+package tim.prune.function.srtm;
+
+public class SrtmSourceException extends Exception {
+       public SrtmSourceException(String message) {
+               super(message);
+       }
+}
index 301bbaf2ccb7849f1378a6f910999c188965a12a..7bc1d386419732c8d7ab9b048bcf06407d06a36b 100644 (file)
@@ -67,7 +67,20 @@ public class SrtmTile
                        + (_longitude >= 0?"E":"W")
                        + (Math.abs(_longitude) < 100?"0":"")
                        + (Math.abs(_longitude) < 10?"0":"")
-                       + Math.abs(_longitude)
-                       + ".hgt.zip";
+                       + Math.abs(_longitude);
+       }
+
+       public SrtmSource findBestCachedSource()
+       {
+               SrtmSource[] sources = {new SrtmGl1Source(),
+                                       new Srtm3Source() };
+               for (int i = 0; i < sources.length; i++)
+               {
+                       if (sources[i].isCached(this))
+                       {
+                               return sources[i];
+                       }
+               }
+               return null;
        }
 }
diff --git a/src/tim/prune/function/srtm/TileFinder.java b/src/tim/prune/function/srtm/TileFinder.java
deleted file mode 100644 (file)
index 60a9479..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package tim.prune.function.srtm;
-
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-
-
-/**
- * Class to get the URLs of the SRTM tiles
- * using the srtmtiles.dat file
- */
-public abstract class TileFinder
-{
-       /** URL prefix for all tiles */
-       private static final String URL_PREFIX = "https://dds.cr.usgs.gov/srtm/version2_1/SRTM3/";
-       /** Directory names for each continent */
-       private static final String[] CONTINENTS = {"", "Eurasia", "North_America", "Australia",
-               "Islands", "South_America", "Africa"};
-
-
-       /**
-        * Get the Urls for the given list of tiles
-        * @param inTiles list of Tiles to get
-        * @return array of URLs
-        */
-       public static URL[] getUrls(ArrayList<SrtmTile> inTiles)
-       {
-               if (inTiles == null || inTiles.size() < 1) {return null;}
-               URL[] urls = new URL[inTiles.size()];
-               // Read dat file into array
-               byte[] lookup = readDatFile();
-               for (int t=0; t<inTiles.size(); t++)
-               {
-                       SrtmTile tile = inTiles.get(t);
-                       // Get byte from lookup array
-                       int idx = (tile.getLatitude() + 59)*360 + (tile.getLongitude() + 180);
-                       try
-                       {
-                               int dir = lookup[idx];
-                               if (dir > 0) {
-                                       try {
-                                               urls[t] = new URL(URL_PREFIX + CONTINENTS[dir] + "/" + tile.getTileName());
-                                       } catch (MalformedURLException e) {} // ignore error, url stays null
-                               }
-                       } catch (ArrayIndexOutOfBoundsException e) {} // ignore error, url stays null
-               }
-               return urls;
-       }
-
-       /**
-        * Read the dat file and get the contents
-        * @return byte array containing file contents
-        */
-       private static byte[] readDatFile()
-       {
-               InputStream in = null;
-               try
-               {
-                       // Need absolute path to dat file
-                       in = TileFinder.class.getResourceAsStream("/tim/prune/function/srtm/srtmtiles.dat");
-                       if (in != null)
-                       {
-                               byte[] buffer = new byte[in.available()];
-                               in.read(buffer);
-                               in.close();
-                               return buffer;
-                       }
-               }
-               catch (java.io.IOException e) {
-                       System.err.println("Exception trying to read srtmtiles.dat : " + e.getMessage());
-               }
-               finally
-               {
-                       try {
-                               in.close();
-                       }
-                       catch (Exception e) {} // ignore
-               }
-               return null;
-       }
-}
index 25640d8e5072bfc2e70acfc3e26c7de208a494e3..2300296760fa1febfeb8f2dc59d393ca35bb639a 100644 (file)
@@ -36,7 +36,8 @@ public abstract class DisplayUtils
                if (inNumSecs < 86400L) return "" + (inNumSecs / 60 / 60) + I18nManager.getText("display.range.time.hours")
                        + " " + ((inNumSecs / 60) % 60) + I18nManager.getText("display.range.time.mins");
                if (inNumSecs < 432000L) return "" + (inNumSecs / 86400L) + I18nManager.getText("display.range.time.days")
-                       + " " + (inNumSecs / 60 / 60) % 24 + I18nManager.getText("display.range.time.hours");
+                       + " " + (inNumSecs / 60 / 60) % 24 + I18nManager.getText("display.range.time.hours")
+                       + " " + ((inNumSecs / 60) % 60) + I18nManager.getText("display.range.time.mins");
                if (inNumSecs < 86400000L) return "" + (inNumSecs / 86400L) + I18nManager.getText("display.range.time.days");
                return "big";
        }
index bce3b4d67b0a6df980b66882f175245af1134155..bd0d37813d40c8d9259707999a7285bafddec348 100644 (file)
@@ -34,6 +34,9 @@ import tim.prune.function.SearchOpenCachingDeFunction;
 import tim.prune.function.browser.UrlGenerator;
 import tim.prune.function.browser.WebMapFunction;
 import tim.prune.function.search.SearchMapillaryFunction;
+import tim.prune.function.srtm.DownloadSrtmFunction;
+import tim.prune.function.srtm.SrtmGl1Source;
+import tim.prune.function.srtm.Srtm3Source;
 
 /**
  * Class to manage the menu bar and tool bar,
@@ -77,6 +80,7 @@ public class MenuManager implements DataSubscriber
        private JMenuItem _reverseItem = null;
        private JMenuItem _addTimeOffsetItem = null;
        private JMenuItem _addAltitudeOffsetItem = null;
+       private JMenuItem _removeAltitudesItem = null;
        private JMenuItem _mergeSegmentsItem = null;
        private JMenuItem _rearrangeWaypointsItem = null;
        private JMenuItem _splitSegmentsItem = null;
@@ -93,7 +97,7 @@ public class MenuManager implements DataSubscriber
        private JMenuItem _getGpsiesItem = null;
        private JMenuItem _uploadGpsiesItem = null;
        private JMenuItem _lookupSrtmItem = null;
-       private JMenuItem _downloadSrtmItem = null;
+       private JMenu     _downloadSrtmMenu = null;
        private JMenuItem _nearbyWikipediaItem = null;
        private JMenuItem _nearbyOsmPoiItem = null;
        private JMenuItem _showPeakfinderItem = null;
@@ -257,8 +261,15 @@ public class MenuManager implements DataSubscriber
                // SRTM
                _lookupSrtmItem = makeMenuItem(FunctionLibrary.FUNCTION_LOOKUP_SRTM, false);
                onlineMenu.add(_lookupSrtmItem);
-               _downloadSrtmItem = makeMenuItem(FunctionLibrary.FUNCTION_DOWNLOAD_SRTM, false);
-               onlineMenu.add(_downloadSrtmItem);
+               // Download SRTM sub-menu
+               _downloadSrtmMenu = new JMenu(I18nManager.getText("function.downloadsrtm"));
+               _downloadSrtmMenu.setEnabled(false);
+               JMenuItem downloadStrmGl1Item = makeMenuItem(new DownloadSrtmFunction(_app, new SrtmGl1Source()));
+               _downloadSrtmMenu.add(downloadStrmGl1Item);
+               JMenuItem downloadStrm3Item = makeMenuItem(new DownloadSrtmFunction(_app, new Srtm3Source()));
+               _downloadSrtmMenu.add(downloadStrm3Item);
+               onlineMenu.add(_downloadSrtmMenu);
+
                // Get gpsies tracks
                _getGpsiesItem = makeMenuItem(FunctionLibrary.FUNCTION_GET_GPSIES, false);
                onlineMenu.add(_getGpsiesItem);
@@ -425,6 +436,8 @@ public class MenuManager implements DataSubscriber
                rangeMenu.add(_addTimeOffsetItem);
                _addAltitudeOffsetItem = makeMenuItem(FunctionLibrary.FUNCTION_ADD_ALTITUDE_OFFSET, false);
                rangeMenu.add(_addAltitudeOffsetItem);
+               _removeAltitudesItem = makeMenuItem(FunctionLibrary.FUNCTION_REMOVE_ALTITUDES, false);
+               rangeMenu.add(_removeAltitudesItem);
                _mergeSegmentsItem = new JMenuItem(I18nManager.getText("menu.range.mergetracksegments"));
                _mergeSegmentsItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
@@ -651,6 +664,8 @@ public class MenuManager implements DataSubscriber
                settingsMenu.add(makeMenuItem(new ChooseSingleParameter(_app, FunctionLibrary.FUNCTION_SET_ALTITUDE_TOLERANCE)));
                // Set timezone
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_TIMEZONE));
+               // Set Earthdata authentication
+               settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_EARTHDATA_AUTH));
                settingsMenu.addSeparator();
                // Save configuration
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SAVECONFIG));
@@ -889,7 +904,7 @@ public class MenuManager implements DataSubscriber
                _getWeatherItem.setEnabled(hasData);
                _findWaypointItem.setEnabled(hasData && _track.hasWaypoints());
                // have we got a cache?
-               _downloadSrtmItem.setEnabled(hasData && Config.getConfigString(Config.KEY_DISK_CACHE) != null);
+               _downloadSrtmMenu.setEnabled(hasData && Config.getConfigString(Config.KEY_DISK_CACHE) != null);
                // have we got any timestamps?
                _deleteByDateItem.setEnabled(hasData && _track.hasData(Field.TIMESTAMP));
 
@@ -956,6 +971,7 @@ public class MenuManager implements DataSubscriber
                _reverseItem.setEnabled(hasRange);
                _addTimeOffsetItem.setEnabled(hasRange);
                _addAltitudeOffsetItem.setEnabled(hasRange);
+               _removeAltitudesItem.setEnabled(hasRange);
                _convertNamesToTimesItem.setEnabled(hasRange && _track.hasWaypoints());
                _deleteFieldValuesItem.setEnabled(hasRange);
                _fullRangeDetailsItem.setEnabled(hasRange);
index 25eba2fb1847306d41aed63021c7cbe3e4ed2cf0..aec78a61f7703e3f3f97807c5347a04a91743708 100644 (file)
@@ -40,4 +40,12 @@ public class Viewport
                double maxLon = MapUtils.getLongitudeFromX(mapPosition.getXFromPixels(width, width));
                return new double[] {minLat, minLon, maxLat, maxLon};
        }
+
+       /**
+        * Recentre the viewport on the data
+        */
+       public void recentreViewport()
+       {
+               _mapCanvas.zoomToFit();
+       }
 }
index aa25dbcd939e39b96a6642f188b68665d4c74c4f..ad38d8182700cf49a1d5055f66bbb4ec8b56a20e 100644 (file)
@@ -92,7 +92,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        WpIconDefinition _waypointIconDefinition = null;
 
        /** Constant for click sensitivity when selecting nearest point */
-       private static final int CLICK_SENSITIVITY = 10;
+       private static final int CLICK_SENSITIVITY = 30;
        /** Constant for pan distance from key presses */
        private static final int PAN_DISTANCE = 20;
        /** Constant for pan distance from autopan */
@@ -259,8 +259,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
 
                // add control panels to this one
                setLayout(new BorderLayout());
-               _topPanel.setVisible(false);
-               _sidePanel.setVisible(false);
+               _topPanel.setVisible(true);
+               _sidePanel.setVisible(true);
                add(_topPanel, BorderLayout.NORTH);
                add(_sidePanel, BorderLayout.WEST);
                add(_scaleBar, BorderLayout.SOUTH);
@@ -337,16 +337,19 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        /**
         * Zoom to fit the current data area
         */
-       private void zoomToFit()
+       public void zoomToFit()
        {
+               int maxZoom = (_track.getNumPoints() == 0)?2:_tileManager.getMaxZoomLevel();
                _latRange = _track.getLatRange();
                _lonRange = _track.getLonRange();
                _xRange = new DoubleRange(MapUtils.getXFromLongitude(_lonRange.getMinimum()),
                        MapUtils.getXFromLongitude(_lonRange.getMaximum()));
                _yRange = new DoubleRange(MapUtils.getYFromLatitude(_latRange.getMinimum()),
                        MapUtils.getYFromLatitude(_latRange.getMaximum()));
-               _mapPosition.zoomToXY(_xRange.getMinimum(), _xRange.getMaximum(), _yRange.getMinimum(), _yRange.getMaximum(),
-                       getWidth(), getHeight());
+               _mapPosition.zoomToXY(
+                       _xRange.getMinimum(), _xRange.getMaximum(),
+                       _yRange.getMinimum(), _yRange.getMaximum(),
+                       getWidth(), getHeight(), maxZoom);
        }
 
 
@@ -360,70 +363,59 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                if (_mapImage != null && (_mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())) {
                        _mapImage = null;
                }
-               if (_track.getNumPoints() > 0)
+               // Check for autopan if enabled / necessary
+               if (_autopanCheckBox.isSelected())
                {
-                       // Check for autopan if enabled / necessary
-                       if (_autopanCheckBox.isSelected())
+                       int selectedPoint = _selection.getCurrentPointIndex();
+                       if (selectedPoint >= 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
                        {
-                               int selectedPoint = _selection.getCurrentPointIndex();
-                               if (selectedPoint >= 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
-                               {
-                                       autopanToPoint(selectedPoint);
-                               }
-                               _prevSelectedPoint = selectedPoint;
+                               autopanToPoint(selectedPoint);
                        }
+                       _prevSelectedPoint = selectedPoint;
+               }
 
-                       // Draw the map contents if necessary
-                       if (_mapImage == null || _recalculate)
-                       {
-                               paintMapContents();
-                               _scaleBar.updateScale(_mapPosition.getZoom(), _mapPosition.getYFromPixels(0, 0));
-                       }
-                       // Draw the prepared image onto the panel
-                       if (_mapImage != null) {
-                               inG.drawImage(_mapImage, 0, 0, getWidth(), getHeight(), null);
-                       }
+               // Draw the map contents if necessary
+               if (_mapImage == null || _recalculate)
+               {
+                       paintMapContents();
+                       _scaleBar.updateScale(_mapPosition.getZoom(), _mapPosition.getYFromPixels(0, 0));
+               }
+               // Draw the prepared image onto the panel
+               if (_mapImage != null) {
+                       inG.drawImage(_mapImage, 0, 0, getWidth(), getHeight(), null);
+               }
 
-                       switch (_drawMode)
-                       {
-                               case MODE_DRAG_POINT:
-                                       drawDragLines(inG, _selection.getCurrentPointIndex()-1, _selection.getCurrentPointIndex()+1);
-                                       break;
+               switch (_drawMode)
+               {
+                       case MODE_DRAG_POINT:
+                               drawDragLines(inG, _selection.getCurrentPointIndex()-1, _selection.getCurrentPointIndex()+1);
+                               break;
 
-                               case MODE_CREATE_MIDPOINT:
-                                       drawDragLines(inG, _clickedPoint-1, _clickedPoint);
-                                       break;
+                       case MODE_CREATE_MIDPOINT:
+                               drawDragLines(inG, _clickedPoint-1, _clickedPoint);
+                               break;
 
-                               case MODE_ZOOM_RECT:
-                               case MODE_MARK_RECTANGLE:
-                                       if (_dragFromX != -1 && _dragFromY != -1)
-                                       {
-                                               // Draw the zoom rectangle if necessary
-                                               inG.setColor(Color.RED);
-                                               inG.drawLine(_dragFromX, _dragFromY, _dragFromX, _dragToY);
-                                               inG.drawLine(_dragFromX, _dragFromY, _dragToX, _dragFromY);
-                                               inG.drawLine(_dragToX, _dragFromY, _dragToX, _dragToY);
-                                               inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
-                                       }
-                                       break;
-
-                               case MODE_DRAW_POINTS_CONT:
-                                       // draw line to mouse position to show drawing mode
-                                       inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
-                                       int prevIndex = _track.getNumPoints()-1;
-                                       int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(prevIndex));
-                                       int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(prevIndex));
-                                       inG.drawLine(px, py, _dragToX, _dragToY);
-                                       break;
-                       }
-               }
-               else
-               {
-                       inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_BACKGROUND));
-                       inG.fillRect(0, 0, getWidth(), getHeight());
-                       inG.setColor(COLOR_MESSAGES);
-                       inG.drawString(I18nManager.getText("display.nodata"), 50, getHeight()/2);
-                       _scaleBar.updateScale(-1, 0);
+                       case MODE_ZOOM_RECT:
+                       case MODE_MARK_RECTANGLE:
+                               if (_dragFromX != -1 && _dragFromY != -1)
+                               {
+                                       // Draw the zoom rectangle if necessary
+                                       inG.setColor(Color.RED);
+                                       inG.drawLine(_dragFromX, _dragFromY, _dragFromX, _dragToY);
+                                       inG.drawLine(_dragFromX, _dragFromY, _dragToX, _dragFromY);
+                                       inG.drawLine(_dragToX, _dragFromY, _dragToX, _dragToY);
+                                       inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
+                               }
+                               break;
+
+                       case MODE_DRAW_POINTS_CONT:
+                               // draw line to mouse position to show drawing mode
+                               inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
+                               int prevIndex = _track.getNumPoints()-1;
+                               int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(prevIndex));
+                               int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(prevIndex));
+                               inG.drawLine(px, py, _dragToX, _dragToY);
+                               break;
                }
                // Draw slider etc on top
                paintChildren(inG);
@@ -1074,66 +1066,63 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
         */
        public void mouseClicked(MouseEvent inE)
        {
-               if (_track != null && _track.getNumPoints() > 0)
+               // select point if it's a left-click
+               if (!inE.isMetaDown())
                {
-                       // select point if it's a left-click
-                       if (!inE.isMetaDown())
+                       if (inE.getClickCount() == 1)
                        {
-                               if (inE.getClickCount() == 1)
+                               // single click
+                               if (_drawMode == MODE_DEFAULT)
                                {
-                                       // single click
-                                       if (_drawMode == MODE_DEFAULT)
+                                       int pointIndex = _clickedPoint;
+                                       if (pointIndex == INDEX_UNKNOWN)
                                        {
-                                               int pointIndex = _clickedPoint;
-                                               if (pointIndex == INDEX_UNKNOWN)
-                                               {
-                                                       // index hasn't been calculated yet
-                                                       pointIndex = _track.getNearestPointIndex(
-                                                        _mapPosition.getXFromPixels(inE.getX(), getWidth()),
-                                                        _mapPosition.getYFromPixels(inE.getY(), getHeight()),
-                                                        _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
-                                               }
-                                               // Extend selection for shift-click
-                                               if (inE.isShiftDown()) {
-                                                       _trackInfo.extendSelection(pointIndex);
-                                               }
-                                               else {
-                                                       _trackInfo.selectPoint(pointIndex);
-                                               }
+                                               // index hasn't been calculated yet
+                                               pointIndex = _track.getNearestPointIndex(
+                                                       _mapPosition.getXFromPixels(inE.getX(), getWidth()),
+                                                       _mapPosition.getYFromPixels(inE.getY(), getHeight()),
+                                                       _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
                                        }
-                                       else if (_drawMode == MODE_DRAW_POINTS_START)
-                                       {
-                                               _app.createPoint(createPointFromClick(inE.getX(), inE.getY()));
-                                               _dragToX = inE.getX();
-                                               _dragToY = inE.getY();
-                                               _drawMode = MODE_DRAW_POINTS_CONT;
+                                       // Extend selection for shift-click
+                                       if (inE.isShiftDown()) {
+                                               _trackInfo.extendSelection(pointIndex);
                                        }
-                                       else if (_drawMode == MODE_DRAW_POINTS_CONT)
-                                       {
-                                               DataPoint point = createPointFromClick(inE.getX(), inE.getY());
-                                               _app.createPoint(point, false); // not a new segment
+                                       else {
+                                               _trackInfo.selectPoint(pointIndex);
                                        }
                                }
-                               else if (inE.getClickCount() == 2)
+                               else if (_drawMode == MODE_DRAW_POINTS_START)
                                {
-                                       // double click
-                                       if (_drawMode == MODE_DEFAULT) {
-                                               panMap(inE.getX() - getWidth()/2, inE.getY() - getHeight()/2);
-                                               zoomIn();
-                                       }
-                                       else if (_drawMode == MODE_DRAW_POINTS_START || _drawMode == MODE_DRAW_POINTS_CONT) {
-                                               _drawMode = MODE_DEFAULT;
-                                       }
+                                       _app.createPoint(createPointFromClick(inE.getX(), inE.getY()));
+                                       _dragToX = inE.getX();
+                                       _dragToY = inE.getY();
+                                       _drawMode = MODE_DRAW_POINTS_CONT;
+                               }
+                               else if (_drawMode == MODE_DRAW_POINTS_CONT)
+                               {
+                                       DataPoint point = createPointFromClick(inE.getX(), inE.getY());
+                                       _app.createPoint(point, false); // not a new segment
                                }
                        }
-                       else
+                       else if (inE.getClickCount() == 2)
                        {
-                               // show the popup menu for right-clicks
-                               _popupMenuX = inE.getX();
-                               _popupMenuY = inE.getY();
-                               _popup.show(this, _popupMenuX, _popupMenuY);
+                               // double click
+                               if (_drawMode == MODE_DEFAULT) {
+                                       panMap(inE.getX() - getWidth()/2, inE.getY() - getHeight()/2);
+                                       zoomIn();
+                               }
+                               else if (_drawMode == MODE_DRAW_POINTS_START || _drawMode == MODE_DRAW_POINTS_CONT) {
+                                       _drawMode = MODE_DEFAULT;
+                               }
                        }
                }
+               else
+               {
+                       // show the popup menu for right-clicks
+                       _popupMenuX = inE.getX();
+                       _popupMenuY = inE.getY();
+                       _popup.show(this, _popupMenuX, _popupMenuY);
+               }
                // Reset app mode
                _app.setCurrentMode(App.AppMode.NORMAL);
                if (_drawMode == MODE_MARK_RECTANGLE) _drawMode = MODE_DEFAULT;
@@ -1408,10 +1397,6 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        }
                }
                repaint();
-               // enable or disable components
-               boolean hasData = _track.getNumPoints() > 0;
-               _topPanel.setVisible(hasData);
-               _sidePanel.setVisible(hasData);
                // grab focus for the key presses
                this.requestFocus();
        }
index 25f05fc1a2a238370d8888ea4b807b7d3f5f8adb..71ab6cd4d88ed9eb9823db56ab19dc72d574db53 100644 (file)
@@ -30,14 +30,14 @@ public class MapPosition
         * @param inWidth width of display
         * @param inHeight height of display
         */
-       public void zoomToXY(double inMinX, double inMaxX, double inMinY, double inMaxY, int inWidth, int inHeight)
+       public void zoomToXY(double inMinX, double inMaxX, double inMinY, double inMaxY, int inWidth, int inHeight, int maxZoom)
        {
                // System.out.println("Zooming to " + inMinX + ", " + inMaxX + ", " + inMinY + ", " + inMaxY + "; width=" + inWidth + ", height=" + inHeight);
                double diffX = Math.abs(inMaxX - inMinX);
                double diffY = Math.abs(inMaxY - inMinY);
                // Find out what zoom level to go to
                int requiredZoom = -1;
-               for (int currZoom = MAX_ZOOM; currZoom >= 2; currZoom--)
+               for (int currZoom = maxZoom; currZoom >= 2; currZoom--)
                {
                        if (transformToPixels(diffX, currZoom) < inWidth
                                && transformToPixels(diffY, currZoom) < inHeight)
index f7370c5c90c833e7f053fc786eafdae37ab7f843..d1e0047a2bc2624284308fad0f4ab1ea9df2dc09 100644 (file)
@@ -70,10 +70,19 @@ public class MapTileManager implements ImageObserver
         * @return true if zoom is too high for tiles
         */
        public boolean isOverzoomed()
+       {
+               return _zoom > getMaxZoomLevel();
+       }
+
+       /**
+        * @return the maximum useable zoom level for tiles
+        */
+       public int getMaxZoomLevel()
        {
                // Ask current map source what maximum zoom is
                int maxZoom = (_mapSource == null?0:_mapSource.getMaxZoomLevel());
-               return (_zoom > maxZoom);
+               return maxZoom;
+
        }
 
        /**
index 76939263c9d8af07fa36db1f1686cf99550180dd..82219b22e3f7c71411c364f7f0d5d4cc5b4686d9 100644 (file)
@@ -94,6 +94,7 @@ function.interpolate=Interpolate points
 function.deletebydate=Delete points by date
 function.addtimeoffset=Add time offset
 function.addaltitudeoffset=Add altitude offset
+function.removealtitudes=Remove altitudes
 function.findwaypoint=Find waypoint
 function.rearrangewaypoints=Rearrange waypoints
 function.convertnamestotimes=Convert waypoint names to times
@@ -114,6 +115,9 @@ function.getgpsies=Get Gpsies tracks
 function.uploadgpsies=Upload track to Gpsies
 function.lookupsrtm=Get altitudes from SRTM
 function.downloadsrtm=Download SRTM tiles
+function.downloadsrtm.SRTMGL1_v003=Download SRTM 1 arc-second tiles
+function.downloadsrtm.SRTMGL1_v003.needsetup=An Earthdata account is necessary to download SRTM 1 arc-second tiles
+function.downloadsrtm.SRTM3_v21=Download SRTM 3 arc-second tiles
 function.getwikipedia=Get nearby Wikipedia articles
 function.searchwikipedianames=Search Wikipedia by name
 function.searchosmpois=Get nearby OSM points
@@ -150,6 +154,7 @@ function.managetilecache=Manage tile cache
 function.getweatherforecast=Get weather forecast
 function.setaltitudetolerance=Set altitude tolerance
 function.selecttimezone=Set timezone
+function.setearthdataauthentication=Set Earthdata authentication
 
 # Dialogs
 dialog.exit.confirm.title=Exit GpsPrune
@@ -575,6 +580,11 @@ dialog.displaysettings.size.small=Small
 dialog.displaysettings.size.medium=Medium
 dialog.displaysettings.size.large=Large
 dialog.downloadosm.desc=Confirm to download the raw OSM data for the specified area:
+dialog.earthdataauth.intro=<p>Configure username and password to access your NASA Earthdata login account.</p><p> Create an account at <tt>https://urs.earthdata.nasa.gov/users/new</tt>.</p>
+dialog.earthdataauth.user=Username
+dialog.earthdataauth.password=Password
+dialog.earthdataauth.authaccepted=Username and password accepted
+dialog.earthdataauth.authrejected=Username and password rejected
 dialog.searchwikipedianames.search=Search for:
 dialog.weather.location=Location
 dialog.weather.update=Forecast updated
@@ -632,6 +642,7 @@ confirm.mergetracksegments=Track segments merged
 confirm.reverserange=Range reversed
 confirm.addtimeoffset=Time offset added
 confirm.addaltitudeoffset=Altitude offset added
+confirm.removealtitudes=Altitudes removed
 confirm.rearrangewaypoints=Waypoints rearranged
 confirm.rearrangephotos=Photos rearranged
 confirm.splitsegments=%d segment splits were made
@@ -852,6 +863,7 @@ undo.splitsegments=split track segments
 undo.sewsegments=sew track segments
 undo.addtimeoffset=add time offset
 undo.addaltitudeoffset=add altitude offset
+undo.removealtitudes=remove altitudes
 undo.rearrangewaypoints=rearrange waypoints
 undo.cutandmove=move section
 undo.connect=connect
diff --git a/src/tim/prune/undo/UndoRemoveAltitudes.java b/src/tim/prune/undo/UndoRemoveAltitudes.java
new file mode 100644 (file)
index 0000000..becf2d7
--- /dev/null
@@ -0,0 +1,65 @@
+package tim.prune.undo;
+
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.Altitude;
+import tim.prune.data.DataPoint;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Undo removing (ie: restore the original) altitude from points
+ */
+public class UndoRemoveAltitudes implements UndoOperation
+{
+       /** Start index of section */
+       private int _startIndex;
+       /** altitude values before operation */
+       private Altitude[] _altitudes;
+
+
+       /**
+        * Constructor
+        * @param inTrackInfo track info object
+        */
+       public UndoRemoveAltitudes(TrackInfo inTrackInfo, int inStart, int inEnd)
+       {
+               _startIndex = inStart;
+               final int numPoints = inEnd - inStart + 1;
+               // Make array of cloned altitude objects
+               _altitudes = new Altitude[numPoints];
+               for (int i=0; i<numPoints; i++) {
+                       Altitude a = inTrackInfo.getTrack().getPoint(_startIndex+i).getAltitude();
+                       if (a != null && a.isValid()) {
+                               _altitudes[i] = a.clone();
+                       }
+               }
+       }
+
+
+       /**
+        * @return description of operation including number of points adjusted
+        */
+       public String getDescription()
+       {
+               return I18nManager.getText("undo.removealtitudes") + " (" + (_altitudes.length) + ")";
+       }
+
+
+       /**
+        * Perform the undo operation on the given Track
+        * @param inTrackInfo TrackInfo object on which to perform the operation
+        */
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException
+       {
+               // Perform the inverse operation
+               final int numPoints = _altitudes.length;
+               for (int i=0; i<numPoints; i++)
+               {
+                       DataPoint point = inTrackInfo.getTrack().getPoint(i+_startIndex);
+                       point.resetAltitude(_altitudes[i]);
+               }
+               _altitudes = null;
+               inTrackInfo.getSelection().markInvalid();
+               UpdateMessageBroker.informSubscribers();
+       }
+}