]> gitweb.fperrin.net Git - GpsPrune.git/commitdiff
Merge branch 'start-with-editable-map' into fp-integration
authorFrédéric Perrin <frederic.perrin@att.com>
Wed, 29 Jan 2020 15:24:04 +0000 (15:24 +0000)
committerFrédéric Perrin <frederic.perrin@att.com>
Wed, 29 Jan 2020 15:24:04 +0000 (15:24 +0000)
30 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/FileInfo.java
src/tim/prune/data/Track.java
src/tim/prune/function/AddTimeOffset.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/SelectorDisplay.java
src/tim/prune/gui/map/MapCanvas.java
src/tim/prune/gui/map/MapSource.java
src/tim/prune/gui/map/OsmMapSource.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 2e7594178ae2b379eccffd2d3cf41bb9f6494e61..317064d39984acb566cee90940236c52c32fd58f 100644 (file)
@@ -51,6 +51,7 @@ public class App
 {
        // Instance variables
        private JFrame _frame = null;
+       private String _titlePrefix = null;
        private Track _track = null;
        private TrackInfo _trackInfo = null;
        private int _lastSavePosition = 0;
@@ -79,6 +80,7 @@ public class App
        public App(JFrame inFrame)
        {
                _frame = inFrame;
+               _titlePrefix = _frame.getTitle();
                _undoStack = new UndoStack();
                _track = new Track();
                _trackInfo = new TrackInfo(_track);
@@ -454,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 +779,8 @@ public class App
                _menuManager.informFileLoaded();
                // recentre viewport on new file data
                _viewport.recentreViewport();
+               // update main window title
+               updateTitle();
                // Remove busy lock
                _busyLoading = false;
                // load next file if there's a queue
@@ -1003,4 +1023,16 @@ public class App
        public void setCurrentMode(AppMode inMode) {
                _appMode = inMode;
        }
+
+       /** Update main window title **/
+       public void updateTitle() {
+               ArrayList<String> filenames = _trackInfo.getFileInfo().getFilenames();
+               if (filenames.size() > 0) {
+                       _frame.setTitle(_titlePrefix + ": " + String.join(", ", filenames));
+               }
+               else
+               {
+                       _frame.setTitle(_titlePrefix);
+               }
+       }
 }
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 41900cb8736df60d668238306079256bf2dca175..7eaac8f307d1bcde51f68a3bf662cd6879adeab2 100644 (file)
@@ -74,6 +74,19 @@ public class FileInfo
                return "";
        }
 
+       /**
+        * @return The source names
+        */
+       public ArrayList<String> getFilenames()
+       {
+               ArrayList<String> filenames = new ArrayList<String>();
+               for (SourceInfo source : _sources)
+               {
+                       filenames.add(source.getName());
+               }
+               return filenames;
+       }
+
        /**
         * @param inIndex index number, starting from zero
         * @return source info object
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
index 2e259bb5f5895e94690355d2cbee039080a8434d..199ae5056b133aa27f59b3f509cf9806f9e4dca2 100644 (file)
@@ -33,8 +33,8 @@ public class AddTimeOffset extends GenericFunction
 {
        private JDialog _dialog = null;
        private JRadioButton _addRadio = null, _subtractRadio = null;
-       private WholeNumberField _dayField = null, _hourField = null;
-       private WholeNumberField _minuteField = null;
+       private WholeNumberField _1024weekField = null, _dayField = null;
+       private WholeNumberField _hourField = null, _minuteField = null;
        private JButton _okButton = null;
 
 
@@ -103,8 +103,11 @@ public class AddTimeOffset extends GenericFunction
                // Make a central panel with the text boxes
                JPanel descPanel = new JPanel();
                descPanel.setLayout(new GridLayout(0, 2));
+               descPanel.add(makeRightLabel("dialog.addtimeoffset.1024week"));
+               _1024weekField = new WholeNumberField(3);
+               descPanel.add(_1024weekField);
                descPanel.add(makeRightLabel("dialog.addtimeoffset.days"));
-               _dayField = new WholeNumberField(3);
+               _dayField = new WholeNumberField(4);
                descPanel.add(_dayField);
                descPanel.add(makeRightLabel("dialog.addtimeoffset.hours"));
                _hourField = new WholeNumberField(3);
@@ -128,9 +131,11 @@ public class AddTimeOffset extends GenericFunction
                                _okButton.setEnabled(getOffsetSecs() != 0L);
                        };
                };
+               _1024weekField.addKeyListener(keyListener);
                _dayField.addKeyListener(keyListener);
                _hourField.addKeyListener(keyListener);
                _minuteField.addKeyListener(keyListener);
+               _1024weekField.addMouseListener(mouseListener);
                _dayField.addMouseListener(mouseListener);
                _hourField.addMouseListener(mouseListener);
                _minuteField.addMouseListener(mouseListener);
@@ -180,7 +185,8 @@ public class AddTimeOffset extends GenericFunction
        {
                long offsetSecs = _minuteField.getValue() * 60L
                  + _hourField.getValue() * 60L * 60L
-                 + _dayField.getValue() * 60L * 60L * 24L;
+                 + _dayField.getValue() * 60L * 60L * 24L
+                 + _1024weekField.getValue() * 60L * 60L * 24L * 7L * 1024L;
                if (_subtractRadio.isSelected()) {offsetSecs = -offsetSecs;}
                return offsetSecs;
        }
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..ca5771d45fe9886c2717127b8e80ef257a47dc8e 100644 (file)
@@ -206,7 +206,14 @@ 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
+                       if (wasNameAdded())
+                       {
+                               _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 +238,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..146ed77
--- /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()
+       {
+               _usernameField.setText("");
+               _passwordField.setText("");
+               _authAccepted.setText(" ");
+
+               String authString = Config.getConfigString(Config.KEY_EARTHDATA_AUTH);
+               try
+               {
+                       String decoded = new String(Base64.getDecoder().decode(authString));
+                       if (decoded.contains(":"))
+                       {
+                               _usernameField.setText(decoded.split(":", 2)[0]);
+                               _passwordField.setText(decoded.split(":", 2)[1]);
+                       }
+               }
+               catch (Exception e)
+               {
+                       // empty settings, or invalid base64 data --leave blank
+                       return;
+               }
+       }
+
+       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 fa6630dc5b71400bd4f7202fb688f9a911cc3f6f..9dfc24f42394ad624d925589a2acd344411c12a0 100644 (file)
@@ -228,8 +228,9 @@ public class SelectorDisplay extends GenericDisplay
                        else if (numFiles > 1)
                        {
                                final String labelText = I18nManager.getText("details.track.numfiles") + ": " + numFiles;
+                               final String filenameString = String.join(", ", _trackInfo.getFileInfo().getFilenames());
                                _filenameLabel.setText(labelText);
-                               _filenameLabel.setToolTipText(labelText);
+                               _filenameLabel.setToolTipText(filenameString);
                        }
                        else
                        {
index 8297e019db986117d3c213ca58294fbee49536ec..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 */
index c4e294665e128def7d1fcbfb048cb516624d5d00..177ad670b14d89664f6129cf46d85038c3f8cd8e 100644 (file)
@@ -104,7 +104,7 @@ public abstract class MapSource
                        urlstr = "http://" + urlstr;
                }
                // check trailing /
-               if (!urlstr.endsWith("/")) {
+               if (!urlstr.endsWith("/") && !urlstr.contains("?")) {
                        urlstr = urlstr + "/";
                }
                // Validate current url, return null if not ok
index 925fcf623ea456154c512c5e5e89454eb5b46bf8..e3fe58987b800f54cf10b1b8bd9ed8fe023c0897 100644 (file)
@@ -152,12 +152,31 @@ public class OsmMapSource extends MapSource
 
        /**
         * Make the URL to get the specified tile
+        * @param inLayerNum layer number
+        * @param inZoom zoom level
+        * @param inX x coordinate
+        * @param inY y coordinate
+        * @return relative file path as String
         */
        public String makeURL(int inLayerNum, int inZoom, int inX, int inY)
        {
                // Check if the base url has a [1234], if so replace at random
-               StringBuffer url = new StringBuffer();
-               url.append(pickServerUrl(_baseUrls[inLayerNum]));
+               String baseUrl = pickServerUrl(_baseUrls[inLayerNum]);
+               return makeUrl(baseUrl, inLayerNum, inZoom, inX, inY);
+       }
+
+       public String makeUrl(String baseUrl, int inLayerNum, int inZoom, int inX, int inY)
+       {
+               // If the base URL has {x}/{y} placeholders, use them
+               if (baseUrl.contains("{x}")) {
+                       baseUrl = baseUrl.replace("{z}", Integer.toString(inZoom))
+                               .replace("{x}", Integer.toString(inX))
+                               .replace("{y}", Integer.toString(inY));
+                       return baseUrl;
+               }
+
+               // Else simply append the tile indices and file extension
+               StringBuffer url = new StringBuffer(baseUrl);
                url.append(inZoom).append('/').append(inX).append('/').append(inY);
                url.append('.').append(getFileExtension(inLayerNum));
                if (_apiKey != null)
@@ -167,6 +186,26 @@ public class OsmMapSource extends MapSource
                return url.toString();
        }
 
+       /**
+        * Make a relative file path from the base directory including site name
+        * @param inLayerNum layer number
+        * @param inZoom zoom level
+        * @param inX x coordinate
+        * @param inY y coordinate
+        * @return relative file path as String
+        */
+       public String makeFilePath(int inLayerNum, int inZoom, int inX, int inY)
+       {
+               String siteName = getSiteName(inLayerNum);
+               String filePath = makeUrl(siteName, inLayerNum, inZoom, inX, inY);
+               int indexParam = filePath.indexOf("?");
+               if (indexParam > 0)
+               {
+                       filePath = filePath.substring(0, indexParam);
+               }
+               return filePath;
+       }
+
        /**
         * @return maximum zoom level
         */
index 76939263c9d8af07fa36db1f1686cf99550180dd..fd50356d60bbccb4a88db5159f83787fa0d31759 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
@@ -302,6 +307,7 @@ dialog.pointnameedit.lowercase=lower case
 dialog.pointnameedit.titlecase=Title Case
 dialog.addtimeoffset.add=Add time
 dialog.addtimeoffset.subtract=Subtract time
+dialog.addtimeoffset.1024week=1024-week block
 dialog.addtimeoffset.days=Days
 dialog.addtimeoffset.hours=Hours
 dialog.addtimeoffset.minutes=Minutes
@@ -575,6 +581,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 +643,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 +864,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();
+       }
+}