--- /dev/null
+*.class
+*.jar
--- /dev/null
+Manifest-Version: 1.0\r
+Main-Class: tim.prune.GpsPrune\r
+\r
--- /dev/null
+set -e
+# Build script
+# Version number
+PRUNENAME=gpsprune_19.2
+# remove compile directory
+rm -rf compile
+# remove dist directory
+rm -rf dist
+# create compile directory
+mkdir compile
+echo "building..."
+# compile java
+# TODO: If your java3d libraries are not under /usr/share/java, please edit the following line with the correct path
+javac -d compile -cp /usr/share/java/vecmath.jar:/usr/share/java/j3dutils.jar:/usr/share/java/j3dcore.jar $( find src -name "*.java" -print )
+# add other required resources
+cp -r src/tim/prune/lang compile/tim/prune/
+cp -r src/tim/prune/*.txt compile/tim/prune/
+cp -r src/tim/prune/gui/images compile/tim/prune/gui/
+cp src/tim/prune/function/srtm/srtmtiles.dat compile/tim/prune/function/srtm
+# make dist directory
+mkdir dist
+# build into jar file
+jar cfm dist/${PRUNENAME}.jar buildtools/MANIFEST.MF -C compile .
+# finished!
+echo "build complete"
+
--- /dev/null
+<?xml version="1.0"?>\r
+\r
+<project name="GpsPrune" default="jar" basedir=".">\r
+ <!-- This file was supplied by Denny from the OpenSuse build service project -->\r
+\r
+ <property file="version.properties" />\r
+ <property name="src" value="src" />\r
+ <property name="build" value="build" />\r
+ <property name="dist" value="dist" />\r
+\r
+ <path id="j3dlibs">\r
+ <fileset dir="/usr/share/java/" includes="*.jar"/>\r
+ </path>\r
+\r
+ <!-- Init -->\r
+\r
+ <target name="init">\r
+ <mkdir dir="${build}" />\r
+ <mkdir dir="${dist}" />\r
+ </target>\r
+\r
+ <!-- Clean -->\r
+\r
+ <target name="clean" description="Clean build directory">\r
+\r
+ <delete dir="${build}" />\r
+ <delete dir="${dist}" />\r
+ </target>\r
+\r
+ <!-- Build -->\r
+\r
+ <target name="build" description="Build from source" depends="clean,init">\r
+ <javac srcdir="${src}" destdir="${build}">\r
+ <classpath refid="j3dlibs" />\r
+ </javac>\r
+ </target>\r
+\r
+ <!-- Jar -->\r
+\r
+ <target name="jar" description="Create jar file" depends="build">\r
+ <copy todir="${build}/tim/prune/lang">\r
+ <fileset dir="${src}/tim/prune/lang"/>\r
+ </copy>\r
+ <copy todir="${build}/tim/prune/gui/images">\r
+ <fileset dir="${src}/tim/prune/gui/images"/>\r
+ </copy>\r
+ <copy todir="${build}/tim/prune/">\r
+ <fileset dir="${src}/tim/prune">\r
+ <include name="*.txt"/>\r
+ </fileset>\r
+ </copy>\r
+ <copy file="${src}/tim/prune/function/srtm/srtmtiles.dat" todir="${build}/tim/prune/function/srtm"/>\r
+ <fileset dir="${build}" casesensitive="yes">\r
+ <include name="build/*" />\r
+ </fileset>\r
+ <jar destfile="${dist}/${ant.project.name}-${version}.jar" basedir="${build}">\r
+ <!-- define MANIFEST.MF -->\r
+ <manifest>\r
+ <!-- Who is building this jar? -->\r
+ <attribute name="Built-By" value="${user.name}" />\r
+ <!-- Information about the program itself -->\r
+ <attribute name="Implementation-Vendor" value="ActivityWorkshop.net" />\r
+ <attribute name="Implementation-Title" value="${ant.project.name}" />\r
+ <attribute name="Implementation-Version" value="${version}" />\r
+ <!-- details -->\r
+ <attribute name="Main-Class" value="tim.prune.GpsPrune" />\r
+ <attribute name="Class-Path" value="${ant.project.name}.jar" />\r
+ </manifest>\r
+ </jar>\r
+ </target>\r
+</project>\r
--- /dev/null
+How to build GpsPrune
+=====================
+
+As with any Java program, there are several different ways to build the GpsPrune application from source, and which method you choose depends a lot on what other Java technologies you're familiar with.
+
+The most popular methods used to build GpsPrune are probably:
+
+ 1. Using any Java IDE, and linking the project to your installed Java3d jars
+ 2. Using a build script
+ 2. Using ant
+ 3. Using maven
+ 4. Using gradle
+
+Note: in order not to pollute the root directory with every configuration file for each and every possible build tool, they have all been collected into the separate "buildtools" subdirectory. For some of these methods (such as ant and maven), the corresponding configuration files should first be selected and moved up to the parent directory before running the tool from there.
+
+
+1. Using an IDE
+================
+
+There are many IDEs to choose from, including Eclipse, IntelliJ, Netbeans and many others. A list can be found at https://en.wikipedia.org/wiki/Java_IDEs#Java
+
+Most of these will have the ability to automatically build, run and debug the GpsPrune application, providing that they are told where the Java3d libraries can be found. This can be done by editing the project dependencies to additionally include the external jar files. Where these jar files can be found will vary from system to system, but on most linux systems they are in /usr/share/java/.
+
+
+2. Using a build script
+========================
+
+Requirements: some kind of linux, unix or osx system; build.sh modified to your own paths
+
+First it is necessary to manually edit the build.sh script to add the paths to where your java3d libraries are installed. This tells the javac tool where to find them for compilation. An example is shown in build.sh using the -cp parameter to javac with a colon-separated list of jar files.
+
+To compile the code and build a jar file, run:
+ sh buildtools/build.sh
+
+It should be easily possible to modify the script to run on Windows systems, and any contributions in this direction would be gratefully received.
+
+
+3. Using ant
+=============
+
+Requirements: ant already installed (eg apt-get install ant); build.xml and version.properties
+Firstly, these two configuration files ("build.xml" and "version.properties") should be moved from the "buildtools" directory to the parent directory.
+Secondly, the paths to the java3d jars should be checked and if necessary corrected in the build.xml file. This is shown in the section beginning with <path id="j3dlibs">.
+Thirdly, from this directory where the configuration files (and the src directory) are located, run the ant tool:
+ ant
+
+This will firstly compile the java files from "src" into class files under "compile", and then bundle these together (along with the required resources) into a jar file under "dist".
+
+
+4. Using Maven
+===============
+
+Requirements: Maven already installed (eg apt-get install maven); pom.xml
+Also an internet connection is required whenever a target is run for the first time.
+Note that as well as downloading a variety of plugins, Maven will also download its own versions of the java3d dependencies and store them in its own "repository" to use for the building step.
+
+Before you can use Maven, you need to move the file "pom.xml" from the "buildtools" directory into the parent directory.
+
+Then, to build GpsPrune, execute from this directory:
+ mvn clean install
+
+This doesn't actually install anything, but compiles the code and produces a jar file. The resulting jar will be placed in the target/ folder.
+
+To rebuild, package and run GpsPrune, execute:
+ mvn clean install exec:java
+
+The resources in pom.xml are changed (so they are not the maven common ones) because of the backward compatibility with the project.
+
+4.1. Using Maven to setup Eclipse
+==================================
+
+Requirements: Eclipse already installed (eg apt-get install eclipse-jdt); Maven setup as above
+You can import the project in Eclipse either by calling:
+ mvn eclipse:eclipse
+
+and then File > Import > Existing Projects into Workspace or using File > Import > Existing Maven Projects.
+
+
+5. Using Gradle
+================
+
+TODO
+Requirements: Gradle already installed (eg apt-get install gradle); build.gradle file
+Any contributions to provide a suitable gradle file would be gratefully received.
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>tim.prune</groupId>
+ <artifactId>gpsprune</artifactId>
+ <version>19.2</version>
+ <packaging>jar</packaging>
+
+ <name>tim.prune.gpsprune</name>
+ <url>https://github.com/activityworkshop/GpsPrune</url>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <app.mainClass>tim.prune.GpsPrune</app.mainClass>
+ <java3d.version>1.3.1</java3d.version>
+ <j3dutils.version>1.5.2</j3dutils.version>
+ </properties>
+ <repositories>
+ <repository>
+ <id>talanlabs-releases-repository</id>
+ <url>http://nexus.talanlabs.com/content/repositories/releases/</url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>java3d</groupId>
+ <artifactId>j3d-core</artifactId>
+ <version>${java3d.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>java3d</groupId>
+ <artifactId>vecmath</artifactId>
+ <version>${java3d.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>java3d</groupId>
+ <artifactId>j3dutils</artifactId>
+ <version>${j3dutils.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <outputDirectory>${project.build.directory}/classes</outputDirectory>
+ <finalName>${project.artifactId}_${project.version}</finalName>
+ <sourceDirectory>${project.basedir}/</sourceDirectory>
+ <resources>
+ <resource>
+ <directory>${project.basedir}/src/</directory>
+ <includes>
+ <include>tim/prune/gui/images/*</include>
+ <include>tim/prune/lang/*</include>
+ <include>tim/prune/function/srtm/srtmtiles.dat</include>
+ <include>tim/prune/*.txt</include>
+ </includes>
+ </resource>
+ </resources>
+
+ <pluginManagement>
+ <!-- lock down plugins versions to avoid using Maven defaults -->
+ <plugins>
+ <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <version>3.1.0</version>
+ </plugin>
+ <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.0.2</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.0</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.0.2</version>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>${app.mainClass}</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+
+ </plugin>
+ <plugin>
+ <artifactId>maven-install-plugin</artifactId>
+ <version>2.5.2</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>2.8.2</version>
+ </plugin>
+ <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
+ <plugin>
+ <artifactId>maven-site-plugin</artifactId>
+ <version>3.7.1</version>
+ </plugin>
+ <plugin>
+ <artifactId>maven-project-info-reports-plugin</artifactId>
+ <version>3.0.0</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-eclipse-plugin</artifactId>
+ <version>2.10</version>
+ <configuration>
+ <downloadSources>true</downloadSources>
+ <downloadJavadocs>true</downloadJavadocs>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.6.0</version>
+ <configuration>
+ <mainClass>${app.mainClass}</mainClass>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </pluginManagement>
+ </build>
+
+</project>
--- /dev/null
+version=19.2
{
// Instance variables
private JFrame _frame = null;
+ private String _titlePrefix = null;
private Track _track = null;
private TrackInfo _trackInfo = null;
private int _lastSavePosition = 0;
public App(JFrame inFrame)
{
_frame = inFrame;
+ _titlePrefix = _frame.getTitle();
_undoStack = new UndoStack();
_track = new Track();
_trackInfo = new TrackInfo(_track);
}
+ /**
+ * 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
_busyLoading = false;
// load next file if there's a queue
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);
+ }
+ }
}
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
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
}
+ /**
+ * 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;
}
}
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);
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
{
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)
urlstr = "http://" + urlstr;
}
// check trailing /
- if (!urlstr.endsWith("/")) {
+ if (!urlstr.endsWith("/") && !urlstr.contains("?")) {
urlstr = urlstr + "/";
}
// Validate current url, return null if not ok
* @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;
+
}
/**
/**
* 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)
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
*/
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();
+ }
+}
+++ /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;
- }
-}