--- /dev/null
+*.class
+*.jar
+set -e
# Build script
# Version number
PRUNENAME=gpsprune_19.2
}
+ /**
+ * 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
*/
+ " '" + inSourceInfo.getName() + "'");
// update menu
_menuManager.informFileLoaded();
+ // recentre viewport on new file data
+ _viewport.recentreViewport();
// update main window title
updateTitle();
// Remove busy lock
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;
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;
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;
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;
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);
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);
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 */
}
}
+ /**
+ * 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
}
+ /**
+ * 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
--- /dev/null
+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);
+ }
+}
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);
+ }
}
// Check whether name has really changed
if (hasNameChanged())
{
- // Make lists for edit and undo, and add the changed field
+ // If a new name has been added, changing the point
+ // from trackpoint to waypoint, duplicate it
+ _app.createPoint(_point.clonePoint());
+
+ // make lists for edit and undo, and add the changed field
FieldEditList editList = new FieldEditList();
FieldEditList undoList = new FieldEditList();
editList.addEdit(new FieldEdit(Field.WAYPT_NAME, _nameField.getText().trim()));
|| (!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);
+ }
}
--- /dev/null
+package tim.prune.function.settings;
+
+import java.awt.Component;
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Base64;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.function.srtm.SrtmGl1Source;
+import tim.prune.function.srtm.SrtmSourceException;
+
+/**
+ * Set authentication data for the NASA Earthdata systems
+ */
+public class SetEarthdataAuthentication extends GenericFunction
+{
+ private JDialog _dialog = null;
+ private JTextField _usernameField = null;
+ private JPasswordField _passwordField = null;
+ private JLabel _authAccepted = null;
+
+ /**
+ * Constructor
+ * @param inApp App object
+ */
+ public SetEarthdataAuthentication(App inApp) {
+ super(inApp);
+ }
+
+ /** @return name key */
+ public String getNameKey() {
+ return "function.setearthdataauthentication";
+ }
+
+ public void begin()
+ {
+ if (_dialog == null)
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ // Create Gui and show it
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ }
+ prefillCurrentAuth();
+ _dialog.setVisible(true);
+ }
+
+ /**
+ * Make the dialog components
+ * @return the GUI components for the dialog
+ */
+ private JPanel makeDialogComponents()
+ {
+ // Blurb to explain to the user
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BorderLayout());
+ dialogPanel.add(new JLabel("<html>"+I18nManager.getText("dialog.earthdataauth.intro")+"</html>"), BorderLayout.NORTH);
+
+ // username and password fields
+ JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new BorderLayout());
+ JPanel usernamePasswordPanel = new JPanel();
+ usernamePasswordPanel.setLayout(new GridLayout(2, 2));
+
+ JLabel usernameLabel = new JLabel(I18nManager.getText("dialog.earthdataauth.user"));
+ usernamePasswordPanel.add(usernameLabel);
+ _usernameField = new JTextField("");
+ usernamePasswordPanel.add(_usernameField);
+
+ JLabel passwordLabel = new JLabel(I18nManager.getText("dialog.earthdataauth.password"));
+ usernamePasswordPanel.add(passwordLabel);
+ _passwordField = new JPasswordField("");
+ usernamePasswordPanel.add(_passwordField);
+ mainPanel.add(usernamePasswordPanel, BorderLayout.CENTER);
+
+ JPanel authStatusPanel = new JPanel();
+ authStatusPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ _authAccepted = new JLabel(" ");
+ authStatusPanel.add(_authAccepted);
+ mainPanel.add(authStatusPanel, BorderLayout.SOUTH);
+
+ mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+ dialogPanel.add(mainPanel, BorderLayout.CENTER);
+
+ // ok / cancel buttons at bottom
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ JButton checkButton = new JButton(I18nManager.getText("button.check"));
+ checkButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ testUsernameAndPassword();
+ }
+ });
+ buttonPanel.add(checkButton);
+ JButton okButton = new JButton(I18nManager.getText("button.ok"));
+ okButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ finish();
+ }
+ });
+ buttonPanel.add(okButton);
+ JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ _dialog.dispose();
+ }
+ });
+ buttonPanel.add(cancelButton);
+ dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+ dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+ return dialogPanel;
+ }
+
+ private String getNewAuthString()
+ {
+ String username = _usernameField.getText();
+ String password = _passwordField.getText();
+ return Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
+ }
+
+ private void finish()
+ {
+ Config.setConfigString(Config.KEY_EARTHDATA_AUTH, getNewAuthString());
+ _dialog.dispose();
+ }
+
+ private void prefillCurrentAuth()
+ {
+ String authString = Config.getConfigString(Config.KEY_EARTHDATA_AUTH);
+ if (authString == null)
+ {
+ _usernameField.setText("");
+ _passwordField.setText("");
+ }
+ String decoded = new String(Base64.getDecoder().decode(authString));
+ if (decoded.contains(":"))
+ {
+ _usernameField.setText(decoded.split(":", 2)[0]);
+ _passwordField.setText(decoded.split(":", 2)[1]);
+ }
+ else
+ {
+ _usernameField.setText("");
+ _passwordField.setText("");
+ }
+
+ _authAccepted.setText(" ");
+ }
+
+ private void testUsernameAndPassword()
+ {
+ String username = _usernameField.getText();
+ String password = _passwordField.getText();
+ SrtmGl1Source srtmGL1 = new SrtmGl1Source();
+ try
+ {
+ _authAccepted.setText("...");
+ srtmGL1.testAuth(getNewAuthString());
+ _authAccepted.setText(I18nManager.getText("dialog.earthdataauth.authaccepted"));
+ }
+ catch (SrtmSourceException e)
+ {
+ _authAccepted.setText(I18nManager.getText("dialog.earthdataauth.authrejected") + ": " + e.getMessage());
+ }
+ }
+}
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;
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;
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();
}
/**
*/
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());
* 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>();
}
}
- 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)
{
_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();
}
}
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);
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;
- }
}
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;
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;
/**
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;
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());
}
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;
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
_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();
- }
}
}
return;
}
+ if (! errorMessage.equals("")) {
+ _app.showErrorMessageNoLookup(getNameKey(), errorMessage);
+ return;
+ }
if (numAltitudesFound > 0)
{
// Inform app including undo information
I18nManager.getTextWithNumber("confirm.lookupsrtm", numAltitudesFound));
}
}
- else if (errorMessage != null) {
- _app.showErrorMessageNoLookup(getNameKey(), errorMessage);
- }
else if (inTileList.size() > 0) {
_app.showErrorMessage(getNameKey(), "error.lookupsrtm.nonefound");
}
}
}
- /**
- * 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)
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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();
+ }
+}
--- /dev/null
+package tim.prune.function.srtm;
+
+public class SrtmSourceException extends Exception {
+ public SrtmSourceException(String message) {
+ super(message);
+ }
+}
+ (_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;
}
}
+++ /dev/null
-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;
- }
-}
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";
}
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,
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;
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;
// 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);
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) {
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));
_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));
_reverseItem.setEnabled(hasRange);
_addTimeOffsetItem.setEnabled(hasRange);
_addAltitudeOffsetItem.setEnabled(hasRange);
+ _removeAltitudesItem.setEnabled(hasRange);
_convertNamesToTimesItem.setEnabled(hasRange && _track.hasWaypoints());
_deleteFieldValuesItem.setEnabled(hasRange);
_fullRangeDetailsItem.setEnabled(hasRange);
double maxLon = MapUtils.getLongitudeFromX(mapPosition.getXFromPixels(width, width));
return new double[] {minLat, minLon, maxLat, maxLon};
}
+
+ /**
+ * Recentre the viewport on the data
+ */
+ public void recentreViewport()
+ {
+ _mapCanvas.zoomToFit();
+ }
}
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 */
// add control panels to this one
setLayout(new BorderLayout());
- _topPanel.setVisible(false);
- _sidePanel.setVisible(false);
+ _topPanel.setVisible(true);
+ _sidePanel.setVisible(true);
add(_topPanel, BorderLayout.NORTH);
add(_sidePanel, BorderLayout.WEST);
add(_scaleBar, BorderLayout.SOUTH);
/**
* Zoom to fit the current data area
*/
- private void zoomToFit()
+ public void zoomToFit()
{
+ int maxZoom = (_track.getNumPoints() == 0)?2:_tileManager.getMaxZoomLevel();
_latRange = _track.getLatRange();
_lonRange = _track.getLonRange();
_xRange = new DoubleRange(MapUtils.getXFromLongitude(_lonRange.getMinimum()),
MapUtils.getXFromLongitude(_lonRange.getMaximum()));
_yRange = new DoubleRange(MapUtils.getYFromLatitude(_latRange.getMinimum()),
MapUtils.getYFromLatitude(_latRange.getMaximum()));
- _mapPosition.zoomToXY(_xRange.getMinimum(), _xRange.getMaximum(), _yRange.getMinimum(), _yRange.getMaximum(),
- getWidth(), getHeight());
+ _mapPosition.zoomToXY(
+ _xRange.getMinimum(), _xRange.getMaximum(),
+ _yRange.getMinimum(), _yRange.getMaximum(),
+ getWidth(), getHeight(), maxZoom);
}
if (_mapImage != null && (_mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())) {
_mapImage = null;
}
- if (_track.getNumPoints() > 0)
+ // Check for autopan if enabled / necessary
+ if (_autopanCheckBox.isSelected())
{
- // Check for autopan if enabled / necessary
- if (_autopanCheckBox.isSelected())
+ int selectedPoint = _selection.getCurrentPointIndex();
+ if (selectedPoint >= 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
{
- int selectedPoint = _selection.getCurrentPointIndex();
- if (selectedPoint >= 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
- {
- autopanToPoint(selectedPoint);
- }
- _prevSelectedPoint = selectedPoint;
+ autopanToPoint(selectedPoint);
}
+ _prevSelectedPoint = selectedPoint;
+ }
- // Draw the map contents if necessary
- if (_mapImage == null || _recalculate)
- {
- paintMapContents();
- _scaleBar.updateScale(_mapPosition.getZoom(), _mapPosition.getYFromPixels(0, 0));
- }
- // Draw the prepared image onto the panel
- if (_mapImage != null) {
- inG.drawImage(_mapImage, 0, 0, getWidth(), getHeight(), null);
- }
+ // Draw the map contents if necessary
+ if (_mapImage == null || _recalculate)
+ {
+ paintMapContents();
+ _scaleBar.updateScale(_mapPosition.getZoom(), _mapPosition.getYFromPixels(0, 0));
+ }
+ // Draw the prepared image onto the panel
+ if (_mapImage != null) {
+ inG.drawImage(_mapImage, 0, 0, getWidth(), getHeight(), null);
+ }
- switch (_drawMode)
- {
- case MODE_DRAG_POINT:
- drawDragLines(inG, _selection.getCurrentPointIndex()-1, _selection.getCurrentPointIndex()+1);
- break;
+ switch (_drawMode)
+ {
+ case MODE_DRAG_POINT:
+ drawDragLines(inG, _selection.getCurrentPointIndex()-1, _selection.getCurrentPointIndex()+1);
+ break;
- case MODE_CREATE_MIDPOINT:
- drawDragLines(inG, _clickedPoint-1, _clickedPoint);
- break;
+ case MODE_CREATE_MIDPOINT:
+ drawDragLines(inG, _clickedPoint-1, _clickedPoint);
+ break;
- case MODE_ZOOM_RECT:
- case MODE_MARK_RECTANGLE:
- if (_dragFromX != -1 && _dragFromY != -1)
- {
- // Draw the zoom rectangle if necessary
- inG.setColor(Color.RED);
- inG.drawLine(_dragFromX, _dragFromY, _dragFromX, _dragToY);
- inG.drawLine(_dragFromX, _dragFromY, _dragToX, _dragFromY);
- inG.drawLine(_dragToX, _dragFromY, _dragToX, _dragToY);
- inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
- }
- break;
-
- case MODE_DRAW_POINTS_CONT:
- // draw line to mouse position to show drawing mode
- inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
- int prevIndex = _track.getNumPoints()-1;
- int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(prevIndex));
- int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(prevIndex));
- inG.drawLine(px, py, _dragToX, _dragToY);
- break;
- }
- }
- else
- {
- inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_BACKGROUND));
- inG.fillRect(0, 0, getWidth(), getHeight());
- inG.setColor(COLOR_MESSAGES);
- inG.drawString(I18nManager.getText("display.nodata"), 50, getHeight()/2);
- _scaleBar.updateScale(-1, 0);
+ case MODE_ZOOM_RECT:
+ case MODE_MARK_RECTANGLE:
+ if (_dragFromX != -1 && _dragFromY != -1)
+ {
+ // Draw the zoom rectangle if necessary
+ inG.setColor(Color.RED);
+ inG.drawLine(_dragFromX, _dragFromY, _dragFromX, _dragToY);
+ inG.drawLine(_dragFromX, _dragFromY, _dragToX, _dragFromY);
+ inG.drawLine(_dragToX, _dragFromY, _dragToX, _dragToY);
+ inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
+ }
+ break;
+
+ case MODE_DRAW_POINTS_CONT:
+ // draw line to mouse position to show drawing mode
+ inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
+ int prevIndex = _track.getNumPoints()-1;
+ int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(prevIndex));
+ int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(prevIndex));
+ inG.drawLine(px, py, _dragToX, _dragToY);
+ break;
}
// Draw slider etc on top
paintChildren(inG);
*/
public void mouseClicked(MouseEvent inE)
{
- if (_track != null && _track.getNumPoints() > 0)
+ // select point if it's a left-click
+ if (!inE.isMetaDown())
{
- // select point if it's a left-click
- if (!inE.isMetaDown())
+ if (inE.getClickCount() == 1)
{
- if (inE.getClickCount() == 1)
+ // single click
+ if (_drawMode == MODE_DEFAULT)
{
- // single click
- if (_drawMode == MODE_DEFAULT)
+ int pointIndex = _clickedPoint;
+ if (pointIndex == INDEX_UNKNOWN)
{
- int pointIndex = _clickedPoint;
- if (pointIndex == INDEX_UNKNOWN)
- {
- // index hasn't been calculated yet
- pointIndex = _track.getNearestPointIndex(
- _mapPosition.getXFromPixels(inE.getX(), getWidth()),
- _mapPosition.getYFromPixels(inE.getY(), getHeight()),
- _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
- }
- // Extend selection for shift-click
- if (inE.isShiftDown()) {
- _trackInfo.extendSelection(pointIndex);
- }
- else {
- _trackInfo.selectPoint(pointIndex);
- }
+ // index hasn't been calculated yet
+ pointIndex = _track.getNearestPointIndex(
+ _mapPosition.getXFromPixels(inE.getX(), getWidth()),
+ _mapPosition.getYFromPixels(inE.getY(), getHeight()),
+ _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
}
- else if (_drawMode == MODE_DRAW_POINTS_START)
- {
- _app.createPoint(createPointFromClick(inE.getX(), inE.getY()));
- _dragToX = inE.getX();
- _dragToY = inE.getY();
- _drawMode = MODE_DRAW_POINTS_CONT;
+ // Extend selection for shift-click
+ if (inE.isShiftDown()) {
+ _trackInfo.extendSelection(pointIndex);
}
- else if (_drawMode == MODE_DRAW_POINTS_CONT)
- {
- DataPoint point = createPointFromClick(inE.getX(), inE.getY());
- _app.createPoint(point, false); // not a new segment
+ else {
+ _trackInfo.selectPoint(pointIndex);
}
}
- else if (inE.getClickCount() == 2)
+ else if (_drawMode == MODE_DRAW_POINTS_START)
{
- // double click
- if (_drawMode == MODE_DEFAULT) {
- panMap(inE.getX() - getWidth()/2, inE.getY() - getHeight()/2);
- zoomIn();
- }
- else if (_drawMode == MODE_DRAW_POINTS_START || _drawMode == MODE_DRAW_POINTS_CONT) {
- _drawMode = MODE_DEFAULT;
- }
+ _app.createPoint(createPointFromClick(inE.getX(), inE.getY()));
+ _dragToX = inE.getX();
+ _dragToY = inE.getY();
+ _drawMode = MODE_DRAW_POINTS_CONT;
+ }
+ else if (_drawMode == MODE_DRAW_POINTS_CONT)
+ {
+ DataPoint point = createPointFromClick(inE.getX(), inE.getY());
+ _app.createPoint(point, false); // not a new segment
}
}
- else
+ else if (inE.getClickCount() == 2)
{
- // show the popup menu for right-clicks
- _popupMenuX = inE.getX();
- _popupMenuY = inE.getY();
- _popup.show(this, _popupMenuX, _popupMenuY);
+ // double click
+ if (_drawMode == MODE_DEFAULT) {
+ panMap(inE.getX() - getWidth()/2, inE.getY() - getHeight()/2);
+ zoomIn();
+ }
+ else if (_drawMode == MODE_DRAW_POINTS_START || _drawMode == MODE_DRAW_POINTS_CONT) {
+ _drawMode = MODE_DEFAULT;
+ }
}
}
+ else
+ {
+ // show the popup menu for right-clicks
+ _popupMenuX = inE.getX();
+ _popupMenuY = inE.getY();
+ _popup.show(this, _popupMenuX, _popupMenuY);
+ }
// Reset app mode
_app.setCurrentMode(App.AppMode.NORMAL);
if (_drawMode == MODE_MARK_RECTANGLE) _drawMode = MODE_DEFAULT;
}
}
repaint();
- // enable or disable components
- boolean hasData = _track.getNumPoints() > 0;
- _topPanel.setVisible(hasData);
- _sidePanel.setVisible(hasData);
// grab focus for the key presses
this.requestFocus();
}
* @param inWidth width of display
* @param inHeight height of display
*/
- public void zoomToXY(double inMinX, double inMaxX, double inMinY, double inMaxY, int inWidth, int inHeight)
+ public void zoomToXY(double inMinX, double inMaxX, double inMinY, double inMaxY, int inWidth, int inHeight, int maxZoom)
{
// System.out.println("Zooming to " + inMinX + ", " + inMaxX + ", " + inMinY + ", " + inMaxY + "; width=" + inWidth + ", height=" + inHeight);
double diffX = Math.abs(inMaxX - inMinX);
double diffY = Math.abs(inMaxY - inMinY);
// Find out what zoom level to go to
int requiredZoom = -1;
- for (int currZoom = MAX_ZOOM; currZoom >= 2; currZoom--)
+ for (int currZoom = maxZoom; currZoom >= 2; currZoom--)
{
if (transformToPixels(diffX, currZoom) < inWidth
&& transformToPixels(diffY, currZoom) < inHeight)
* @return true if zoom is too high for tiles
*/
public boolean isOverzoomed()
+ {
+ return _zoom > getMaxZoomLevel();
+ }
+
+ /**
+ * @return the maximum useable zoom level for tiles
+ */
+ public int getMaxZoomLevel()
{
// Ask current map source what maximum zoom is
int maxZoom = (_mapSource == null?0:_mapSource.getMaxZoomLevel());
- return (_zoom > maxZoom);
+ return maxZoom;
+
}
/**
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
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
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
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
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
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
--- /dev/null
+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();
+ }
+}