");
JEditorPane descPane = new JEditorPane("text/html", descBuffer.toString());
descPane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
diff --git a/tim/prune/function/AddAltitudeOffset.java b/tim/prune/function/AddAltitudeOffset.java
index 85c52de..c98ab3e 100644
--- a/tim/prune/function/AddAltitudeOffset.java
+++ b/tim/prune/function/AddAltitudeOffset.java
@@ -19,8 +19,9 @@ import tim.prune.App;
import tim.prune.GenericFunction;
import tim.prune.I18nManager;
import tim.prune.config.Config;
-import tim.prune.data.Altitude;
import tim.prune.data.Field;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
/**
* Class to provide the function to add an altitude offset to a track range
@@ -31,7 +32,7 @@ public class AddAltitudeOffset extends GenericFunction
private JLabel _descLabel = null;
private JTextField _editField = null;
private JButton _okButton = null;
- private Altitude.Format _altFormat = Altitude.Format.NO_FORMAT;
+ private Unit _altUnit = null;
/**
@@ -152,11 +153,11 @@ public class AddAltitudeOffset extends GenericFunction
*/
private void setLabelText()
{
- _altFormat = Altitude.Format.FEET;
+ _altUnit = UnitSetLibrary.UNITS_FEET;
if (Config.getUnitSet().getAltitudeUnit().isStandard()) {
- _altFormat = Altitude.Format.METRES;
+ _altUnit = UnitSetLibrary.UNITS_METRES;
}
- final String unitKey = (_altFormat==Altitude.Format.METRES?"units.metres.short":"units.feet.short");
+ final String unitKey = _altUnit.getShortnameKey();
_descLabel.setText(I18nManager.getText("dialog.addaltitude.desc") + " (" + I18nManager.getText(unitKey) + ")");
}
@@ -166,7 +167,7 @@ public class AddAltitudeOffset extends GenericFunction
private void finish()
{
// Pass information back to App to complete function
- _app.finishAddAltitudeOffset(_editField.getText(), _altFormat);
+ _app.finishAddAltitudeOffset(_editField.getText(), _altUnit);
_dialog.dispose();
}
}
diff --git a/tim/prune/function/AddMapSourceDialog.java b/tim/prune/function/AddMapSourceDialog.java
index da1f15e..52a8874 100644
--- a/tim/prune/function/AddMapSourceDialog.java
+++ b/tim/prune/function/AddMapSourceDialog.java
@@ -105,9 +105,21 @@ public class AddMapSourceDialog
KeyAdapter keyListener = new KeyAdapter() {
public void keyReleased(KeyEvent e) {
super.keyReleased(e);
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ _addDialog.dispose();
+ }
+ else {
+ enableOK();
+ }
+ }
+ };
+ // Listener for any gui changes (to enable ok when anything changes on an edit)
+ ActionListener okEnabler = new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
enableOK();
}
};
+
// openstreetmap panel
JPanel osmPanel = new JPanel();
osmPanel.setLayout(new BorderLayout());
@@ -144,6 +156,8 @@ public class AddMapSourceDialog
radioGroup.add(_baseTypeRadios[i]);
c.gridx = 2+i; c.weightx = 0.0;
gbPanel.add(_baseTypeRadios[i], c);
+ // Each type radio needs listener to call enableOk()
+ _baseTypeRadios[i].addActionListener(okEnabler);
}
// Top layer
@@ -161,6 +175,8 @@ public class AddMapSourceDialog
radioGroup.add(_topTypeRadios[i]);
c.gridx = 2+i; c.weightx = 0.0;
gbPanel.add(_topTypeRadios[i], c);
+ // Each type radio needs listener to call enableOk()
+ _topTypeRadios[i].addActionListener(okEnabler);
}
// Max zoom
c.gridx = 0; c.gridy = 3;
@@ -169,6 +185,8 @@ public class AddMapSourceDialog
for (int i=10; i<=20; i++) {
_oZoomCombo.addItem("" + i);
}
+ // zoom dropdown needs listener to call enableOk()
+ _oZoomCombo.addActionListener(okEnabler);
c.gridx = 1;
gbPanel.add(_oZoomCombo, c);
osmPanel.add(gbPanel, BorderLayout.NORTH);
diff --git a/tim/prune/function/DownloadOsmFunction.java b/tim/prune/function/DownloadOsmFunction.java
index e3d3627..76c69da 100644
--- a/tim/prune/function/DownloadOsmFunction.java
+++ b/tim/prune/function/DownloadOsmFunction.java
@@ -243,7 +243,7 @@ public class DownloadOsmFunction extends GenericFunction implements Runnable
*/
public void run()
{
- final String url = "http://xapi.openstreetmap.org/api/0.6/map?bbox=" +
+ final String url = "http://www.overpass-api.de/api/xapi?map?bbox=" +
_latLonLabels[1].getText() + "," + _latLonLabels[3].getText() + "," +
_latLonLabels[2].getText() + "," + _latLonLabels[0].getText();
diff --git a/tim/prune/function/Export3dFunction.java b/tim/prune/function/Export3dFunction.java
index 5b12125..9e92e62 100644
--- a/tim/prune/function/Export3dFunction.java
+++ b/tim/prune/function/Export3dFunction.java
@@ -9,7 +9,7 @@ import tim.prune.GenericFunction;
public abstract class Export3dFunction extends GenericFunction
{
/** altitude exaggeration factor */
- protected double _altFactor = 50.0;
+ protected double _altFactor = 5.0;
/**
* Required constructor
diff --git a/tim/prune/function/FullRangeDetails.java b/tim/prune/function/FullRangeDetails.java
index a0bae65..d9dbb70 100644
--- a/tim/prune/function/FullRangeDetails.java
+++ b/tim/prune/function/FullRangeDetails.java
@@ -8,7 +8,6 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
-import java.text.NumberFormat;
import javax.swing.BorderFactory;
import javax.swing.JButton;
@@ -20,9 +19,7 @@ import tim.prune.App;
import tim.prune.GenericFunction;
import tim.prune.I18nManager;
import tim.prune.config.Config;
-import tim.prune.data.Altitude;
-import tim.prune.data.AltitudeRange;
-import tim.prune.data.DataPoint;
+import tim.prune.data.RangeStats;
import tim.prune.data.Selection;
import tim.prune.data.Unit;
import tim.prune.gui.DisplayUtils;
@@ -63,10 +60,6 @@ public class FullRangeDetails extends GenericFunction
/** Labels for vertical speed */
private JLabel _totalVertSpeedLabel, _movingVertSpeedLabel = null;
- /** Number formatter for one decimal place */
- private static final NumberFormat FORMAT_ONE_DP = NumberFormat.getNumberInstance();
- /** Flexible number formatter for different decimal places */
- private NumberFormat _distanceFormatter = NumberFormat.getInstance();
/**
* Constructor
@@ -75,8 +68,6 @@ public class FullRangeDetails extends GenericFunction
public FullRangeDetails(App inApp)
{
super(inApp);
- FORMAT_ONE_DP.setMaximumFractionDigits(1);
- FORMAT_ONE_DP.setMinimumFractionDigits(1);
}
/** Get the name key */
@@ -250,11 +241,14 @@ public class FullRangeDetails extends GenericFunction
private void updateDetails()
{
Selection selection = _app.getTrackInfo().getSelection();
+ // Do the calculations with a separate class
+ RangeStats stats = new RangeStats(_app.getTrackInfo().getTrack(), selection.getStart(), selection.getEnd());
+
// Number of points
- _numPointsLabel.setText("" + (selection.getEnd()-selection.getStart()+1));
+ _numPointsLabel.setText("" + stats.getNumPoints());
// Number of segments
- _numSegsLabel.setText("" + selection.getNumSegments());
- final boolean isMultiSegments = (selection.getNumSegments() > 1);
+ _numSegsLabel.setText("" + stats.getNumSegments());
+ final boolean isMultiSegments = (stats.getNumSegments() > 1);
// Set visibility of third column accordingly
_movingDistanceLabel.setVisible(isMultiSegments);
_movingDurationLabel.setVisible(isMultiSegments);
@@ -265,132 +259,78 @@ public class FullRangeDetails extends GenericFunction
_movingGradientLabel.setVisible(isMultiSegments);
_movingVertSpeedLabel.setVisible(isMultiSegments);
- // Distance in current units
+ // Total and moving distance in current units
final Unit distUnit = Config.getUnitSet().getDistanceUnit();
final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
- final double selectionDistance = selection.getDistance();
- _totalDistanceLabel.setText(roundedNumber(selectionDistance) + " " + distUnitsStr);
+ _totalDistanceLabel.setText(DisplayUtils.roundedNumber(stats.getTotalDistance()) + " " + distUnitsStr);
+ _movingDistanceLabel.setText(DisplayUtils.roundedNumber(stats.getMovingDistance()) + " " + distUnitsStr);
// Duration
- long numSecs = selection.getNumSeconds();
- _totalDurationLabel.setText(DisplayUtils.buildDurationString(numSecs));
+ _totalDurationLabel.setText(DisplayUtils.buildDurationString(stats.getTotalDurationInSeconds()));
+ _movingDurationLabel.setText(DisplayUtils.buildDurationString(stats.getMovingDurationInSeconds()));
+
// Climb and descent
final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
- if (selection.getAltitudeRange().hasRange()) {
- _totalClimbLabel.setText(selection.getAltitudeRange().getClimb(altUnit) + altUnitsStr);
- _totalDescentLabel.setText(selection.getAltitudeRange().getDescent(altUnit) + altUnitsStr);
+ if (stats.getTotalAltitudeRange().hasRange()) {
+ _totalClimbLabel.setText(stats.getTotalAltitudeRange().getClimb(altUnit) + altUnitsStr);
+ _totalDescentLabel.setText(stats.getTotalAltitudeRange().getDescent(altUnit) + altUnitsStr);
}
else {
_totalClimbLabel.setText("");
_totalDescentLabel.setText("");
}
+ if (stats.getMovingAltitudeRange().hasRange()) {
+ _movingClimbLabel.setText(stats.getMovingAltitudeRange().getClimb(altUnit) + altUnitsStr);
+ _movingDescentLabel.setText(stats.getMovingAltitudeRange().getDescent(altUnit) + altUnitsStr);
+ }
+ else {
+ _movingClimbLabel.setText("");
+ _movingDescentLabel.setText("");
+ }
// Overall pace and speed
final String speedUnitsStr = I18nManager.getText(Config.getUnitSet().getSpeedUnit().getShortnameKey());
- if (numSecs > 0 && selectionDistance > 0)
+ long numSecs = stats.getTotalDurationInSeconds();
+ double dist = stats.getTotalDistance();
+ if (numSecs > 0 && dist > 0)
{
- _totalPaceLabel.setText(
- DisplayUtils.buildDurationString((long) (numSecs/selectionDistance))
+ _totalSpeedLabel.setText(DisplayUtils.roundedNumber(dist/numSecs*3600.0) + " " + speedUnitsStr);
+ _totalPaceLabel.setText(DisplayUtils.buildDurationString((long) (numSecs/dist))
+ " / " + distUnitsStr);
- _totalSpeedLabel.setText(roundedNumber(selectionDistance/numSecs*3600.0)
- + " " + speedUnitsStr);
}
else {
- _totalPaceLabel.setText("");
_totalSpeedLabel.setText("");
+ _totalPaceLabel.setText("");
}
-
- // Moving distance
- double movingDist = selection.getMovingDistance();
- _movingDistanceLabel.setText(roundedNumber(movingDist) + " " + distUnitsStr);
- // Moving average speed
- long numMovingSecs = selection.getMovingSeconds();
- if (numMovingSecs > 0)
+ // and same for within the segments
+ numSecs = stats.getMovingDurationInSeconds();
+ dist = stats.getMovingDistance();
+ if (numSecs > 0 && dist > 0)
{
- _movingDurationLabel.setText(DisplayUtils.buildDurationString(numMovingSecs));
- _movingSpeedLabel.setText(roundedNumber(movingDist/numMovingSecs*3600.0)
- + " " + speedUnitsStr);
- _movingPaceLabel.setText(
- DisplayUtils.buildDurationString((long) (numMovingSecs/movingDist))
+ _movingSpeedLabel.setText(DisplayUtils.roundedNumber(dist/numSecs*3600.0) + " " + speedUnitsStr);
+ _movingPaceLabel.setText(DisplayUtils.buildDurationString((long) (numSecs/dist))
+ " / " + distUnitsStr);
}
- else
- {
- _movingDurationLabel.setText("");
+ else {
_movingSpeedLabel.setText("");
_movingPaceLabel.setText("");
}
- // Moving gradient and moving climb/descent
- Altitude firstAlt = null, lastAlt = null;
- Altitude veryFirstAlt = null, veryLastAlt = null;
- AltitudeRange altRange = new AltitudeRange();
- double movingHeightDiff = 0.0;
- if (movingDist > 0.0)
- {
- for (int pNum = selection.getStart(); pNum <= selection.getEnd(); pNum++)
- {
- DataPoint p = _app.getTrackInfo().getTrack().getPoint(pNum);
- if (p != null && !p.isWaypoint())
- {
- // If we're starting a new segment, calculate the height diff of the previous one
- if (p.getSegmentStart())
- {
- if (firstAlt != null && firstAlt.isValid() && lastAlt != null && lastAlt.isValid())
- movingHeightDiff = movingHeightDiff + lastAlt.getMetricValue() - firstAlt.getMetricValue();
- firstAlt = null; lastAlt = null;
- }
- Altitude alt = p.getAltitude();
- if (alt != null && alt.isValid())
- {
- if (firstAlt == null) firstAlt = alt;
- else lastAlt = alt;
- if (veryFirstAlt == null) veryFirstAlt = alt;
- else veryLastAlt = alt;
- }
- // Keep track of climb and descent too
- if (p.getSegmentStart())
- altRange.ignoreValue(alt);
- else
- altRange.addValue(alt);
- }
- }
- // deal with last segment
- if (firstAlt != null && firstAlt.isValid() && lastAlt != null && lastAlt.isValid())
- movingHeightDiff = movingHeightDiff + lastAlt.getMetricValue() - firstAlt.getMetricValue();
- final double metricMovingDist = movingDist / distUnit.getMultFactorFromStd(); // convert back to metres
- final double gradient = movingHeightDiff * 100.0 / metricMovingDist;
- _movingGradientLabel.setText(FORMAT_ONE_DP.format(gradient) + " %");
- }
- if (!altRange.hasRange()) {
- _movingGradientLabel.setText("");
- }
- final boolean hasAltitudes = veryFirstAlt != null && veryFirstAlt.isValid() && veryLastAlt != null && veryLastAlt.isValid();
-
- // Total gradient
- final double metreDist = selection.getDistance() / distUnit.getMultFactorFromStd(); // convert back to metres
- if (hasAltitudes && metreDist > 0.0)
- {
- // got an altitude and range
- int altDiffInMetres = veryLastAlt.getValue(Altitude.Format.METRES) - veryFirstAlt.getValue(Altitude.Format.METRES);
- double gradient = altDiffInMetres * 100.0 / metreDist;
- _totalGradientLabel.setText(FORMAT_ONE_DP.format(gradient) + " %");
+ // Gradient
+ if (stats.getTotalAltitudeRange().hasRange()) {
+ _totalGradientLabel.setText(DisplayUtils.formatOneDp(stats.getTotalGradient()) + " %");
}
else {
- // no altitude given
_totalGradientLabel.setText("");
}
-
- // Moving climb/descent
- if (altRange.hasRange()) {
- _movingClimbLabel.setText(altRange.getClimb(altUnit) + altUnitsStr);
- _movingDescentLabel.setText(altRange.getDescent(altUnit) + altUnitsStr);
+ if (stats.getMovingAltitudeRange().hasRange()) {
+ _movingGradientLabel.setText(DisplayUtils.formatOneDp(stats.getMovingGradient()) + " %");
}
else {
- _movingClimbLabel.setText("");
- _movingDescentLabel.setText("");
+ _movingGradientLabel.setText("");
}
+
// Maximum speed
SpeedData speeds = new SpeedData(_app.getTrackInfo().getTrack());
speeds.init(Config.getUnitSet());
@@ -402,7 +342,7 @@ public class FullRangeDetails extends GenericFunction
}
}
if (maxSpeed > 0.0) {
- _maxSpeedLabel.setText(roundedNumber(maxSpeed) + " " + speedUnitsStr);
+ _maxSpeedLabel.setText(DisplayUtils.roundedNumber(maxSpeed) + " " + speedUnitsStr);
}
else {
_maxSpeedLabel.setText("");
@@ -410,40 +350,17 @@ public class FullRangeDetails extends GenericFunction
// vertical speed
final String vertSpeedUnitsStr = I18nManager.getText(Config.getUnitSet().getVerticalSpeedUnit().getShortnameKey());
- if (hasAltitudes && metreDist > 0.0 && numSecs > 0)
+ if (stats.getMovingAltitudeRange().hasRange() && stats.getTotalDurationInSeconds() > 0)
{
- // got an altitude and time - do total
- final int altDiffInMetres = veryLastAlt.getValue(Altitude.Format.METRES) - veryFirstAlt.getValue(Altitude.Format.METRES);
- final double altDiff = altDiffInMetres * altUnit.getMultFactorFromStd();
- _totalVertSpeedLabel.setText(roundedNumber(altDiff/numSecs) + " " + vertSpeedUnitsStr);
- // and moving
- _movingVertSpeedLabel.setText(roundedNumber(movingHeightDiff * altUnit.getMultFactorFromStd() / numMovingSecs) + " " + vertSpeedUnitsStr);
+ // got an altitude and time - do totals
+ _totalVertSpeedLabel.setText(DisplayUtils.roundedNumber(stats.getTotalVerticalSpeed()) + " " + vertSpeedUnitsStr);
+ _movingVertSpeedLabel.setText(DisplayUtils.roundedNumber(stats.getMovingVerticalSpeed()) + " " + vertSpeedUnitsStr);
}
- else {
+ else
+ {
// no vertical speed available
_totalVertSpeedLabel.setText("");
_movingVertSpeedLabel.setText("");
}
}
-
- /**
- * Format a number to a sensible precision
- * @param inDist distance
- * @return formatted String
- */
- private String roundedNumber(double inDist)
- {
- // Set precision of formatter
- int numDigits = 0;
- if (inDist < 1.0)
- numDigits = 3;
- else if (inDist < 10.0)
- numDigits = 2;
- else if (inDist < 100.0)
- numDigits = 1;
- // set formatter
- _distanceFormatter.setMaximumFractionDigits(numDigits);
- _distanceFormatter.setMinimumFractionDigits(numDigits);
- return _distanceFormatter.format(inDist);
- }
}
diff --git a/tim/prune/function/GetWikipediaFunction.java b/tim/prune/function/GetWikipediaFunction.java
index 0e6d9eb..4c11bf9 100644
--- a/tim/prune/function/GetWikipediaFunction.java
+++ b/tim/prune/function/GetWikipediaFunction.java
@@ -71,21 +71,48 @@ public class GetWikipediaFunction extends GenericDownloaderFunction
lat = (coords[0] + coords[2]) / 2.0;
lon = (coords[1] + coords[3]) / 2.0;
}
- else {
+ else
+ {
lat = point.getLatitude().getDouble();
lon = point.getLongitude().getDouble();
}
- String descMessage = "";
- InputStream inStream = null;
+ // Firstly try the local language
+ String lang = I18nManager.getText("wikipedia.lang");
+ submitSearch(lat, lon, lang);
+ // If we didn't get anything, try a secondary language
+ if (_trackListModel.isEmpty() && _errorMessage == null && lang.equals("als")) {
+ submitSearch(lat, lon, "de");
+ }
+ // If still nothing then try english
+ if (_trackListModel.isEmpty() && _errorMessage == null && !lang.equals("en")) {
+ submitSearch(lat, lon, "en");
+ }
+ // Set status label according to error or "none found", leave blank if ok
+ if (_errorMessage == null && _trackListModel.isEmpty()) {
+ _errorMessage = I18nManager.getText("dialog.gpsies.nonefound");
+ }
+ _statusLabel.setText(_errorMessage == null ? "" : _errorMessage);
+ }
+
+ /**
+ * Submit the search for the given parameters
+ * @param inLat latitude
+ * @param inLon longitude
+ * @param inLang language code to use, such as en or de
+ */
+ private void submitSearch(double inLat, double inLon, String inLang)
+ {
// Example http://api.geonames.org/findNearbyWikipedia?lat=47&lng=9
String urlString = "http://api.geonames.org/findNearbyWikipedia?lat=" +
- lat + "&lng=" + lon + "&maxRows=" + MAX_RESULTS
- + "&radius=" + MAX_DISTANCE + "&lang=" + I18nManager.getText("wikipedia.lang")
+ inLat + "&lng=" + inLon + "&maxRows=" + MAX_RESULTS
+ + "&radius=" + MAX_DISTANCE + "&lang=" + inLang
+ "&username=" + GEONAMES_USERNAME;
// Parse the returned XML with a special handler
GetWikipediaXmlHandler xmlHandler = new GetWikipediaXmlHandler();
+ InputStream inStream = null;
+
try
{
URL url = new URL(urlString);
@@ -94,7 +121,7 @@ public class GetWikipediaFunction extends GenericDownloaderFunction
saxParser.parse(inStream, xmlHandler);
}
catch (Exception e) {
- descMessage = e.getClass().getName() + " - " + e.getMessage();
+ _errorMessage = e.getClass().getName() + " - " + e.getMessage();
}
// Close stream and ignore errors
try {
@@ -104,21 +131,18 @@ public class GetWikipediaFunction extends GenericDownloaderFunction
ArrayList trackList = xmlHandler.getTrackList();
_trackListModel.addTracks(trackList);
- // Set status label according to error or "none found", leave blank if ok
- if (descMessage.equals("") && (trackList == null || trackList.size() == 0)) {
- descMessage = I18nManager.getText("dialog.gpsies.nonefound");
- }
- _statusLabel.setText(descMessage);
// Show error message if any
- if (trackList == null || trackList.size() == 0) {
+ if (_trackListModel.isEmpty())
+ {
String error = xmlHandler.getErrorMessage();
- if (error != null && !error.equals("")) {
+ if (error != null && !error.equals(""))
+ {
_app.showErrorMessageNoLookup(getNameKey(), error);
+ _errorMessage = error;
}
}
}
-
/**
* Load the selected point(s)
*/
diff --git a/tim/prune/function/PasteCoordinates.java b/tim/prune/function/PasteCoordinates.java
index 7587949..0228014 100644
--- a/tim/prune/function/PasteCoordinates.java
+++ b/tim/prune/function/PasteCoordinates.java
@@ -29,6 +29,8 @@ import tim.prune.data.DataPoint;
import tim.prune.data.Field;
import tim.prune.data.Latitude;
import tim.prune.data.Longitude;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
import tim.prune.gui.GuiGridLayout;
/**
@@ -75,7 +77,7 @@ public class PasteCoordinates extends GenericFunction
// MAYBE: Paste clipboard into the edit field
_coordField.setText("");
_nameField.setText("");
- boolean useMetres = (Config.getUnitSet().getDefaultAltitudeFormat() == Altitude.Format.METRES);
+ boolean useMetres = (Config.getUnitSet().getAltitudeUnit() == UnitSetLibrary.UNITS_METRES);
_altUnitsDropDown.setSelectedIndex(useMetres?0:1);
enableOK();
_dialog.setVisible(true);
@@ -236,9 +238,9 @@ public class PasteCoordinates extends GenericFunction
if (inValue3 != null)
{
// Look at altitude units dropdown
- final Altitude.Format altFormat = (_altUnitsDropDown.getSelectedIndex()==0?
- Altitude.Format.METRES:Altitude.Format.FEET);
- alt = new Altitude(inValue3, altFormat);
+ final Unit altUnit = (_altUnitsDropDown.getSelectedIndex()==0?
+ UnitSetLibrary.UNITS_METRES : UnitSetLibrary.UNITS_FEET);
+ alt = new Altitude(inValue3, altUnit);
if (!alt.isValid()) {alt = null;}
}
// See if value1 can be lat and value2 lon:
diff --git a/tim/prune/function/SearchWikipediaNames.java b/tim/prune/function/SearchWikipediaNames.java
index 0458943..5712983 100644
--- a/tim/prune/function/SearchWikipediaNames.java
+++ b/tim/prune/function/SearchWikipediaNames.java
@@ -67,7 +67,7 @@ public class SearchWikipediaNames extends GenericDownloaderFunction
JOptionPane.QUESTION_MESSAGE, null, null, "");
if (search != null)
{
- _searchTerm = search.toString();
+ _searchTerm = search.toString().toLowerCase();
if (!_searchTerm.equals("")) {
super.begin();
}
@@ -81,29 +81,43 @@ public class SearchWikipediaNames extends GenericDownloaderFunction
{
_statusLabel.setText(I18nManager.getText("confirm.running"));
- String descMessage = "";
- InputStream inStream = null;
+ // Replace awkward characters with character equivalents
+ final String searchTerm = encodeSearchTerm(_searchTerm);
- // language (only de and en available)
+ // Firstly try the local language
String lang = I18nManager.getText("wikipedia.lang");
- if (lang.equals("de") || lang.equals("als")) {
- lang = "de";
+ submitSearch(searchTerm, lang);
+ // If we didn't get anything, try a secondary language
+ if (_trackListModel.isEmpty() && _errorMessage == null && lang.equals("als")) {
+ submitSearch(searchTerm, "de");
}
- else {
- lang = "en";
+ // If still nothing then try english
+ if (_trackListModel.isEmpty() && _errorMessage == null && !lang.equals("en")) {
+ submitSearch(searchTerm, "en");
}
- // Replace awkward characters with character equivalents
- String searchTerm;
- try {
- searchTerm = URLEncoder.encode(_searchTerm, "UTF-8");
- } catch (UnsupportedEncodingException e1) {
- searchTerm = _searchTerm;
+
+ // Set status label according to error or "none found", leave blank if ok
+ if (_errorMessage == null && _trackListModel.isEmpty()) {
+ _errorMessage = I18nManager.getText("dialog.gpsies.nonefound");
}
- // Example http://ws.geonames.org/wikipediaSearch?q=london&maxRows=10
- String urlString = "http://api.geonames.org/wikipediaSearch?title=" + searchTerm
- + "&maxRows=" + MAX_RESULTS + "&lang=" + lang + "&username=" + GEONAMES_USERNAME;
+ _statusLabel.setText(_errorMessage == null ? "" : _errorMessage);
+ }
+
+ /**
+ * Submit the given search to the server
+ * @param inSearchTerm search term
+ * @param inLang language code such as en, de
+ */
+ private void submitSearch(String inSearchTerm, String inLang)
+ {
+ // System.out.println("Searching for '" + inSearchTerm + "' with lang: " + inLang);
+
+ String urlString = "http://api.geonames.org/wikipediaSearch?title=" + inSearchTerm
+ + "&maxRows=" + MAX_RESULTS + "&lang=" + inLang + "&username=" + GEONAMES_USERNAME;
// Parse the returned XML with a special handler
GetWikipediaXmlHandler xmlHandler = new GetWikipediaXmlHandler();
+ InputStream inStream = null;
+
try
{
URL url = new URL(urlString);
@@ -112,7 +126,7 @@ public class SearchWikipediaNames extends GenericDownloaderFunction
saxParser.parse(inStream, xmlHandler);
}
catch (Exception e) {
- descMessage = e.getClass().getName() + " - " + e.getMessage();
+ _errorMessage = e.getClass().getName() + " - " + e.getMessage();
}
// Close stream and ignore errors
try {
@@ -122,15 +136,59 @@ public class SearchWikipediaNames extends GenericDownloaderFunction
ArrayList trackList = xmlHandler.getTrackList();
// TODO: Do a better job of sorting replies by relevance - use three different lists
_trackListModel.addTracks(trackList);
+ }
- // Set status label according to error or "none found", leave blank if ok
- if (descMessage.equals("") && (trackList == null || trackList.size() == 0)) {
- descMessage = I18nManager.getText("dialog.gpsies.nonefound");
+ /**
+ * Replace tricky characters in the search term and encode the others
+ * @param inSearchTerm entered search term
+ * @return modified search term to give to geonames
+ */
+ private static final String encodeSearchTerm(String inSearchTerm)
+ {
+ if (inSearchTerm != null && inSearchTerm.length() > 0)
+ {
+ // Replace umlauts oe, ue, ae, OE, UE, AE, szlig
+ StringBuilder sb = new StringBuilder();
+ final int numChars = inSearchTerm.length();
+ for (int i=0; i0 && _heightField.getValue()>0);
- }
- };
- MouseAdapter mouseListener = new MouseAdapter() {
- public void mouseReleased(java.awt.event.MouseEvent arg0) {
- _okButton.setEnabled(_widthField.getValue()>0 && _heightField.getValue()>0);
- };
- };
- _widthField.addKeyListener(keyListener);
- _heightField.addKeyListener(keyListener);
- _widthField.addMouseListener(mouseListener);
- _heightField.addMouseListener(mouseListener);
-
- // button panel at bottom
- JPanel buttonPanel = new JPanel();
- buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
- _okButton = new JButton(I18nManager.getText("button.ok"));
- ActionListener okListener = new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- finish();
- }
- };
- _okButton.addActionListener(okListener);
- _okButton.setEnabled(false);
- 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;
- }
-
- /**
- * @param inKey text key
- * @return right-aligned label
- */
- private static final JLabel makeRightLabel(String inKey)
- {
- JLabel label = new JLabel(I18nManager.getText(inKey) + " : ");
- label.setHorizontalAlignment(SwingConstants.RIGHT);
- return label;
- }
-
-
- /**
- * Finish the dialog when OK pressed
- */
- private void finish()
- {
- if (_widthField.getValue() > 0 && _heightField.getValue() > 0)
- {
- // Set entered values in Config
- Config.setConfigInt(Config.KEY_KMZ_IMAGE_WIDTH, _widthField.getValue());
- Config.setConfigInt(Config.KEY_KMZ_IMAGE_HEIGHT, _heightField.getValue());
- _dialog.dispose();
- }
- }
-}
diff --git a/tim/prune/function/SetLanguage.java b/tim/prune/function/SetLanguage.java
index 4407808..3962787 100644
--- a/tim/prune/function/SetLanguage.java
+++ b/tim/prune/function/SetLanguage.java
@@ -45,11 +45,11 @@ public class SetLanguage extends GenericFunction
"espa\u00F1ol", "fran\u00E7ais", "italiano", "magyar", "nederlands", "polski",
"portugu\u00EAs", "\u0440\u0443\u0441\u0441\u043a\u0438\u0439 (russian)", "\u4e2d\u6587 (chinese)", "\u65E5\u672C\u8A9E (japanese)",
"\uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean)", "schwiizerd\u00FC\u00FCtsch", "t\u00FCrk\u00E7e",
- "rom\u00E2n\u0103", "afrikaans", "bahasa indonesia"
+ "afrikaans", "rom\u00E2n\u0103"
};
/** Associated language codes (must be in same order as names!) */
private static final String[] LANGUAGE_CODES = {"cz", "de", "en", "en_us", "es", "fr", "it", "hu",
- "nl", "pl", "pt", "ru", "zh", "ja", "ko", "de_ch", "tr", "ro", "af", "in"
+ "nl", "pl", "pt", "ru", "zh", "ja", "ko", "de_ch", "tr", "af", "ro"
};
diff --git a/tim/prune/function/edit/EditFieldsTableModel.java b/tim/prune/function/edit/EditFieldsTableModel.java
index f4ee561..4fb46d7 100644
--- a/tim/prune/function/edit/EditFieldsTableModel.java
+++ b/tim/prune/function/edit/EditFieldsTableModel.java
@@ -10,6 +10,7 @@ import tim.prune.I18nManager;
public class EditFieldsTableModel extends AbstractTableModel
{
private String[] _fieldNames = null;
+ private String[] _originalValues = null;
private String[] _fieldValues = null;
private boolean[] _valueChanged = null;
@@ -20,9 +21,10 @@ public class EditFieldsTableModel extends AbstractTableModel
*/
public EditFieldsTableModel(int inSize)
{
- _fieldNames = new String[inSize];
- _fieldValues = new String[inSize];
- _valueChanged = new boolean[inSize];
+ _fieldNames = new String[inSize];
+ _originalValues = new String[inSize];
+ _fieldValues = new String[inSize];
+ _valueChanged = new boolean[inSize];
}
@@ -35,6 +37,7 @@ public class EditFieldsTableModel extends AbstractTableModel
public void addFieldInfo(String inName, String inValue, int inIndex)
{
_fieldNames[inIndex] = inName;
+ _originalValues[inIndex] = inValue;
_fieldValues[inIndex] = inValue;
_valueChanged[inIndex] = false;
}
@@ -45,7 +48,7 @@ public class EditFieldsTableModel extends AbstractTableModel
*/
public int getColumnCount()
{
- return 3;
+ return 2;
}
@@ -67,11 +70,7 @@ public class EditFieldsTableModel extends AbstractTableModel
{
return _fieldNames[inRowIndex];
}
- else if (inColumnIndex == 1)
- {
- return _fieldValues[inRowIndex];
- }
- return Boolean.valueOf(_valueChanged[inRowIndex]);
+ return _fieldValues[inRowIndex];
}
@@ -111,8 +110,7 @@ public class EditFieldsTableModel extends AbstractTableModel
public String getColumnName(int inColNum)
{
if (inColNum == 0) return I18nManager.getText("dialog.pointedit.table.field");
- else if (inColNum == 1) return I18nManager.getText("dialog.pointedit.table.value");
- return I18nManager.getText("dialog.pointedit.table.changed");
+ return I18nManager.getText("dialog.pointedit.table.value");
}
@@ -120,28 +118,36 @@ public class EditFieldsTableModel extends AbstractTableModel
* Update the value of the given row
* @param inRowNum number of row, starting at 0
* @param inValue new value
- * @return true if data updated
*/
- public boolean updateValue(int inRowNum, String inValue)
+ public void updateValue(int inRowNum, String inValue)
{
+ String origValue = _originalValues[inRowNum];
String currValue = _fieldValues[inRowNum];
- // ignore empty-to-empty changes
- if ((currValue == null || currValue.equals("")) && (inValue == null || inValue.equals("")))
+ // Update model if changed from original value
+ _valueChanged[inRowNum] = areStringsDifferent(origValue, inValue);
+ // Update model if changed from current value
+ if (areStringsDifferent(currValue, inValue))
{
- return false;
- }
- // ignore changes when strings equal
- if (currValue == null || inValue == null || !currValue.equals(inValue))
- {
- // really changed
_fieldValues[inRowNum] = inValue;
- _valueChanged[inRowNum] = true;
fireTableRowsUpdated(inRowNum, inRowNum);
- return true;
}
- return false;
}
+ /**
+ * Compare two strings to see if they're equal or not (nulls treated the same as empty strings)
+ * @param inString1 first string
+ * @param inString2 second string
+ * @return true if the strings are different
+ */
+ private static boolean areStringsDifferent(String inString1, String inString2)
+ {
+ // if both empty then same
+ if ((inString1 == null || inString1.equals("")) && (inString2 == null || inString2.equals("")))
+ {
+ return false;
+ }
+ return (inString1 == null || inString2 == null || !inString1.equals(inString2));
+ }
/**
* Get the value at the given index
diff --git a/tim/prune/function/edit/PointEditor.java b/tim/prune/function/edit/PointEditor.java
index 2321ece..40cf12b 100644
--- a/tim/prune/function/edit/PointEditor.java
+++ b/tim/prune/function/edit/PointEditor.java
@@ -1,31 +1,39 @@
package tim.prune.function.edit;
import java.awt.BorderLayout;
+import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
+import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
-import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellRenderer;
import tim.prune.App;
import tim.prune.I18nManager;
+import tim.prune.config.Config;
import tim.prune.data.DataPoint;
import tim.prune.data.Field;
import tim.prune.data.FieldList;
import tim.prune.data.Track;
+import tim.prune.data.Unit;
/**
* Class to manage the display and editing of point data
@@ -36,11 +44,14 @@ public class PointEditor
private JFrame _parentFrame = null;
private JDialog _dialog = null;
private JTable _table = null;
+ private JLabel _fieldnameLabel = null;
+ private JTextField _valueField = null;
+ private JTextArea _valueArea = null;
+ private JScrollPane _valueAreaPane = null;
private Track _track = null;
private DataPoint _point = null;
private EditFieldsTableModel _model = null;
- private JButton _editButton = null;
- private JButton _okButton = null;
+ private int _prevRowIndex = -1;
/**
@@ -76,9 +87,16 @@ public class PointEditor
Field field = fieldList.getField(i);
_model.addFieldInfo(field.getName(), _point.getFieldValue(field), i);
}
- // Create Gui and show it
+ // Create Gui
_dialog.getContentPane().add(makeDialogComponents());
_dialog.pack();
+ // Init right-hand side
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ _valueField.setVisible(false);
+ _valueAreaPane.setVisible(false);
+ }
+ });
_dialog.setVisible(true);
}
@@ -90,44 +108,71 @@ public class PointEditor
private Component makeDialogComponents()
{
JPanel panel = new JPanel();
- panel.setLayout(new BorderLayout(1, 10));
+ panel.setLayout(new BorderLayout(20, 10));
// Create GUI layout for point editor
- _table = new JTable(_model);
+ _table = new JTable(_model)
+ {
+ // Paint the changed fields orange
+ public Component prepareRenderer(TableCellRenderer renderer, int row, int column)
+ {
+ Component comp = super.prepareRenderer(renderer, row, column);
+ boolean changed = ((EditFieldsTableModel) getModel()).getChanged(row);
+ comp.setBackground(changed ? Color.orange : getBackground());
+ return comp;
+ }
+ };
_table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ _table.getSelectionModel().clearSelection();
_table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e)
{
- // enable edit button when row selected
- _editButton.setEnabled(true);
+ fieldSelected();
}
});
- _table.setPreferredScrollableViewportSize(new Dimension(_table.getWidth(), _table.getRowHeight() * 6));
- panel.add(new JScrollPane(_table), BorderLayout.CENTER);
+ _table.setPreferredScrollableViewportSize(new Dimension(_table.getWidth() * 2, _table.getRowHeight() * 6));
+ JScrollPane tablePane = new JScrollPane(_table);
+ tablePane.setPreferredSize(new Dimension(150, 100));
+
// Label at top
- JLabel topLabel = new JLabel(I18nManager.getText("dialog.pointedit.text"));
+ JLabel topLabel = new JLabel(I18nManager.getText("dialog.pointedit.intro"));
topLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 3, 6));
panel.add(topLabel, BorderLayout.NORTH);
- _editButton = new JButton(I18nManager.getText("button.edit"));
- _editButton.addActionListener(new ActionListener() {
+
+ // listener for ok event
+ ActionListener okListener = new ActionListener() {
public void actionPerformed(ActionEvent e)
{
- // Update field value and enable ok button
- String currValue = _model.getValue(_table.getSelectedRow());
- Object newValue = JOptionPane.showInputDialog(_dialog,
- I18nManager.getText("dialog.pointedit.changevalue.text"),
- I18nManager.getText("dialog.pointedit.changevalue.title"),
- JOptionPane.QUESTION_MESSAGE, null, null, currValue);
- if (newValue != null
- && _model.updateValue(_table.getSelectedRow(), newValue.toString()))
- {
- _okButton.setEnabled(true);
- }
+ // update App with edit
+ confirmEdit();
+ _dialog.dispose();
}
- });
- _editButton.setEnabled(false);
+ };
+
JPanel rightPanel = new JPanel();
- rightPanel.add(_editButton);
- panel.add(rightPanel, BorderLayout.EAST);
+ rightPanel.setLayout(new BorderLayout());
+ JPanel rightiPanel = new JPanel();
+ rightiPanel.setLayout(new BoxLayout(rightiPanel, BoxLayout.Y_AXIS));
+ // Add GUI elements to rhs
+ _fieldnameLabel = new JLabel(I18nManager.getText("dialog.pointedit.nofield"));
+ rightiPanel.add(_fieldnameLabel);
+ _valueField = new JTextField(11);
+ // Add listener for enter button
+ _valueField.addActionListener(okListener);
+ rightiPanel.add(_valueField);
+ rightPanel.add(rightiPanel, BorderLayout.NORTH);
+ _valueArea = new JTextArea(5, 15);
+ _valueArea.setLineWrap(true);
+ _valueArea.setWrapStyleWord(true);
+ _valueAreaPane = new JScrollPane(_valueArea);
+ rightPanel.add(_valueAreaPane, BorderLayout.CENTER);
+
+ // Put the table and the right-hand panel together in a grid
+ JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new GridLayout(0, 2, 10, 10));
+ mainPanel.add(tablePane);
+ mainPanel.add(rightPanel);
+ panel.add(mainPanel, BorderLayout.CENTER);
+
// Bottom panel for OK, cancel buttons
JPanel lowerPanel = new JPanel();
lowerPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
@@ -139,27 +184,109 @@ public class PointEditor
}
});
lowerPanel.add(cancelButton);
- _okButton = new JButton(I18nManager.getText("button.ok"));
- _okButton.setEnabled(false);
- _okButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- // update App with edit
- confirmEdit();
- _dialog.dispose();
- }
- });
- lowerPanel.add(_okButton);
+ JButton okButton = new JButton(I18nManager.getText("button.ok"));
+ okButton.addActionListener(okListener);
+ lowerPanel.add(okButton);
panel.add(lowerPanel, BorderLayout.SOUTH);
return panel;
}
+ /**
+ * When table selection changes, need to update model and go to the selected field
+ */
+ private void fieldSelected()
+ {
+ int rowNum = _table.getSelectedRow();
+ if (rowNum == _prevRowIndex) {return;} // selection hasn't changed
+ // Check the current values
+ if (_prevRowIndex >= 0)
+ {
+ Field prevField = _track.getFieldList().getField(_prevRowIndex);
+ boolean littleField = prevField.isBuiltIn() && prevField != Field.DESCRIPTION;
+ String newValue = littleField ? _valueField.getText() : _valueArea.getText();
+ // Update the model from the current GUI values
+ _model.updateValue(_prevRowIndex, newValue);
+ }
+
+ if (rowNum < 0)
+ {
+ _fieldnameLabel.setText("");
+ }
+ else
+ {
+ String currValue = _model.getValue(rowNum);
+ Field field = _track.getFieldList().getField(rowNum);
+ _fieldnameLabel.setText(makeFieldLabel(field, _point));
+ _fieldnameLabel.setVisible(true);
+ boolean littleField = field.isBuiltIn() && field != Field.DESCRIPTION;
+ if (littleField) {
+ _valueField.setText(currValue);
+ }
+ else {
+ _valueArea.setText(currValue);
+ }
+ _valueField.setVisible(littleField);
+ _valueAreaPane.setVisible(!littleField);
+ if (littleField) {
+ _valueField.requestFocus();
+ }
+ else {
+ _valueArea.requestFocus();
+ }
+ }
+ _prevRowIndex = rowNum;
+ }
+
+ /**
+ * @param inField field
+ * @param inPoint current point
+ * @return label string for above the entry field / area
+ */
+ private static String makeFieldLabel(Field inField, DataPoint inPoint)
+ {
+ String label = I18nManager.getText("dialog.pointedit.table.field") + ": " + inField.getName();
+ // Add units if the field is altitude / speed / vspeed
+ if (inField == Field.ALTITUDE)
+ {
+ label += makeUnitsLabel(inPoint.hasAltitude() ? inPoint.getAltitude().getUnit() : Config.getUnitSet().getAltitudeUnit());
+ }
+ else if (inField == Field.SPEED)
+ {
+ label += makeUnitsLabel(inPoint.hasHSpeed() ? inPoint.getHSpeed().getUnit() : Config.getUnitSet().getSpeedUnit());
+ }
+ else if (inField == Field.VERTICAL_SPEED)
+ {
+ label += makeUnitsLabel(inPoint.hasVSpeed() ? inPoint.getVSpeed().getUnit() : Config.getUnitSet().getVerticalSpeedUnit());
+ }
+ return label;
+ }
+
+ /**
+ * @param inUnit units for altitude / speed
+ * @return addition to the field label to describe the units
+ */
+ private static String makeUnitsLabel(Unit inUnit)
+ {
+ if (inUnit == null) return "";
+ return " (" + I18nManager.getText(inUnit.getShortnameKey()) + ")";
+ }
+
/**
* Confirm the edit and inform the app
*/
private void confirmEdit()
{
+ // Apply the edits to the current field
+ int rowNum = _table.getSelectedRow();
+ if (rowNum >= 0)
+ {
+ Field currField = _track.getFieldList().getField(rowNum);
+ boolean littleField = currField.isBuiltIn() && currField != Field.DESCRIPTION;
+ String newValue = littleField ? _valueField.getText() : _valueArea.getText();
+ _model.updateValue(_prevRowIndex, newValue);
+ }
+
// Package the modified fields into an object
FieldList fieldList = _track.getFieldList();
int numFields = fieldList.getNumFields();
diff --git a/tim/prune/function/edit/PointNameEditor.java b/tim/prune/function/edit/PointNameEditor.java
index bfa46c4..53e1f83 100644
--- a/tim/prune/function/edit/PointNameEditor.java
+++ b/tim/prune/function/edit/PointNameEditor.java
@@ -8,6 +8,7 @@ import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
+import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
@@ -77,7 +78,8 @@ public class PointNameEditor extends GenericFunction
panel.setLayout(new BorderLayout());
// Create GUI layout for point name editor
JPanel centrePanel = new JPanel();
- centrePanel.add(new JLabel(I18nManager.getText("dialog.pointnameedit.name") + ":"));
+ centrePanel.setLayout(new BorderLayout(8, 8));
+ centrePanel.add(new JLabel(I18nManager.getText("dialog.pointnameedit.name") + ": "), BorderLayout.WEST);
// Make listener to react to ok being pressed
ActionListener okActionListener = new ActionListener() {
public void actionPerformed(ActionEvent e)
@@ -101,8 +103,13 @@ public class PointNameEditor extends GenericFunction
}
});
_nameField.addActionListener(okActionListener);
- centrePanel.add(_nameField);
- panel.add(centrePanel);
+ centrePanel.add(_nameField, BorderLayout.CENTER);
+ // holder panel to stop the text box from being stretched
+ JPanel holderPanel = new JPanel();
+ holderPanel.setLayout(new BorderLayout());
+ holderPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+ holderPanel.add(centrePanel, BorderLayout.NORTH);
+ panel.add(holderPanel, BorderLayout.CENTER);
JPanel rightPanel = new JPanel();
rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
JButton upperButton = new JButton(I18nManager.getText("dialog.pointnameedit.uppercase"));
diff --git a/tim/prune/function/estimate/EstimateTime.java b/tim/prune/function/estimate/EstimateTime.java
new file mode 100644
index 0000000..7a502e4
--- /dev/null
+++ b/tim/prune/function/estimate/EstimateTime.java
@@ -0,0 +1,363 @@
+package tim.prune.function.estimate;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.RangeStats;
+import tim.prune.data.Selection;
+import tim.prune.data.Unit;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.DisplayUtils;
+import tim.prune.gui.GuiGridLayout;
+
+/**
+ * Class to calculate and show the results of estimating (hike) time for the current range
+ */
+public class EstimateTime extends GenericFunction
+{
+ /** Dialog */
+ private JDialog _dialog = null;
+ /** Labels for distances */
+ private JLabel _distanceLabel = null;
+ /** Labels for durations */
+ private JLabel _estimatedDurationLabel = null, _actualDurationLabel = null;
+ /** Labels for climbs */
+ private JLabel _gentleClimbLabel = null, _steepClimbLabel = null;
+ /** Labels for descents */
+ private JLabel _gentleDescentLabel = null, _steepDescentLabel = null;
+ /** Labels and text fields for parameters */
+ private JLabel _flatSpeedLabel = null;
+ private DecimalNumberField _flatSpeedField = null;
+ private JLabel _climbParamLabel = null;
+ private DecimalNumberField _gentleClimbField = null, _steepClimbField = null;
+ private JLabel _descentParamLabel = null;
+ private DecimalNumberField _gentleDescentField = null, _steepDescentField = null;
+ /** Range stats */
+ private RangeStats _stats = null;
+
+
+ /**
+ * Constructor
+ * @param inApp App object
+ */
+ public EstimateTime(App inApp)
+ {
+ super(inApp);
+ }
+
+ /** Get the name key */
+ public String getNameKey() {
+ return "function.estimatetime";
+ }
+
+ /**
+ * Begin the function
+ */
+ public void begin()
+ {
+ // Get the stats on the selection before launching the dialog
+ Selection selection = _app.getTrackInfo().getSelection();
+ _stats = new RangeStats(_app.getTrackInfo().getTrack(), selection.getStart(), selection.getEnd());
+
+ if (_stats.getMovingDistance() < 0.01)
+ {
+ _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.nodistance");
+ return;
+ }
+ if (_dialog == null)
+ {
+ // TODO: Check whether params are at default, show tip message if unaltered?
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ }
+ updateDetails();
+ _dialog.setVisible(true);
+ }
+
+ /**
+ * Create dialog components
+ * @return Panel containing all gui elements in dialog
+ */
+ private Component makeDialogComponents()
+ {
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BorderLayout(5, 5));
+
+ // main panel with a box layout
+ JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+ // Label at top
+ JLabel introLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.intro") + ":");
+ introLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ introLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(introLabel);
+ mainPanel.add(Box.createVerticalStrut(4));
+
+ // Details panel in a grid
+ JPanel detailsPanel = new JPanel();
+ detailsPanel.setLayout(new GridLayout(0, 4, 6, 2));
+ detailsPanel.setBorder(BorderFactory.createTitledBorder(
+ I18nManager.getText("dialog.estimatetime.details")));
+
+ // Distance
+ JLabel distLabel = new JLabel(I18nManager.getText("fieldname.distance") + ": ");
+ distLabel.setHorizontalAlignment(JLabel.RIGHT);
+ detailsPanel.add(distLabel);
+ _distanceLabel = new JLabel("5 km");
+ detailsPanel.add(_distanceLabel);
+ detailsPanel.add(new JLabel("")); detailsPanel.add(new JLabel("")); // two blank cells
+
+ detailsPanel.add(new JLabel(""));
+ detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
+ detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
+ detailsPanel.add(new JLabel("")); // blank cells
+
+ // Climb
+ JLabel climbLabel = new JLabel(I18nManager.getText("dialog.estimatetime.climb") + ": ");
+ climbLabel.setHorizontalAlignment(JLabel.RIGHT);
+ detailsPanel.add(climbLabel);
+ _gentleClimbLabel = new JLabel("1500 m");
+ detailsPanel.add(_gentleClimbLabel);
+ _steepClimbLabel = new JLabel("1500 m");
+ detailsPanel.add(_steepClimbLabel);
+ detailsPanel.add(new JLabel(""));
+
+ // Descent
+ JLabel descentLabel = new JLabel(I18nManager.getText("dialog.estimatetime.descent") + ": ");
+ descentLabel.setHorizontalAlignment(JLabel.RIGHT);
+ detailsPanel.add(descentLabel);
+ _gentleDescentLabel = new JLabel("1500 m");
+ detailsPanel.add(_gentleDescentLabel);
+ _steepDescentLabel = new JLabel("1500 m");
+ detailsPanel.add(_steepDescentLabel);
+ detailsPanel.add(new JLabel(""));
+
+ detailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(detailsPanel);
+ mainPanel.add(Box.createVerticalStrut(4));
+
+ // Parameters panel in a flexible grid
+ JPanel paramsPanel = new JPanel();
+ GuiGridLayout paramsGrid = new GuiGridLayout(paramsPanel, new double[] {1.5, 0.2, 1.0, 0.2, 0.5},
+ new boolean[] {true, false, false, false, false});
+ paramsPanel.setBorder(BorderFactory.createTitledBorder(
+ I18nManager.getText("dialog.estimatetime.parameters")));
+ KeyAdapter paramChangeListener = new KeyAdapter() {
+ public void keyTyped(KeyEvent inE) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ calculateEstimatedTime();
+ }
+ });
+ }
+ public void keyPressed(KeyEvent inE) {
+ if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+ }
+ };
+
+ // Flat speed
+ _flatSpeedLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
+ _flatSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
+ paramsGrid.add(_flatSpeedLabel);
+ _flatSpeedField = new DecimalNumberField();
+ _flatSpeedField.addKeyListener(paramChangeListener);
+ paramsGrid.add(_flatSpeedField);
+ JLabel minsLabel = new JLabel(I18nManager.getText("units.minutes"));
+ paramsGrid.add(minsLabel);
+ paramsGrid.nextRow();
+ // Headers for gentle and steep
+ paramsGrid.add(new JLabel(""));
+ paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
+ paramsGrid.add(new JLabel("")); // blank cell
+ paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
+ paramsGrid.nextRow();
+ // Gentle climb
+ _climbParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
+ _climbParamLabel.setHorizontalAlignment(JLabel.RIGHT);
+ paramsGrid.add(_climbParamLabel);
+ _gentleClimbField = new DecimalNumberField();
+ _gentleClimbField.addKeyListener(paramChangeListener);
+ paramsGrid.add(_gentleClimbField);
+ paramsGrid.add(new JLabel(minsLabel.getText()));
+ // Steep climb
+ _steepClimbField = new DecimalNumberField();
+ _steepClimbField.addKeyListener(paramChangeListener);
+ paramsGrid.add(_steepClimbField);
+ paramsGrid.add(new JLabel(minsLabel.getText()));
+
+ // Gentle descent
+ _descentParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
+ _descentParamLabel.setHorizontalAlignment(JLabel.RIGHT);
+ paramsGrid.add(_descentParamLabel);
+ _gentleDescentField = new DecimalNumberField(true); // negative numbers allowed
+ _gentleDescentField.addKeyListener(paramChangeListener);
+ paramsGrid.add(_gentleDescentField);
+ paramsGrid.add(new JLabel(minsLabel.getText()));
+ // Steep climb
+ _steepDescentField = new DecimalNumberField(true); // negative numbers allowed
+ _steepDescentField.addKeyListener(paramChangeListener);
+ paramsGrid.add(_steepDescentField);
+ paramsGrid.add(new JLabel(minsLabel.getText()));
+
+ paramsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(paramsPanel);
+ mainPanel.add(Box.createVerticalStrut(12));
+
+ // Results panel
+ JPanel resultsPanel = new JPanel();
+ resultsPanel.setBorder(BorderFactory.createTitledBorder(
+ I18nManager.getText("dialog.estimatetime.results")));
+ resultsPanel.setLayout(new GridLayout(0, 2, 3, 3));
+ // estimated time
+ _estimatedDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "); // filled in later
+ Font origFont = _estimatedDurationLabel.getFont();
+ _estimatedDurationLabel.setFont(origFont.deriveFont(Font.BOLD, origFont.getSize2D() + 2.0f));
+
+ resultsPanel.add(_estimatedDurationLabel);
+ // actual time (if available)
+ _actualDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "); // filled in later
+ resultsPanel.add(_actualDurationLabel);
+
+ resultsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(resultsPanel);
+ mainPanel.add(Box.createVerticalStrut(4));
+
+ dialogPanel.add(mainPanel, BorderLayout.NORTH);
+ // button panel at bottom
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ JButton closeButton = new JButton(I18nManager.getText("button.close"));
+ closeButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ finishDialog();
+ }
+ });
+ closeButton.addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent inE) {
+ if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+ }
+ });
+ buttonPanel.add(closeButton);
+ dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+ return dialogPanel;
+ }
+
+
+ /**
+ * Recalculate the values and update the labels
+ */
+ private void updateDetails()
+ {
+ // Warn if the current track hasn't got any height information
+ if (!_stats.getMovingAltitudeRange().hasRange()) {
+ _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.noaltitudes");
+ }
+
+ // Distance in current units
+ final Unit distUnit = Config.getUnitSet().getDistanceUnit();
+ final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
+ final double movingDist = _stats.getMovingDistance();
+ _distanceLabel.setText(DisplayUtils.roundedNumber(movingDist) + " " + distUnitsStr);
+
+ // Climb and descent values
+ final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
+ final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
+ _gentleClimbLabel.setText(_stats.getGentleAltitudeRange().getClimb(altUnit) + altUnitsStr);
+ _steepClimbLabel.setText(_stats.getSteepAltitudeRange().getClimb(altUnit) + altUnitsStr);
+ _gentleDescentLabel.setText(_stats.getGentleAltitudeRange().getDescent(altUnit) + altUnitsStr);
+ _steepDescentLabel.setText(_stats.getSteepAltitudeRange().getDescent(altUnit) + altUnitsStr);
+
+ // Try to get parameters from config
+ EstimationParameters estParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
+
+ String[] paramValues = estParams.getStrings();
+ if (paramValues == null || paramValues.length != 5) {return;} // TODO: What to do in case of error?
+ // Flat time is either for 5 km, 3 miles or 3 nm
+ _flatSpeedLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
+ " " + EstimationParameters.getStandardDistance() + ": ");
+ _flatSpeedField.setText(paramValues[0]);
+
+ final String heightString = " " + EstimationParameters.getStandardClimb() + ": ";
+ _climbParamLabel.setText(I18nManager.getText("dialog.estimatetime.climb") + heightString);
+ _gentleClimbField.setText(paramValues[1]);
+ _steepClimbField.setText(paramValues[2]);
+ _descentParamLabel.setText(I18nManager.getText("dialog.estimatetime.descent") + heightString);
+ _gentleDescentField.setText(paramValues[3]);
+ _steepDescentField.setText(paramValues[4]);
+
+ // Use the entered parameters to estimate the time
+ calculateEstimatedTime();
+
+ // Get the actual time if available, for comparison
+ if (_stats.getMovingDurationInSeconds() > 0)
+ {
+ _actualDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "
+ + DisplayUtils.buildDurationString(_stats.getMovingDurationInSeconds()));
+ }
+ else {
+ _actualDurationLabel.setText("");
+ }
+ }
+
+
+ /**
+ * Use the current parameter and the range stats to calculate the estimated time
+ * and populate the answer in the dialog
+ */
+ private void calculateEstimatedTime()
+ {
+ // Populate an EstimationParameters object from the four strings
+ EstimationParameters params = new EstimationParameters();
+ params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
+ _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
+ if (!params.isValid()) {
+ _estimatedDurationLabel.setText("- - -");
+ }
+ else
+ {
+ final long numSeconds = (long) (params.applyToStats(_stats) * 60.0);
+ _estimatedDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "
+ + DisplayUtils.buildDurationString(numSeconds));
+ }
+ }
+
+
+ /**
+ * Finish with the dialog, by pressing the "Close" button
+ */
+ private void finishDialog()
+ {
+ // Make estimation parameters from entered strings, if valid save to config
+ EstimationParameters params = new EstimationParameters();
+ params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
+ _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
+ if (params.isValid()) {
+ Config.setConfigString(Config.KEY_ESTIMATION_PARAMS, params.toConfigString());
+ }
+ _dialog.dispose();
+ }
+}
diff --git a/tim/prune/function/estimate/EstimationParameters.java b/tim/prune/function/estimate/EstimationParameters.java
new file mode 100644
index 0000000..e78994b
--- /dev/null
+++ b/tim/prune/function/estimate/EstimationParameters.java
@@ -0,0 +1,278 @@
+package tim.prune.function.estimate;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.RangeStats;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+
+/**
+ * Class to hold, parse and convert the parameters for time estimation.
+ * These are five (metric) values which can be loaded and saved from config,
+ * and are used by the EstimateTime function
+ */
+public class EstimationParameters
+{
+ /** Minutes required for flat travel, fixed metric distance */
+ private double _flatMins = 0.0;
+ /** Minutes required for climbing, fixed metric distance */
+ private double _gentleClimbMins = 0.0, _steepClimbMins;
+ /** Minutes required for descending, fixed metric distance */
+ private double _gentleDescentMins = 0.0, _steepDescentMins;
+ /** True if parsing from a string failed */
+ private boolean _parseFailed = false;
+
+ /** Kilometres unit for comparison */
+ private static final Unit KILOMETRES = UnitSetLibrary.UNITS_KILOMETRES;
+
+
+ /**
+ * Bare constructor using default values
+ */
+ public EstimationParameters()
+ {
+ resetToDefaults();
+ }
+
+ /**
+ * Constructor from config string
+ * @param inConfigString single, semicolon-separated string from config
+ */
+ public EstimationParameters(String inConfigString)
+ {
+ populateWithString(inConfigString);
+ if (_parseFailed) {
+ resetToDefaults();
+ }
+ }
+
+ /**
+ * Reset all the values to their hardcoded defaults
+ */
+ public void resetToDefaults()
+ {
+ _flatMins = 60.0;
+ _gentleClimbMins = 12.0; _steepClimbMins = 18.0;
+ _gentleDescentMins = 0.0; _steepDescentMins = 12.0;
+ _parseFailed = false;
+ }
+
+ /**
+ * Populate the values from the config, which means all values are metric
+ * @param inString semicolon-separated string of five parameters
+ */
+ private void populateWithString(String inString)
+ {
+ if (inString != null && !inString.trim().equals(""))
+ {
+ String[] params = inString.trim().split(";");
+ _parseFailed = (params == null || params.length != 5);
+ if (!_parseFailed)
+ {
+ for (String p : params)
+ {
+ if (!isParamStringValid(p)) {
+ _parseFailed = true;
+ }
+ }
+ }
+ if (!_parseFailed)
+ {
+ try
+ {
+ // Use fixed UK locale to parse these, because of fixed "." formatting
+ NumberFormat twoDpFormatter = NumberFormat.getNumberInstance(Locale.UK);
+ _flatMins = twoDpFormatter.parse(params[0]).doubleValue();
+ _gentleClimbMins = twoDpFormatter.parse(params[1]).doubleValue();
+ _steepClimbMins = twoDpFormatter.parse(params[2]).doubleValue();
+ _gentleDescentMins = twoDpFormatter.parse(params[3]).doubleValue();
+ _steepDescentMins = twoDpFormatter.parse(params[4]).doubleValue();
+ }
+ catch (Exception e) {
+ _parseFailed = true;
+ }
+ }
+ }
+ else _parseFailed = true;
+ }
+
+ /**
+ * Populate the values using five user-entered strings (now Units-specific!)
+ * @param inFlat minutes for flat
+ * @param inGClimb minutes for gentle climb
+ * @param inSClimb minutes for steep climb
+ * @param inGDescent minutes for gentle descent
+ * @param inSDescent minutes for steep descent
+ */
+ public void populateWithStrings(String inFlat, String inGClimb, String inSClimb, String inGDescent, String inSDescent)
+ {
+ if (isParamStringValid(inFlat) && isParamStringValid(inGClimb) && isParamStringValid(inSClimb)
+ && isParamStringValid(inGDescent) && isParamStringValid(inSDescent))
+ {
+ Unit distUnit = Config.getUnitSet().getDistanceUnit();
+ Unit altUnit = Config.getUnitSet().getAltitudeUnit();
+ final double distFactor = (distUnit == KILOMETRES ? 1.0 : (5000/3.0 * distUnit.getMultFactorFromStd()));
+ final double altFactor = (altUnit.isStandard() ? 1.0 : (1.0/3.0 * altUnit.getMultFactorFromStd()));
+ NumberFormat localFormatter = NumberFormat.getNumberInstance();
+ try
+ {
+ _flatMins = localFormatter.parse(inFlat).doubleValue() * distFactor;
+ _gentleClimbMins = localFormatter.parse(inGClimb).doubleValue() * altFactor;
+ _steepClimbMins = localFormatter.parse(inSClimb).doubleValue() * altFactor;
+ _gentleDescentMins = localFormatter.parse(inGDescent).doubleValue() * altFactor;
+ _steepDescentMins = localFormatter.parse(inSDescent).doubleValue() * altFactor;
+ }
+ catch (Exception e) {_parseFailed = true;}
+ }
+ else _parseFailed = true;
+ }
+
+ /**
+ * Populate with double metric values, for example the results of a Learning process
+ * @param inFlat time for 5km on the flat
+ * @param inGClimb time for 100m gentle climb
+ * @param inSClimb time for 100m steep climb
+ * @param inGDescent time for 100m gentle descent
+ * @param inSDescent time for 100m steep descent
+ */
+ public void populateWithMetrics(double inFlat, double inGClimb, double inSClimb, double inGDescent, double inSDescent)
+ {
+ _flatMins = inFlat;
+ _gentleClimbMins = inGClimb;
+ _steepClimbMins = inSClimb;
+ _gentleDescentMins = inGDescent;
+ _steepDescentMins = inSDescent;
+ }
+
+ /**
+ * @param inString parameter string to check
+ * @return true if it looks valid (no letters, at least one digit)
+ */
+ private static boolean isParamStringValid(String inString)
+ {
+ if (inString == null) {return false;}
+ boolean hasDigit = false, currPunc = false, prevPunc = false;
+ for (int i=0; i 0.0 && _gentleClimbMins >= 0.0 && _steepClimbMins >= 0.0;
+ }
+
+ /**
+ * @return five strings for putting in text fields for editing / display
+ */
+ public String[] getStrings()
+ {
+ Unit distUnit = Config.getUnitSet().getDistanceUnit();
+ Unit altUnit = Config.getUnitSet().getAltitudeUnit();
+ double distFactor = (distUnit == KILOMETRES ? 1.0 : (5000/3.0 * distUnit.getMultFactorFromStd()));
+ double altFactor = (altUnit.isStandard() ? 1.0 : (1.0/3.0 * altUnit.getMultFactorFromStd()));
+ // Use locale-specific number formatting, eg commas for german
+ NumberFormat numFormatter = NumberFormat.getNumberInstance();
+ if (numFormatter instanceof DecimalFormat) {
+ ((DecimalFormat) numFormatter).applyPattern("0.00");
+ }
+ // Conversion between units
+ return new String[] {
+ numFormatter.format(_flatMins / distFactor),
+ numFormatter.format(_gentleClimbMins / altFactor), numFormatter.format(_steepClimbMins / altFactor),
+ numFormatter.format(_gentleDescentMins / altFactor), numFormatter.format(_steepDescentMins / altFactor)
+ };
+ }
+
+ /**
+ * @return unit-specific string describing the distance for the flat time (5km/3mi/3NM)
+ */
+ public static String getStandardDistance()
+ {
+ Unit distUnit = Config.getUnitSet().getDistanceUnit();
+ return (distUnit == KILOMETRES ? "5 " : "3 ") + I18nManager.getText(distUnit.getShortnameKey());
+ }
+
+ /**
+ * @return unit-specific string describing the height difference for the climbs/descents (100m/300ft)
+ */
+ public static String getStandardClimb()
+ {
+ Unit altUnit = Config.getUnitSet().getAltitudeUnit();
+ return (altUnit.isStandard() ? "100 " : "300 ") + I18nManager.getText(altUnit.getShortnameKey());
+ }
+
+ /**
+ * @return contents of parameters as a semicolon-separated (metric) string for the config
+ */
+ public String toConfigString()
+ {
+ return "" + twoDp(_flatMins) + ";" + twoDp(_gentleClimbMins) + ";" + twoDp(_steepClimbMins) + ";"
+ + twoDp(_gentleDescentMins) + ";" + twoDp(_steepDescentMins);
+ }
+
+ /**
+ * @param inNum number to output
+ * @return formatted string to two decimal places, with decimal point
+ */
+ private static String twoDp(double inNum)
+ {
+ if (inNum < 0.0) return "-" + twoDp(-inNum);
+ int hundreds = (int) (inNum * 100 + 0.5);
+ return "" + (hundreds / 100) + "." + (hundreds % 100);
+ }
+
+ /**
+ * Apply the parameters to the given range stats
+ * @param inStats stats of current range
+ * @return estimated number of minutes required
+ */
+ public double applyToStats(RangeStats inStats)
+ {
+ if (inStats == null || !inStats.isValid()) return 0.0;
+ final Unit METRES = UnitSetLibrary.UNITS_METRES;
+ final double STANDARD_CLIMB = 100.0; // metres
+ final double STANDARD_DISTANCE = 5.0; // kilometres
+ return _flatMins * inStats.getMovingDistanceKilometres() / STANDARD_DISTANCE
+ + _gentleClimbMins * inStats.getGentleAltitudeRange().getClimb(METRES) / STANDARD_CLIMB
+ + _steepClimbMins * inStats.getSteepAltitudeRange().getClimb(METRES) / STANDARD_CLIMB
+ + _gentleDescentMins * inStats.getGentleAltitudeRange().getDescent(METRES) / STANDARD_CLIMB
+ + _steepDescentMins * inStats.getSteepAltitudeRange().getDescent(METRES) / STANDARD_CLIMB;
+ }
+
+ /**
+ * Combine two sets of parameters together
+ * @param inOther other set
+ * @param inFraction fractional weight
+ * @return combined set
+ */
+ public EstimationParameters combine(EstimationParameters inOther, double inFraction)
+ {
+ if (inFraction < 0.0 || inFraction > 1.0 || inOther == null) {
+ return null;
+ }
+ // inFraction is the weight of this one, weight of the other one is 1-inFraction
+ final double fraction2 = 1 - inFraction;
+ EstimationParameters combined = new EstimationParameters();
+ combined._flatMins = inFraction * _flatMins + fraction2 * inOther._flatMins;
+ combined._gentleClimbMins = inFraction * _gentleClimbMins + fraction2 * inOther._gentleClimbMins;
+ combined._gentleDescentMins = inFraction * _gentleDescentMins + fraction2 * inOther._gentleDescentMins;
+ combined._steepClimbMins = inFraction * _steepClimbMins + fraction2 * inOther._steepClimbMins;
+ combined._steepDescentMins = inFraction * _steepDescentMins + fraction2 * inOther._steepDescentMins;
+ return combined;
+ }
+}
diff --git a/tim/prune/function/estimate/LearnParameters.java b/tim/prune/function/estimate/LearnParameters.java
new file mode 100644
index 0000000..8f4ea68
--- /dev/null
+++ b/tim/prune/function/estimate/LearnParameters.java
@@ -0,0 +1,520 @@
+package tim.prune.function.estimate;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollBar;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Distance;
+import tim.prune.data.RangeStats;
+import tim.prune.data.Track;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.function.estimate.jama.Matrix;
+import tim.prune.gui.ProgressDialog;
+
+/**
+ * Function to learn the estimation parameters from the current track
+ */
+public class LearnParameters extends GenericFunction implements Runnable
+{
+ /** Progress dialog */
+ ProgressDialog _progress = null;
+ /** Results dialog */
+ JDialog _dialog = null;
+ /** Calculated parameters */
+ private ParametersPanel _calculatedParamPanel = null;
+ private EstimationParameters _calculatedParams = null;
+ /** Slider for weighted average */
+ private JScrollBar _weightSlider = null;
+ /** Label to describe position of slider */
+ private JLabel _sliderDescLabel = null;
+ /** Combined parameters */
+ private ParametersPanel _combinedParamPanel = null;
+ /** Combine button */
+ private JButton _combineButton = null;
+
+
+ /**
+ * Inner class used to hold the results of the matrix solving
+ */
+ static class MatrixResults
+ {
+ public EstimationParameters _parameters = null;
+ public double _averageErrorPc = 0.0; // percentage
+ }
+
+
+ /**
+ * Constructor
+ * @param inApp App object
+ */
+ public LearnParameters(App inApp)
+ {
+ super(inApp);
+ }
+
+ /** @return key for function name */
+ public String getNameKey() {
+ return "function.learnestimationparams";
+ }
+
+ /**
+ * Begin the function
+ */
+ public void begin()
+ {
+ // Show progress bar
+ if (_progress == null) {
+ _progress = new ProgressDialog(_parentFrame, getNameKey());
+ }
+ _progress.show();
+ // Start new thread for the calculations
+ new Thread(this).start();
+ }
+
+ /**
+ * Run method in separate thread
+ */
+ public void run()
+ {
+ _progress.setMaximum(100);
+ // Go through the track and collect the range stats for each sample
+ ArrayList statsList = new ArrayList(20);
+ Track track = _app.getTrackInfo().getTrack();
+ final int numPoints = track.getNumPoints();
+ final int sampleSize = numPoints / 30;
+ int prevStartIndex = -1;
+ for (int i=0; i<30; i++)
+ {
+ int startIndex = i * sampleSize;
+ RangeStats stats = getRangeStats(track, startIndex, startIndex + sampleSize, prevStartIndex);
+ if (stats != null && stats.getMovingDistanceKilometres() > 1.0
+ && !stats.getTimestampsIncomplete()
+ && stats.getTotalDurationInSeconds() > 100
+ && stats.getStartIndex() > prevStartIndex)
+ {
+ // System.out.println("Got stats for " + stats.getStartIndex() + " to " + stats.getEndIndex());
+ statsList.add(stats);
+ prevStartIndex = stats.getStartIndex();
+ }
+ _progress.setValue(i);
+ }
+
+ // Check if we've got enough samples
+ // System.out.println("Got a total of " + statsList.size() + " samples");
+ if (statsList.size() < 10)
+ {
+ _progress.dispose();
+ // Show error message, not enough samples
+ _app.showErrorMessage(getNameKey(), "error.learnestimationparams.failed");
+ return;
+ }
+ // Loop around, solving the matrices and removing the highest-error sample
+ MatrixResults results = reduceSamples(statsList);
+ if (results == null)
+ {
+ _progress.dispose();
+ _app.showErrorMessage(getNameKey(), "error.learnestimationparams.failed");
+ return;
+ }
+
+ _progress.dispose();
+
+ // Create the dialog if necessary
+ if (_dialog == null)
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ // Create Gui and show it
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ }
+
+ // Populate the values in the dialog
+ populateCalculatedValues(results);
+ updateCombinedLabels(calculateCombinedParameters());
+ _dialog.setVisible(true);
+ }
+
+
+ /**
+ * Make the dialog components
+ * @return the GUI components for the dialog
+ */
+ private Component makeDialogComponents()
+ {
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BorderLayout());
+
+ // main panel with a box layout
+ JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+ // Label at top
+ JLabel introLabel = new JLabel(I18nManager.getText("dialog.learnestimationparams.intro") + ":");
+ introLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ introLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(introLabel);
+
+ // Panel for the calculated results
+ _calculatedParamPanel = new ParametersPanel("dialog.estimatetime.results", true);
+ _calculatedParamPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(_calculatedParamPanel);
+ mainPanel.add(Box.createVerticalStrut(14));
+
+ mainPanel.add(new JLabel(I18nManager.getText("dialog.learnestimationparams.combine") + ":"));
+ mainPanel.add(Box.createVerticalStrut(4));
+ _weightSlider = new JScrollBar(JScrollBar.HORIZONTAL, 5, 1, 0, 11);
+ _weightSlider.addAdjustmentListener(new AdjustmentListener() {
+ public void adjustmentValueChanged(AdjustmentEvent inEvent)
+ {
+ if (!inEvent.getValueIsAdjusting()) {
+ updateCombinedLabels(calculateCombinedParameters());
+ }
+ }
+ });
+ mainPanel.add(_weightSlider);
+ _sliderDescLabel = new JLabel(" ");
+ _sliderDescLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(_sliderDescLabel);
+ mainPanel.add(Box.createVerticalStrut(12));
+
+ // Results panel
+ _combinedParamPanel = new ParametersPanel("dialog.learnestimationparams.combinedresults");
+ _combinedParamPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ mainPanel.add(_combinedParamPanel);
+
+ dialogPanel.add(mainPanel, BorderLayout.NORTH);
+
+ // button panel at bottom
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+
+ // Combine
+ _combineButton = new JButton(I18nManager.getText("button.combine"));
+ _combineButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ combineAndFinish();
+ }
+ });
+ buttonPanel.add(_combineButton);
+
+ // Cancel
+ JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ _dialog.dispose();
+ }
+ });
+ KeyAdapter escapeListener = new KeyAdapter() {
+ public void keyPressed(KeyEvent inE) {
+ if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+ }
+ };
+ _combineButton.addKeyListener(escapeListener);
+ cancelButton.addKeyListener(escapeListener);
+ buttonPanel.add(cancelButton);
+ dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+ return dialogPanel;
+ }
+
+ /**
+ * Construct a rangestats object for the selected range
+ * @param inTrack track object
+ * @param inStartIndex start index
+ * @param inEndIndex end index
+ * @param inPreviousStartIndex the previously used start index, or -1
+ * @return range stats object or null if required information missing from this bit of the track
+ */
+ private RangeStats getRangeStats(Track inTrack, int inStartIndex, int inEndIndex, int inPreviousStartIndex)
+ {
+ // Check parameters
+ if (inTrack == null || inStartIndex < 0 || inEndIndex <= inStartIndex || inStartIndex > inTrack.getNumPoints()) {
+ return null;
+ }
+ final int numPoints = inTrack.getNumPoints();
+ int start = inStartIndex;
+
+ // Search forward until a decent track point found for the start
+ DataPoint p = inTrack.getPoint(start);
+ while (start < numPoints && (p == null || p.isWaypoint() || !p.hasTimestamp() || !p.hasAltitude()))
+ {
+ start++;
+ p = inTrack.getPoint(start);
+ }
+ if (inPreviousStartIndex >= 0 && start <= (inPreviousStartIndex + 10) // overlapping too much with previous range
+ || (start >= (numPoints - 10))) // starting too late in the track
+ {
+ return null;
+ }
+
+ // Search forward (counting the radians) until a decent end point found
+ double movingRads = 0.0;
+ final double minimumRads = Distance.convertDistanceToRadians(1.0, UnitSetLibrary.UNITS_KILOMETRES);
+ DataPoint prevPoint = inTrack.getPoint(start);
+ int endIndex = start;
+ boolean shouldStop = false;
+ do
+ {
+ endIndex++;
+ p = inTrack.getPoint(endIndex);
+ if (p != null && !p.isWaypoint())
+ {
+ if (!p.hasAltitude() || !p.hasTimestamp()) {return null;} // abort if no time/altitude
+ if (prevPoint != null && !p.getSegmentStart()) {
+ movingRads += DataPoint.calculateRadiansBetween(prevPoint, p);
+ }
+ }
+ prevPoint = p;
+ if (endIndex >= numPoints) {
+ shouldStop = true; // reached the end of the track
+ }
+ else if (movingRads >= minimumRads && endIndex >= inEndIndex) {
+ shouldStop = true; // got at least a kilometre
+ }
+ }
+ while (!shouldStop);
+
+ // Check moving distance
+ if (movingRads >= minimumRads) {
+ return new RangeStats(inTrack, start, endIndex);
+ }
+ return null;
+ }
+
+ /**
+ * Build an A matrix for the given list of RangeStats objects
+ * @param inStatsList list of (non-null) RangeStats objects
+ * @return A matrix with n rows and 5 columns
+ */
+ private static Matrix buildAMatrix(ArrayList inStatsList)
+ {
+ final Unit METRES = UnitSetLibrary.UNITS_METRES;
+ Matrix result = new Matrix(inStatsList.size(), 5);
+ int row = 0;
+ for (RangeStats stats : inStatsList)
+ {
+ result.setValue(row, 0, stats.getMovingDistanceKilometres());
+ result.setValue(row, 1, stats.getGentleAltitudeRange().getClimb(METRES));
+ result.setValue(row, 2, stats.getSteepAltitudeRange().getClimb(METRES));
+ result.setValue(row, 3, stats.getGentleAltitudeRange().getDescent(METRES));
+ result.setValue(row, 4, stats.getSteepAltitudeRange().getDescent(METRES));
+ row++;
+ }
+ return result;
+ }
+
+ /**
+ * Build a B matrix containing the observations (moving times)
+ * @param inStatsList list of (non-null) RangeStats objects
+ * @return B matrix with single column of n rows
+ */
+ private static Matrix buildBMatrix(ArrayList inStatsList)
+ {
+ Matrix result = new Matrix(inStatsList.size(), 1);
+ int row = 0;
+ for (RangeStats stats : inStatsList)
+ {
+ result.setValue(row, 0, stats.getMovingDurationInSeconds() / 60.0); // convert seconds to minutes
+ row++;
+ }
+ return result;
+ }
+
+ /**
+ * Look for the maximum absolute value in the given column matrix
+ * @param inMatrix matrix with only one column
+ * @return row index of cell with greatest absolute value, or -1 if not valid
+ */
+ private static int getIndexOfMaxValue(Matrix inMatrix)
+ {
+ if (inMatrix == null || inMatrix.getNumColumns() > 1) {
+ return -1;
+ }
+ int index = 0;
+ double currValue = 0.0, maxValue = 0.0;
+ // Loop over the first column looking for the maximum absolute value
+ for (int i=0; i maxValue)
+ {
+ maxValue = currValue;
+ index = i;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * See if the given set of samples is sufficient for getting a descent solution (at least 3 nonzero values)
+ * @param inRangeSet list of RangeStats objects
+ * @param inRowToIgnore row index to ignore, or -1 to use them all
+ * @return true if the samples look ok
+ */
+ private static boolean isRangeSetSufficient(ArrayList inRangeSet, int inRowToIgnore)
+ {
+ int numGC = 0, numSC = 0, numGD = 0, numSD = 0; // number of samples with gentle/steep climb/descent values > 0
+ final Unit METRES = UnitSetLibrary.UNITS_METRES;
+ int i = 0;
+ for (RangeStats stats : inRangeSet)
+ {
+ if (i != inRowToIgnore)
+ {
+ if (stats.getGentleAltitudeRange().getClimb(METRES) > 0) {numGC++;}
+ if (stats.getSteepAltitudeRange().getClimb(METRES) > 0) {numSC++;}
+ if (stats.getGentleAltitudeRange().getDescent(METRES) > 0) {numGD++;}
+ if (stats.getSteepAltitudeRange().getDescent(METRES) > 0) {numSD++;}
+ }
+ i++;
+ }
+ return numGC > 3 && numSC > 3 && numGD > 3 && numSD > 3;
+ }
+
+ /**
+ * Reduce the number of samples in the given list by eliminating the ones with highest errors
+ * @param inStatsList list of stats
+ * @return results in an object
+ */
+ private MatrixResults reduceSamples(ArrayList inStatsList)
+ {
+ int statsIndexToRemove = -1;
+ Matrix answer = null;
+ boolean finished = false;
+ double averageErrorPc = 0.0;
+ while (!finished)
+ {
+ // Remove the marked stats object, if any
+ if (statsIndexToRemove >= 0) {
+ inStatsList.remove(statsIndexToRemove);
+ }
+
+ // Build up the matrices
+ Matrix A = buildAMatrix(inStatsList);
+ Matrix B = buildBMatrix(inStatsList);
+ // System.out.println("Times in minutes are:\n" + B.toString());
+
+ // Solve (if possible)
+ try
+ {
+ answer = A.solve(B);
+ // System.out.println("Solved matrix with " + A.getNumRows() + " rows:\n" + answer.toString());
+ // Work out the percentage error for each estimate
+ Matrix estimates = A.times(answer);
+ Matrix errors = estimates.minus(B).divideEach(B);
+ // System.out.println("Errors: " + errors.toString());
+ averageErrorPc = errors.getAverageAbsValue();
+ // find biggest percentage error, remove it from list
+ statsIndexToRemove = getIndexOfMaxValue(errors);
+ if (statsIndexToRemove < 0)
+ {
+ System.err.println("Something wrong - index is " + statsIndexToRemove);
+ throw new Exception();
+ }
+ // Check whether removing this element would make the range set insufficient
+ finished = inStatsList.size() <= 25 || !isRangeSetSufficient(inStatsList, statsIndexToRemove);
+ }
+ catch (Exception e)
+ {
+ // Couldn't solve at all
+ System.out.println("Failed to reduce: " + e.getClass().getName() + " - " + e.getMessage());
+ return null;
+ }
+ _progress.setValue(20 + 80 * (30 - inStatsList.size())/5); // Counting from 30 to 25
+ }
+ // Copy results to an EstimationParameters object
+ MatrixResults result = new MatrixResults();
+ result._parameters = new EstimationParameters();
+ result._parameters.populateWithMetrics(answer.get(0, 0) * 5, // convert from 1km to 5km
+ answer.get(1, 0) * 100.0, answer.get(2, 0) * 100.0, // convert from m to 100m
+ answer.get(3, 0) * 100.0, answer.get(4, 0) * 100.0);
+ result._averageErrorPc = averageErrorPc;
+ return result;
+ }
+
+
+ /**
+ * Populate the dialog's labels with the calculated values
+ * @param inResults results of the calculations
+ */
+ private void populateCalculatedValues(MatrixResults inResults)
+ {
+ if (inResults == null || inResults._parameters == null)
+ {
+ _calculatedParams = null;
+ _calculatedParamPanel.updateParameters(null, 0.0);
+ }
+ else
+ {
+ _calculatedParams = inResults._parameters;
+ _calculatedParamPanel.updateParameters(_calculatedParams, inResults._averageErrorPc);
+ }
+ }
+
+ /**
+ * Combine the calculated parameters with the existing ones
+ * according to the value of the slider
+ * @return combined parameters
+ */
+ private EstimationParameters calculateCombinedParameters()
+ {
+ final double fraction1 = 1 - 0.1 * _weightSlider.getValue(); // slider left = value 0 = fraction 1 = keep current
+ EstimationParameters oldParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
+ return oldParams.combine(_calculatedParams, fraction1);
+ }
+
+ /**
+ * Update the labels to show the combined parameters
+ * @param inCombinedParams combined estimation parameters
+ */
+ private void updateCombinedLabels(EstimationParameters inCombinedParams)
+ {
+ // Update the slider description label
+ String sliderDesc = null;
+ final int sliderVal = _weightSlider.getValue();
+ switch (sliderVal)
+ {
+ case 0: sliderDesc = I18nManager.getText("dialog.learnestimationparams.weight.100pccurrent"); break;
+ case 5: sliderDesc = I18nManager.getText("dialog.learnestimationparams.weight.50pc"); break;
+ case 10: sliderDesc = I18nManager.getText("dialog.learnestimationparams.weight.100pccalculated"); break;
+ default:
+ final int currTenths = 10 - sliderVal, calcTenths = sliderVal;
+ sliderDesc = "" + currTenths + "0% " + I18nManager.getText("dialog.learnestimationparams.weight.current")
+ + " + " + calcTenths + "0% " + I18nManager.getText("dialog.learnestimationparams.weight.calculated");
+ }
+ _sliderDescLabel.setText(sliderDesc);
+ // And update all the combined params labels
+ _combinedParamPanel.updateParameters(inCombinedParams);
+ _combineButton.setEnabled(sliderVal > 0);
+ }
+
+ /**
+ * React to the combine button, by saving the combined parameters in the config
+ */
+ private void combineAndFinish()
+ {
+ EstimationParameters params = calculateCombinedParameters();
+ Config.setConfigString(Config.KEY_ESTIMATION_PARAMS, params.toConfigString());
+ _dialog.dispose();
+ }
+}
diff --git a/tim/prune/function/estimate/ParametersPanel.java b/tim/prune/function/estimate/ParametersPanel.java
new file mode 100644
index 0000000..3632831
--- /dev/null
+++ b/tim/prune/function/estimate/ParametersPanel.java
@@ -0,0 +1,158 @@
+package tim.prune.function.estimate;
+
+import java.awt.Component;
+import java.awt.GridLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.DisplayUtils;
+
+/**
+ * Display panel for showing estimation parameters
+ * in a standard grid form
+ */
+public class ParametersPanel extends JPanel
+{
+ /** Flag for whether average error should be shown */
+ private boolean _showAverageError = false;
+ /** Labels for calculated parameters */
+ private JLabel _fsUnitsLabel = null, _flatSpeedLabel = null;
+ private JLabel _climbUnitsLabel = null;
+ private JLabel _gentleClimbLabel = null, _steepClimbLabel = null;
+ private JLabel _descentUnitsLabel = null;
+ private JLabel _gentleDescentLabel = null, _steepDescentLabel = null;
+ private JLabel _averageErrorLabel = null;
+
+
+ /**
+ * Constructor
+ * @param inTitleKey key to use for title of panel
+ */
+ public ParametersPanel(String inTitleKey)
+ {
+ this(inTitleKey, false);
+ }
+
+ /**
+ * Constructor
+ * @param inTitleKey key to use for title of panel
+ * @param inShowAvgError true to show average error line
+ */
+ public ParametersPanel(String inTitleKey, boolean inShowAvgError)
+ {
+ super();
+ _showAverageError = inShowAvgError;
+ if (inTitleKey != null) {
+ setBorder(BorderFactory.createTitledBorder(I18nManager.getText(inTitleKey)));
+ }
+ setLayout(new GridLayout(0, 3, 3, 3));
+ addLabels();
+ }
+
+ private void addLabels()
+ {
+ // flat speed
+ _fsUnitsLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + " 5km : ");
+ _fsUnitsLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+ add(_fsUnitsLabel);
+ _flatSpeedLabel = new JLabel("60 minutes"); // (filled in later)
+ add(_flatSpeedLabel);
+ add(new JLabel(""));
+ // Headers for gentle and steep
+ add(new JLabel(""));
+ JLabel gentleLabel = new JLabel(I18nManager.getText("dialog.estimatetime.gentle"));
+ add(gentleLabel);
+ JLabel steepLabel = new JLabel(I18nManager.getText("dialog.estimatetime.steep"));
+ add(steepLabel);
+ // Climb
+ _climbUnitsLabel = new JLabel("Climb 100m: ");
+ _climbUnitsLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+ add(_climbUnitsLabel);
+ _gentleClimbLabel = new JLabel("22 minutes"); // (filled in later)
+ add(_gentleClimbLabel);
+ _steepClimbLabel = new JLabel("22 minutes"); // (filled in later)
+ add(_steepClimbLabel);
+ // Descent
+ _descentUnitsLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": ");
+ _descentUnitsLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+ add(_descentUnitsLabel);
+ _gentleDescentLabel = new JLabel("22 minutes"); // (filled in later)
+ add(_gentleDescentLabel);
+ _steepDescentLabel = new JLabel("22 minutes"); // (filled in later)
+ add(_steepDescentLabel);
+ // Average error
+ if (_showAverageError)
+ {
+ JLabel errorLabel = new JLabel(I18nManager.getText("dialog.learnestimationparams.averageerror") + ": ");
+ errorLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+ add(errorLabel);
+ _averageErrorLabel = new JLabel("22 minutes"); // (filled in later)
+ add(_averageErrorLabel);
+ }
+ }
+
+ /**
+ * Update the labels using the given parameters
+ * @param inParams the parameters used or calculated
+ * @param inAverageError average error as percentage
+ * @param inShowError true to show this error value, false otherwise
+ */
+ private void updateParameters(EstimationParameters inParams, double inAverageError, boolean inShowError)
+ {
+ if (inParams == null || !inParams.isValid())
+ {
+ _flatSpeedLabel.setText("");
+ _gentleClimbLabel.setText(""); _steepClimbLabel.setText("");
+ _gentleDescentLabel.setText(""); _steepDescentLabel.setText("");
+ }
+ else
+ {
+ final String minsText = " " + I18nManager.getText("units.minutes");
+ String[] values = inParams.getStrings(); // these strings are already formatted locally
+ _fsUnitsLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
+ " " + EstimationParameters.getStandardDistance() + ": ");
+ _flatSpeedLabel.setText(values[0] + minsText);
+ final String heightString = " " + EstimationParameters.getStandardClimb() + ": ";
+ _climbUnitsLabel.setText(I18nManager.getText("dialog.estimatetime.climb") + heightString);
+ _gentleClimbLabel.setText(values[1] + minsText);
+ _steepClimbLabel.setText(values[2] + minsText);
+ _descentUnitsLabel.setText(I18nManager.getText("dialog.estimatetime.descent") + heightString);
+ _gentleDescentLabel.setText(values[3] + minsText);
+ _steepDescentLabel.setText(values[4] + minsText);
+ }
+ // Average error
+ if (_averageErrorLabel != null)
+ {
+ if (inParams == null || !inParams.isValid() || !inShowError)
+ {
+ _averageErrorLabel.setText("");
+ }
+ else
+ {
+ _averageErrorLabel.setText(DisplayUtils.formatOneDp(inAverageError) + " %");
+ }
+ }
+ }
+
+ /**
+ * Just show the parameters, with no average error
+ * @param inParams parameters to show
+ */
+ public void updateParameters(EstimationParameters inParams)
+ {
+ updateParameters(inParams, 0.0, false);
+ }
+
+ /**
+ * Show the parameters and the average error
+ * @param inParams parameters to show
+ * @param inAverageError average error as percentage
+ */
+ public void updateParameters(EstimationParameters inParams, double inAverageError)
+ {
+ updateParameters(inParams, inAverageError, true);
+ }
+}
diff --git a/tim/prune/function/estimate/jama/Maths.java b/tim/prune/function/estimate/jama/Maths.java
new file mode 100644
index 0000000..7c3326d
--- /dev/null
+++ b/tim/prune/function/estimate/jama/Maths.java
@@ -0,0 +1,31 @@
+package tim.prune.function.estimate.jama;
+
+/**
+ * Static helper method, taken from public domain NIST code for JAMA
+ */
+public abstract class Maths
+{
+ /**
+ * Work out sqrt(a^2 + b^2)
+ */
+ public static double pythag(double a, double b)
+ {
+ double r;
+
+ if (Math.abs(a) > Math.abs(b))
+ {
+ r = b/a;
+ r = Math.abs(a)*Math.sqrt(1+r*r);
+ }
+ else if (b != 0)
+ {
+ r = a/b;
+ r = Math.abs(b)*Math.sqrt(1+r*r);
+ }
+ else
+ {
+ r = 0.0;
+ }
+ return r;
+ }
+}
diff --git a/tim/prune/function/estimate/jama/Matrix.java b/tim/prune/function/estimate/jama/Matrix.java
new file mode 100644
index 0000000..face1a9
--- /dev/null
+++ b/tim/prune/function/estimate/jama/Matrix.java
@@ -0,0 +1,259 @@
+package tim.prune.function.estimate.jama;
+
+/**
+ * The Java Matrix Class provides the fundamental operations of numerical linear algebra.
+ * Original authors The MathWorks, Inc. and the National Institute of Standards and Technology
+ * The original public domain code has now been modified and reduced to only contain
+ * the use of QR Decomposition of rectangular matrices, to solve least squares regression,
+ * and is placed under GPL2 with the rest of the GpsPrune code.
+ */
+public class Matrix
+{
+
+ /** Array for internal storage of elements */
+ private double[][] _matrix;
+
+ /** Row and column dimensions */
+ private int _m, _n;
+
+
+ /**
+ * Construct an m-by-n matrix of zeros
+ * @param inM Number of rows
+ * @param inN Number of colums
+ */
+ public Matrix(int inM, int inN)
+ {
+ _m = inM;
+ _n = inN;
+ _matrix = new double[inM][inN];
+ }
+
+ /**
+ * Construct a matrix from a 2-D array
+ * @param A Two-dimensional array of doubles.
+ * @exception IllegalArgumentException All rows must have the same length
+ */
+ public Matrix(double[][] A)
+ {
+ _m = A.length;
+ _n = A[0].length;
+ for (int i = 0; i < _m; i++) {
+ if (A[i].length != _n) {
+ throw new IllegalArgumentException("All rows must have the same length.");
+ }
+ }
+ _matrix = A;
+ }
+
+ /**
+ * Construct a matrix quickly without checking arguments.
+ * @param inA Two-dimensional array of doubles.
+ * @param inM Number of rows
+ * @param inN Number of columns
+ */
+ public Matrix(double[][] inA, int inM, int inN)
+ {
+ _matrix = inA;
+ _m = inM;
+ _n = inN;
+ }
+
+ /*
+ * ------------------------ Public Methods ------------------------
+ */
+
+
+ /**
+ * Set a value in a cell of the matrix
+ * @param inRow row index
+ * @param inCol column index
+ * @param inValue value to set
+ */
+ public void setValue(int inRow, int inCol, double inValue)
+ {
+ _matrix[inRow][inCol] = inValue;
+ }
+
+ /**
+ * Access the internal two-dimensional array.
+ * @return Pointer to the two-dimensional array of matrix elements.
+ */
+ public double[][] getArray() {
+ return _matrix;
+ }
+
+ /**
+ * Copy the internal two-dimensional array.
+ * @return Two-dimensional array copy of matrix elements.
+ */
+ public double[][] getArrayCopy()
+ {
+ double[][] C = new double[_m][_n];
+ for (int i = 0; i < _m; i++) {
+ for (int j = 0; j < _n; j++) {
+ C[i][j] = _matrix[i][j];
+ }
+ }
+ return C;
+ }
+
+ /**
+ * Get a single element.
+ * @param inRow Row index
+ * @param inCol Column index
+ * @return A(inRow,inCol)
+ * @exception ArrayIndexOutOfBoundsException
+ */
+ public double get(int inRow, int inCol) {
+ return _matrix[inRow][inCol];
+ }
+
+ /** @return number of rows _m */
+ public int getNumRows() {
+ return _m;
+ }
+
+ /** @return number of columns _n */
+ public int getNumColumns() {
+ return _n;
+ }
+
+ /**
+ * Get a submatrix
+ * @param i0 Initial row index
+ * @param i1 Final row index
+ * @param j0 Initial column index
+ * @param j1 Final column index
+ * @return A(i0:i1,j0:j1)
+ * @exception ArrayIndexOutOfBoundsException
+ */
+ public Matrix getMatrix(int i0, int i1, int j0, int j1)
+ {
+ Matrix X = new Matrix(i1 - i0 + 1, j1 - j0 + 1);
+ double[][] B = X.getArray();
+ try {
+ for (int i = i0; i <= i1; i++) {
+ for (int j = j0; j <= j1; j++) {
+ B[i - i0][j - j0] = _matrix[i][j];
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+ }
+ return X;
+ }
+
+
+ /**
+ * Linear algebraic matrix multiplication, A * B
+ * @param B another matrix
+ * @return Matrix product, A * B
+ * @exception IllegalArgumentException if matrix dimensions don't agree
+ */
+ public Matrix times(Matrix B)
+ {
+ if (B._m != _n) {
+ throw new IllegalArgumentException("Matrix inner dimensions must agree.");
+ }
+ Matrix X = new Matrix(_m, B._n);
+ double[][] C = X.getArray();
+ double[] Bcolj = new double[_n];
+ for (int j = 0; j < B._n; j++) {
+ for (int k = 0; k < _n; k++) {
+ Bcolj[k] = B._matrix[k][j];
+ }
+ for (int i = 0; i < _m; i++) {
+ double[] Arowi = _matrix[i];
+ double s = 0;
+ for (int k = 0; k < _n; k++) {
+ s += Arowi[k] * Bcolj[k];
+ }
+ C[i][j] = s;
+ }
+ }
+ return X;
+ }
+
+ /**
+ * Subtract the other matrix from this one
+ * @param B another matrix
+ * @return difference this - B
+ * @exception IllegalArgumentException if matrix dimensions don't agree
+ */
+ public Matrix minus(Matrix B)
+ {
+ if (B._m != _m || B._n != _n) {
+ throw new IllegalArgumentException("Matrix dimensions must agree.");
+ }
+ Matrix result = new Matrix(_m, _n);
+ for (int i = 0; i < _m; i++) {
+ for (int j = 0; j < _n; j++) {
+ result.setValue(i, j, get(i, j) - B.get(i, j));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Divide each element of this matrix by the corresponding element in the other one
+ * @param B another matrix
+ * @return this[i,j]/other[i,j]
+ * @exception IllegalArgumentException if matrix dimensions don't agree
+ */
+ public Matrix divideEach(Matrix B)
+ {
+ if (B._m != _m || B._n != _n) {
+ throw new IllegalArgumentException("Matrix dimensions must agree.");
+ }
+ Matrix result = new Matrix(_m, _n);
+ for (int i = 0; i < _m; i++) {
+ for (int j = 0; j < _n; j++) {
+ result.setValue(i, j, get(i, j) / B.get(i, j));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Solve A*X = B
+ * @param B right hand side
+ * @return least squares solution
+ */
+ public Matrix solve(Matrix B) {
+ return new QRDecomposition(this).solve(B);
+ }
+
+ /**
+ * @return the average absolute value of all the elements in the matrix
+ */
+ public double getAverageAbsValue()
+ {
+ double total = 0.0;
+ for (int i = 0; i < _m; i++) {
+ for (int j = 0; j < _n; j++) {
+ total += Math.abs(_matrix[i][j]);
+ }
+ }
+ return total / _m / _n;
+ }
+
+ /**
+ * Primitive output for debugging
+ */
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append('(');
+ for (int i = 0; i < _m; i++) {
+ builder.append('(');
+ for (int j = 0; j < _n; j++) {
+ builder.append((_matrix[i][j]));
+ builder.append(", ");
+ }
+ builder.append(") ");
+ }
+ builder.append(')');
+ return builder.toString();
+ }
+}
diff --git a/tim/prune/function/estimate/jama/QRDecomposition.java b/tim/prune/function/estimate/jama/QRDecomposition.java
new file mode 100644
index 0000000..e8aa2b7
--- /dev/null
+++ b/tim/prune/function/estimate/jama/QRDecomposition.java
@@ -0,0 +1,219 @@
+package tim.prune.function.estimate.jama;
+
+/**
+ * QR Decomposition.
+ *
+ * For an m-by-n matrix A with m >= n, the QR decomposition is an m-by-n
+ * orthogonal matrix Q and an n-by-n upper triangular matrix R so that A = Q*R.
+ *
+ * The QR decomposition always exists, even if the matrix does not have full
+ * rank, so the constructor will never fail. The primary use of the QR
+ * decomposition is in the least squares solution of nonsquare systems of
+ * simultaneous linear equations. This will fail if isFullRank() returns false.
+ *
+ * Original authors The MathWorks, Inc. and the National Institute of Standards and Technology
+ * The original public domain code has now been modified and reduced,
+ * and is placed under GPL2 with the rest of the GpsPrune code.
+ */
+public class QRDecomposition
+{
+
+ /** Array for internal storage of decomposition */
+ private double[][] _QR;
+
+ /** Row and column dimensions */
+ private int _m, _n;
+
+ /** Array for internal storage of diagonal of R */
+ private double[] _Rdiag;
+
+
+ /**
+ * QR Decomposition, computed by Householder reflections.
+ *
+ * @param inA Rectangular matrix
+ * @return Structure to access R and the Householder vectors and compute Q.
+ */
+ public QRDecomposition(Matrix inA)
+ {
+ // Initialize.
+ _QR = inA.getArrayCopy();
+ _m = inA.getNumRows();
+ _n = inA.getNumColumns();
+ _Rdiag = new double[_n];
+
+ // Main loop.
+ for (int k = 0; k < _n; k++)
+ {
+ // Compute 2-norm of k-th column without under/overflow.
+ double nrm = 0;
+ for (int i = k; i < _m; i++) {
+ nrm = Maths.pythag(nrm, _QR[i][k]);
+ }
+
+ if (nrm != 0.0)
+ {
+ // Form k-th Householder vector.
+ if (_QR[k][k] < 0) {
+ nrm = -nrm;
+ }
+ for (int i = k; i < _m; i++) {
+ _QR[i][k] /= nrm;
+ }
+ _QR[k][k] += 1.0;
+
+ // Apply transformation to remaining columns.
+ for (int j = k + 1; j < _n; j++)
+ {
+ double s = 0.0;
+ for (int i = k; i < _m; i++) {
+ s += _QR[i][k] * _QR[i][j];
+ }
+ s = -s / _QR[k][k];
+ for (int i = k; i < _m; i++) {
+ _QR[i][j] += s * _QR[i][k];
+ }
+ }
+ }
+ _Rdiag[k] = -nrm;
+ }
+ }
+
+ /*
+ * ------------------------ Public Methods ------------------------
+ */
+
+ /**
+ * Is the matrix full rank?
+ * @return true if R, and hence A, has full rank.
+ */
+ public boolean isFullRank()
+ {
+ for (int j = 0; j < _n; j++) {
+ if (_Rdiag[j] == 0)
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return the Householder vectors
+ * @deprecated
+ * @return Lower trapezoidal matrix whose columns define the reflections
+ */
+ private Matrix getH()
+ {
+ Matrix X = new Matrix(_m, _n);
+ double[][] H = X.getArray();
+ for (int i = 0; i < _m; i++) {
+ for (int j = 0; j < _n; j++) {
+ if (i >= j) {
+ H[i][j] = _QR[i][j];
+ } else {
+ H[i][j] = 0.0;
+ }
+ }
+ }
+ return X;
+ }
+
+ /**
+ * Return the upper triangular factor
+ * @deprecated
+ * @return R
+ */
+ private Matrix getR()
+ {
+ Matrix X = new Matrix(_n, _n);
+ double[][] R = X.getArray();
+ for (int i = 0; i < _n; i++) {
+ for (int j = 0; j < _n; j++) {
+ if (i < j) {
+ R[i][j] = _QR[i][j];
+ } else if (i == j) {
+ R[i][j] = _Rdiag[i];
+ } else {
+ R[i][j] = 0.0;
+ }
+ }
+ }
+ return X;
+ }
+
+ /**
+ * Generate and return the (economy-sized) orthogonal factor
+ * @deprecated
+ * @return Q
+ */
+ private Matrix getQ()
+ {
+ Matrix X = new Matrix(_m, _n);
+ double[][] Q = X.getArray();
+ for (int k = _n - 1; k >= 0; k--) {
+ for (int i = 0; i < _m; i++) {
+ Q[i][k] = 0.0;
+ }
+ Q[k][k] = 1.0;
+ for (int j = k; j < _n; j++) {
+ if (_QR[k][k] != 0) {
+ double s = 0.0;
+ for (int i = k; i < _m; i++) {
+ s += _QR[i][k] * Q[i][j];
+ }
+ s = -s / _QR[k][k];
+ for (int i = k; i < _m; i++) {
+ Q[i][j] += s * _QR[i][k];
+ }
+ }
+ }
+ }
+ return X;
+ }
+
+ /**
+ * Least squares solution of A*X = B
+ * @param B A Matrix with as many rows as A and any number of columns
+ * @return X that minimizes the two norm of Q*R*X-B
+ * @exception IllegalArgumentException if matrix dimensions don't agree
+ * @exception RuntimeException if Matrix is rank deficient.
+ */
+ public Matrix solve(Matrix B)
+ {
+ if (B.getNumRows() != _m) {
+ throw new IllegalArgumentException("Matrix row dimensions must agree.");
+ }
+ if (!isFullRank()) {
+ throw new RuntimeException("Matrix is rank deficient.");
+ }
+
+ // Copy right hand side
+ int nx = B.getNumColumns();
+ double[][] X = B.getArrayCopy();
+
+ // Compute Y = transpose(Q)*B
+ for (int k = 0; k < _n; k++) {
+ for (int j = 0; j < nx; j++) {
+ double s = 0.0;
+ for (int i = k; i < _m; i++) {
+ s += _QR[i][k] * X[i][j];
+ }
+ s = -s / _QR[k][k];
+ for (int i = k; i < _m; i++) {
+ X[i][j] += s * _QR[i][k];
+ }
+ }
+ }
+ // Solve R*X = Y;
+ for (int k = _n - 1; k >= 0; k--) {
+ for (int j = 0; j < nx; j++) {
+ X[k][j] /= _Rdiag[k];
+ }
+ for (int i = 0; i < k; i++) {
+ for (int j = 0; j < nx; j++) {
+ X[i][j] -= X[k][j] * _QR[i][k];
+ }
+ }
+ }
+ return (new Matrix(X, _n, nx).getMatrix(0, _n - 1, 0, nx - 1));
+ }
+}
diff --git a/tim/prune/function/gpsies/GenericDownloaderFunction.java b/tim/prune/function/gpsies/GenericDownloaderFunction.java
index cf634c0..286ba1a 100644
--- a/tim/prune/function/gpsies/GenericDownloaderFunction.java
+++ b/tim/prune/function/gpsies/GenericDownloaderFunction.java
@@ -40,6 +40,8 @@ public abstract class GenericDownloaderFunction extends GenericFunction implemen
protected JTable _trackTable = null;
/** Cancelled flag */
protected boolean _cancelled = false;
+ /** error message */
+ protected String _errorMessage = null;
/** Status label */
protected JLabel _statusLabel = null;
/** Description box */
@@ -84,6 +86,7 @@ public abstract class GenericDownloaderFunction extends GenericFunction implemen
_showButton.setEnabled(false);
_cancelled = false;
_descriptionBox.setText("");
+ _errorMessage = null;
// Start new thread to load list asynchronously
new Thread(this).start();
diff --git a/tim/prune/function/gpsies/TrackListModel.java b/tim/prune/function/gpsies/TrackListModel.java
index 904dd8b..274c0b8 100644
--- a/tim/prune/function/gpsies/TrackListModel.java
+++ b/tim/prune/function/gpsies/TrackListModel.java
@@ -57,6 +57,12 @@ public class TrackListModel extends AbstractTableModel
return _trackList.size();
}
+ /** @return true if there are no rows */
+ public boolean isEmpty()
+ {
+ return getRowCount() == 0;
+ }
+
/**
* @param inColNum column number
* @return column label for given column
diff --git a/tim/prune/function/gpsies/UploadGpsiesFunction.java b/tim/prune/function/gpsies/UploadGpsiesFunction.java
index 586cbb6..9a4ae30 100644
--- a/tim/prune/function/gpsies/UploadGpsiesFunction.java
+++ b/tim/prune/function/gpsies/UploadGpsiesFunction.java
@@ -182,7 +182,8 @@ public class UploadGpsiesFunction extends GenericFunction
enableOK();
}
};
- GuiGridLayout actGrid = new GuiGridLayout(activityPanel, true);
+ // Why not a simple grid layout here?
+ GuiGridLayout actGrid = new GuiGridLayout(activityPanel, new double[] {1.0, 1.0}, new boolean[] {false, false});
final int numActivities = ACTIVITY_KEYS.length;
_activityCheckboxes = new JCheckBox[numActivities];
for (int i=0; i 0)
{
// Inform app including undo information
diff --git a/tim/prune/function/srtm/TileFinder.java b/tim/prune/function/srtm/TileFinder.java
index 209db7f..afcc28f 100644
--- a/tim/prune/function/srtm/TileFinder.java
+++ b/tim/prune/function/srtm/TileFinder.java
@@ -54,10 +54,11 @@ public abstract class TileFinder
*/
private static byte[] readDatFile()
{
+ InputStream in = null;
try
{
// Need absolute path to dat file
- InputStream in = TileFinder.class.getResourceAsStream("/tim/prune/function/srtm/srtmtiles.dat");
+ in = TileFinder.class.getResourceAsStream("/tim/prune/function/srtm/srtmtiles.dat");
if (in != null)
{
byte[] buffer = new byte[in.available()];
@@ -69,6 +70,13 @@ public abstract class TileFinder
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;
}
}
diff --git a/tim/prune/gui/DecimalNumberField.java b/tim/prune/gui/DecimalNumberField.java
new file mode 100644
index 0000000..f1baa2a
--- /dev/null
+++ b/tim/prune/gui/DecimalNumberField.java
@@ -0,0 +1,111 @@
+package tim.prune.gui;
+
+import java.awt.Dimension;
+
+import javax.swing.JTextField;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.PlainDocument;
+
+/**
+ * text field for holding a decimal number with validation
+ * - doesn't allow certain characters such as a-z to be entered
+ */
+public class DecimalNumberField extends JTextField
+{
+ /**
+ * Inner class to act as document for validation
+ */
+ protected static class DecimalNumberDocument extends PlainDocument
+ {
+ private boolean _allowNegative = false;
+
+ /** constructor */
+ DecimalNumberDocument(boolean inAllowNegative) {
+ _allowNegative = inAllowNegative;
+ }
+
+ /**
+ * Override the insert string method
+ * @param offs offset
+ * @param str string
+ * @param a attributes
+ * @throws BadLocationException on insert failure
+ */
+ public void insertString(int offs, String str, AttributeSet a)
+ throws BadLocationException
+ {
+ char[] source = str.toCharArray();
+ char[] result = new char[source.length];
+ int j = 0;
+ for (int i = 0; i < result.length; i++) {
+ if (!Character.isLetter(source[i]) && (_allowNegative || source[i] != '-') && source[i] != ' ') // no letters, no minus sign or space
+ result[j++] = source[i];
+ }
+ super.insertString(offs, new String(result, 0, j), a);
+ }
+ }
+
+
+ /**
+ * Constructor
+ */
+ public DecimalNumberField()
+ {
+ super(6);
+ setDocument(new DecimalNumberDocument(false));
+ }
+
+ /**
+ * Constructor
+ * @param inAllowNegative true to allow negative numbers
+ */
+ public DecimalNumberField(boolean inAllowNegative)
+ {
+ super(6);
+ setDocument(new DecimalNumberDocument(inAllowNegative));
+ }
+
+ /**
+ * @return double value
+ */
+ public double getValue()
+ {
+ return parseValue(getText());
+ }
+
+ /**
+ * @param inValue value to set
+ */
+ public void setValue(double inValue)
+ {
+ setText("" + inValue);
+ }
+
+ /**
+ * @param inText text to parse
+ * @return value as double
+ */
+ private static double parseValue(String inText)
+ {
+ double value = 0.0;
+ try {
+ value = Double.parseDouble(inText);
+ }
+ catch (NumberFormatException nfe) {}
+ if (value < 0) {
+ value = 0;
+ }
+ return value;
+ }
+
+ /**
+ * Put a minimum on the minimum width
+ */
+ public Dimension getMinimumSize()
+ {
+ Dimension dim = super.getMinimumSize();
+ if (dim.width < 50) dim.width = 50;
+ return dim;
+ }
+}
diff --git a/tim/prune/gui/DetailsDisplay.java b/tim/prune/gui/DetailsDisplay.java
index 7b3a4ff..13df7cd 100644
--- a/tim/prune/gui/DetailsDisplay.java
+++ b/tim/prune/gui/DetailsDisplay.java
@@ -8,7 +8,6 @@ import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import java.text.NumberFormat;
import javax.swing.BorderFactory;
import javax.swing.Box;
@@ -51,6 +50,7 @@ public class DetailsDisplay extends GenericDisplay
private JLabel _latLabel = null, _longLabel = null;
private JLabel _altLabel = null;
private JLabel _timeLabel = null;
+ private JLabel _descLabel = null;
private JLabel _speedLabel = null, _vSpeedLabel = null;
private JLabel _nameLabel = null, _typeLabel = null;
@@ -84,8 +84,6 @@ public class DetailsDisplay extends GenericDisplay
// Units
private JComboBox _coordFormatDropdown = null;
private JComboBox _distUnitsDropdown = null;
- // Formatter
- private NumberFormat _distanceFormatter = NumberFormat.getInstance();
// Cached labels
private static final String LABEL_POINT_SELECTED = I18nManager.getText("details.index.selected") + ": ";
@@ -95,6 +93,7 @@ public class DetailsDisplay extends GenericDisplay
private static final String LABEL_POINT_TIMESTAMP = I18nManager.getText("fieldname.timestamp") + ": ";
private static final String LABEL_POINT_WAYPOINTNAME = I18nManager.getText("fieldname.waypointname") + ": ";
private static final String LABEL_POINT_WAYPOINTTYPE = I18nManager.getText("fieldname.waypointtype") + ": ";
+ private static final String LABEL_POINT_DESCRIPTION = I18nManager.getText("fieldname.description") + ": ";
private static final String LABEL_POINT_SPEED = I18nManager.getText("fieldname.speed") + ": ";
private static final String LABEL_POINT_VERTSPEED = I18nManager.getText("fieldname.verticalspeed") + ": ";
private static final String LABEL_RANGE_SELECTED = I18nManager.getText("details.range.selected") + ": ";
@@ -135,6 +134,8 @@ public class DetailsDisplay extends GenericDisplay
_timeLabel = new JLabel("");
_timeLabel.setMinimumSize(new Dimension(120, 10));
pointDetailsPanel.add(_timeLabel);
+ _descLabel = new JLabel("");
+ pointDetailsPanel.add(_descLabel);
_speedLabel = new JLabel("");
pointDetailsPanel.add(_speedLabel);
_vSpeedLabel = new JLabel("");
@@ -296,6 +297,7 @@ public class DetailsDisplay extends GenericDisplay
_longLabel.setText("");
_altLabel.setText("");
_timeLabel.setText("");
+ _descLabel.setText("");
_nameLabel.setText("");
_typeLabel.setText("");
_speedLabel.setText("");
@@ -315,9 +317,27 @@ public class DetailsDisplay extends GenericDisplay
: "");
if (currentPoint.hasTimestamp()) {
_timeLabel.setText(LABEL_POINT_TIMESTAMP + currentPoint.getTimestamp().getText());
+ _timeLabel.setToolTipText(currentPoint.getTimestamp().getText());
}
else {
_timeLabel.setText("");
+ _timeLabel.setToolTipText("");
+ }
+ // Maybe the point has a description?
+ String pointDesc = currentPoint.getFieldValue(Field.DESCRIPTION);
+ if (pointDesc == null || pointDesc.equals("") || currentPoint.hasMedia()) {
+ _descLabel.setText("");
+ _descLabel.setToolTipText("");
+ }
+ else
+ {
+ if (pointDesc.length() < 5) {
+ _descLabel.setText(LABEL_POINT_DESCRIPTION + pointDesc);
+ }
+ else {
+ _descLabel.setText(shortenString(pointDesc));
+ }
+ _descLabel.setToolTipText(pointDesc);
}
// Speed can come from either timestamps and distances, or speed values in data
@@ -325,7 +345,7 @@ public class DetailsDisplay extends GenericDisplay
SpeedCalculator.calculateSpeed(_track, currentPointIndex, speedValue);
if (speedValue.isValid())
{
- String speed = roundedNumber(speedValue.getValue()) + " " + speedUnitsStr;
+ String speed = DisplayUtils.roundedNumber(speedValue.getValue()) + " " + speedUnitsStr;
_speedLabel.setText(LABEL_POINT_SPEED + speed);
}
else {
@@ -337,7 +357,7 @@ public class DetailsDisplay extends GenericDisplay
if (speedValue.isValid())
{
String vSpeedUnitsStr = I18nManager.getText(unitSet.getVerticalSpeedUnit().getShortnameKey());
- String speed = roundedNumber(speedValue.getValue()) + " " + vSpeedUnitsStr;
+ String speed = DisplayUtils.roundedNumber(speedValue.getValue()) + " " + vSpeedUnitsStr;
_vSpeedLabel.setText(LABEL_POINT_VERTSPEED + speed);
}
else {
@@ -374,12 +394,12 @@ public class DetailsDisplay extends GenericDisplay
_rangeLabel.setText(LABEL_RANGE_SELECTED
+ (selection.getStart()+1) + " " + I18nManager.getText("details.range.to")
+ " " + (selection.getEnd()+1));
- _distanceLabel.setText(LABEL_RANGE_DISTANCE + roundedNumber(selection.getDistance()) + " " + distUnitsStr);
+ _distanceLabel.setText(LABEL_RANGE_DISTANCE + DisplayUtils.roundedNumber(selection.getDistance()) + " " + distUnitsStr);
if (selection.getNumSeconds() > 0)
{
_durationLabel.setText(LABEL_RANGE_DURATION + DisplayUtils.buildDurationString(selection.getNumSeconds()));
_aveSpeedLabel.setText(I18nManager.getText("details.range.avespeed") + ": "
- + roundedNumber(selection.getDistance()/selection.getNumSeconds()*3600.0) + " " + speedUnitsStr);
+ + DisplayUtils.roundedNumber(selection.getDistance()/selection.getNumSeconds()*3600.0) + " " + speedUnitsStr);
}
else {
_durationLabel.setText("");
@@ -502,27 +522,6 @@ public class DetailsDisplay extends GenericDisplay
}
- /**
- * Format a number to a sensible precision
- * @param inDist distance
- * @return formatted String
- */
- private String roundedNumber(double inDist)
- {
- // Set precision of formatter
- int numDigits = 0;
- if (inDist < 1.0)
- numDigits = 3;
- else if (inDist < 10.0)
- numDigits = 2;
- else if (inDist < 100.0)
- numDigits = 1;
- // set formatter
- _distanceFormatter.setMaximumFractionDigits(numDigits);
- _distanceFormatter.setMinimumFractionDigits(numDigits);
- return _distanceFormatter.format(inDist);
- }
-
/**
* Restrict the given coordinate to a limited number of decimal places for display
* @param inCoord coordinate string
@@ -582,15 +581,26 @@ public class DetailsDisplay extends GenericDisplay
*/
private static String shortenPath(String inFullPath)
{
+ String path = inFullPath;
// Chop off the home path if possible
final String homePath = System.getProperty("user.home").toLowerCase();
if (inFullPath != null && inFullPath.toLowerCase().startsWith(homePath)) {
- inFullPath = inFullPath.substring(homePath.length()+1);
+ path = inFullPath.substring(homePath.length()+1);
}
- if (inFullPath == null || inFullPath.length() < 21) {
- return inFullPath;
+ return shortenString(path);
+ }
+
+ /**
+ * @param inString string to shorten
+ * @return shortened string from the beginning
+ */
+ private static String shortenString(String inString)
+ {
+ // Limit is hardcoded here, maybe it should depend on parent component width and font size etc?
+ if (inString == null || inString.length() < 21) {
+ return inString;
}
- // path is too long
- return inFullPath.substring(0, 20) + "...";
+ // string is too long
+ return inString.substring(0, 20) + "...";
}
}
diff --git a/tim/prune/gui/DisplayUtils.java b/tim/prune/gui/DisplayUtils.java
index df706b7..49ca387 100644
--- a/tim/prune/gui/DisplayUtils.java
+++ b/tim/prune/gui/DisplayUtils.java
@@ -1,5 +1,7 @@
package tim.prune.gui;
+import java.text.NumberFormat;
+
import tim.prune.I18nManager;
/**
@@ -7,6 +9,19 @@ import tim.prune.I18nManager;
*/
public abstract class DisplayUtils
{
+ /** Number formatter for one decimal place */
+ private static final NumberFormat FORMAT_ONE_DP = NumberFormat.getNumberInstance();
+
+ /** Static block to initialise the one d.p. formatter */
+ static
+ {
+ FORMAT_ONE_DP.setMaximumFractionDigits(1);
+ FORMAT_ONE_DP.setMinimumFractionDigits(1);
+ }
+ /** Flexible number formatter with different decimal places */
+ private static final NumberFormat FORMAT_FLEX = NumberFormat.getNumberInstance();
+
+
/**
* Build a String to describe a time duration
* @param inNumSecs number of seconds
@@ -25,4 +40,34 @@ public abstract class DisplayUtils
if (inNumSecs < 86400000L) return "" + (inNumSecs / 86400L) + I18nManager.getText("display.range.time.days");
return "big";
}
+
+ /**
+ * @param inNumber number to format
+ * @return formatted number to one decimal place
+ */
+ public static String formatOneDp(double inNumber)
+ {
+ return FORMAT_ONE_DP.format(inNumber);
+ }
+
+ /**
+ * Format a number to a sensible precision
+ * @param inVal value to format
+ * @return formatted String using local format
+ */
+ public static String roundedNumber(double inVal)
+ {
+ // Set precision of formatter
+ int numDigits = 0;
+ if (inVal < 1.0)
+ numDigits = 3;
+ else if (inVal < 10.0)
+ numDigits = 2;
+ else if (inVal < 100.0)
+ numDigits = 1;
+ // set formatter
+ FORMAT_FLEX.setMaximumFractionDigits(numDigits);
+ FORMAT_FLEX.setMinimumFractionDigits(numDigits);
+ return FORMAT_FLEX.format(inVal);
+ }
}
diff --git a/tim/prune/gui/GuiGridLayout.java b/tim/prune/gui/GuiGridLayout.java
index 7b226b5..703f7dd 100644
--- a/tim/prune/gui/GuiGridLayout.java
+++ b/tim/prune/gui/GuiGridLayout.java
@@ -8,15 +8,17 @@ import javax.swing.JComponent;
import javax.swing.JPanel;
/**
- * Class to make it easier to use GridBagLayout
- * for a two-column, non-equal-width layout
+ * Class to make it easier to use GridBagLayout for a non-equal-width layout
+ * Default is two columns but can handle more
*/
public class GuiGridLayout
{
private GridBagLayout _layout = null;
private GridBagConstraints _constraints = null;
private JPanel _panel = null;
- private boolean _allLeft = false;
+ private int _numColumns = 0;
+ private double[] _colWeights = null;
+ private boolean[] _rightAligns = null;
private int _x = 0;
private int _y = 0;
@@ -26,20 +28,30 @@ public class GuiGridLayout
*/
public GuiGridLayout(JPanel inPanel)
{
- this(inPanel, false);
+ // Default is two columns, with more weight to the right-hand one; first column is right-aligned
+ this(inPanel, null, null);
}
/**
* Constructor
* @param inPanel panel using layout
- * @param inAllLeft true to align all elements to left
+ * @param inColumnWeights array of column weights
+ * @param inAlignRights array of booleans, true for right alignment, false for left
*/
- public GuiGridLayout(JPanel inPanel, boolean inAllLeft)
+ public GuiGridLayout(JPanel inPanel, double[] inColumnWeights, boolean[] inAlignRights)
{
_panel = inPanel;
- _allLeft = inAllLeft;
_layout = new GridBagLayout();
_constraints = new GridBagConstraints();
+ _colWeights = inColumnWeights;
+ _rightAligns = inAlignRights;
+ if (_colWeights == null || _rightAligns == null || _colWeights.length != _rightAligns.length
+ || _colWeights.length < 2)
+ {
+ _colWeights = new double[] {0.5, 1.0};
+ _rightAligns = new boolean[] {true, false};
+ }
+ _numColumns = _colWeights.length;
_constraints.weightx = 1.0;
_constraints.weighty = 0.0;
_constraints.ipadx = 10;
@@ -57,17 +69,25 @@ public class GuiGridLayout
{
_constraints.gridx = _x;
_constraints.gridy = _y;
- _constraints.weightx = (_x==0?0.5:1.0);
+ _constraints.weightx = _colWeights[_x];
// set anchor
- _constraints.anchor = ((_x == 0 && !_allLeft)?GridBagConstraints.LINE_END:GridBagConstraints.LINE_START);
+ _constraints.anchor = (_rightAligns[_x]?GridBagConstraints.LINE_END:GridBagConstraints.LINE_START);
_layout.setConstraints(inComponent, _constraints);
// add to panel
_panel.add(inComponent);
// work out next position
_x++;
- if (_x > 1) {
- _x = 0;
- _y++;
+ if (_x >= _numColumns) {
+ nextRow();
}
}
+
+ /**
+ * Go to the next row of the grid
+ */
+ public void nextRow()
+ {
+ _x = 0;
+ _y++;
+ }
}
diff --git a/tim/prune/gui/IconManager.java b/tim/prune/gui/IconManager.java
index 72537b4..799e90f 100644
--- a/tim/prune/gui/IconManager.java
+++ b/tim/prune/gui/IconManager.java
@@ -71,6 +71,13 @@ public abstract class IconManager
/** Icon for stopping the current audio clip */
public static final String STOP_AUDIO = "stop_audio.gif";
+ /** Icon for a given entry being valid (green tick) */
+ public static final String ENTRY_VALID = "entry_valid.gif";
+ /** Icon for a given entry being invalid (red cross) */
+ public static final String ENTRY_INVALID = "entry_invalid.gif";
+ /** Icon for a given entry being empty (blank) */
+ public static final String ENTRY_NONE = "entry_none.gif";
+
/**
* Get the specified image
* @param inFilename filename of image (using constants)
diff --git a/tim/prune/gui/MenuManager.java b/tim/prune/gui/MenuManager.java
index c5501ee..16a0a66 100644
--- a/tim/prune/gui/MenuManager.java
+++ b/tim/prune/gui/MenuManager.java
@@ -21,6 +21,7 @@ import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.config.Config;
import tim.prune.data.AudioClip;
+import tim.prune.data.Field;
import tim.prune.data.Photo;
import tim.prune.data.RecentFile;
import tim.prune.data.RecentFileList;
@@ -47,6 +48,7 @@ public class MenuManager implements DataSubscriber
private JMenuItem _exportGpxItem = null;
private JMenuItem _exportPovItem = null;
private JMenuItem _exportSvgItem = null;
+ private JMenuItem _exportImageItem = null;
private JMenu _recentFileMenu = null;
private JMenuItem _undoItem = null;
private JMenuItem _clearUndoItem = null;
@@ -85,6 +87,8 @@ public class MenuManager implements DataSubscriber
private JMenuItem _downloadOsmItem = null;
private JMenuItem _distanceItem = null;
private JMenuItem _fullRangeDetailsItem = null;
+ private JMenuItem _estimateTimeItem = null;
+ private JMenuItem _learnEstimationParams = null;
private JMenuItem _saveExifItem = null;
private JMenuItem _photoPopupItem = null;
private JMenuItem _selectNoPhotoItem = null;
@@ -217,7 +221,11 @@ public class MenuManager implements DataSubscriber
// Svg
_exportSvgItem = makeMenuItem(FunctionLibrary.FUNCTION_SVGEXPORT, false);
fileMenu.add(_exportSvgItem);
+ // Image
+ _exportImageItem = makeMenuItem(FunctionLibrary.FUNCTION_IMAGEEXPORT, false);
+ fileMenu.add(_exportImageItem);
fileMenu.addSeparator();
+ // Exit
JMenuItem exitMenuItem = new JMenuItem(I18nManager.getText("menu.file.exit"));
exitMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
@@ -312,6 +320,9 @@ public class MenuManager implements DataSubscriber
trackMenu.add(searchWikipediaNamesItem);
_downloadOsmItem = makeMenuItem(FunctionLibrary.FUNCTION_DOWNLOAD_OSM, false);
trackMenu.add(_downloadOsmItem);
+ trackMenu.addSeparator();
+ _learnEstimationParams = makeMenuItem(FunctionLibrary.FUNCTION_LEARN_ESTIMATION_PARAMS, false);
+ trackMenu.add(_learnEstimationParams);
menubar.add(trackMenu);
// Range menu
@@ -450,7 +461,7 @@ public class MenuManager implements DataSubscriber
public void actionPerformed(ActionEvent e) {
Config.setConfigBoolean(Config.KEY_SHOW_MAP, _mapCheckbox.isSelected());
UpdateMessageBroker.informSubscribers(MAPSERVER_CHANGED);
- }
+ }
});
viewMenu.add(_mapCheckbox);
// Turn off the sidebars
@@ -508,12 +519,16 @@ public class MenuManager implements DataSubscriber
// Charts
_chartItem = makeMenuItem(FunctionLibrary.FUNCTION_CHARTS, false);
viewMenu.add(_chartItem);
+ viewMenu.addSeparator();
// Distances
_distanceItem = makeMenuItem(FunctionLibrary.FUNCTION_DISTANCES, false);
viewMenu.add(_distanceItem);
// full range details
_fullRangeDetailsItem = makeMenuItem(FunctionLibrary.FUNCTION_FULL_RANGE_DETAILS, false);
viewMenu.add(_fullRangeDetailsItem);
+ // estimate time
+ _estimateTimeItem = makeMenuItem(FunctionLibrary.FUNCTION_ESTIMATE_TIME, false);
+ viewMenu.add(_estimateTimeItem);
menubar.add(viewMenu);
// Add photo menu
@@ -613,8 +628,6 @@ public class MenuManager implements DataSubscriber
settingsMenu.add(_onlineCheckbox);
settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_DISK_CACHE));
settingsMenu.addSeparator();
- // Set kmz image size
- settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_KMZ_IMAGE_SIZE));
// Set program paths
settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_PATHS));
// Set colours
@@ -825,22 +838,25 @@ public class MenuManager implements DataSubscriber
*/
public void dataUpdated(byte inUpdateType)
{
- boolean hasData = (_track != null && _track.getNumPoints() > 0);
+ final boolean hasData = _track != null && _track.getNumPoints() > 0;
+ final boolean hasMultiplePoints = hasData && _track.getNumPoints() > 1;
+
// set functions which require data
_sendGpsItem.setEnabled(hasData);
_saveItem.setEnabled(hasData);
_saveButton.setEnabled(hasData);
_exportKmlItem.setEnabled(hasData);
_exportGpxItem.setEnabled(hasData);
- _exportPovItem.setEnabled(hasData);
- _exportSvgItem.setEnabled(hasData);
+ _exportPovItem.setEnabled(hasMultiplePoints);
+ _exportSvgItem.setEnabled(hasMultiplePoints);
+ _exportImageItem.setEnabled(hasMultiplePoints);
_compressItem.setEnabled(hasData);
_markRectangleItem.setEnabled(hasData);
_deleteMarkedPointsItem.setEnabled(hasData && _track.hasMarkedPoints());
_rearrangeMenu.setEnabled(hasData && _track.hasTrackPoints() && _track.hasWaypoints());
_selectAllItem.setEnabled(hasData);
_selectNoneItem.setEnabled(hasData);
- _show3dItem.setEnabled(hasData);
+ _show3dItem.setEnabled(hasMultiplePoints);
_chartItem.setEnabled(hasData);
_browserMapMenu.setEnabled(hasData);
_distanceItem.setEnabled(hasData);
@@ -850,6 +866,7 @@ public class MenuManager implements DataSubscriber
_lookupWikipediaItem.setEnabled(hasData);
_downloadOsmItem.setEnabled(hasData);
_findWaypointItem.setEnabled(hasData && _track.hasWaypoints());
+
// is undo available?
boolean hasUndo = !_app.getUndoStack().isEmpty();
_undoItem.setEnabled(hasUndo);
@@ -882,7 +899,7 @@ public class MenuManager implements DataSubscriber
_connectButton.setEnabled(connectAvailable);
_disconnectPhotoItem.setEnabled(hasPhoto && currentPhoto.getDataPoint() != null);
_correlatePhotosItem.setEnabled(anyPhotos && hasData);
- _rearrangePhotosItem.setEnabled(anyPhotos && hasData && _track.getNumPoints() > 1);
+ _rearrangePhotosItem.setEnabled(anyPhotos && hasMultiplePoints);
_removePhotoItem.setEnabled(hasPhoto);
_rotatePhotoLeft.setEnabled(hasPhoto);
_rotatePhotoRight.setEnabled(hasPhoto);
@@ -909,6 +926,9 @@ public class MenuManager implements DataSubscriber
_convertNamesToTimesItem.setEnabled(hasRange && _track.hasWaypoints());
_deleteFieldValuesItem.setEnabled(hasRange);
_fullRangeDetailsItem.setEnabled(hasRange);
+ _estimateTimeItem.setEnabled(hasRange);
+ _learnEstimationParams.setEnabled(hasData && _track.hasTrackPoints() && _track.hasData(Field.TIMESTAMP)
+ && _track.hasAltitudeData());
// Is the currently selected point outside the current range?
boolean canCutAndMove = hasRange && hasPoint &&
(_selection.getCurrentPointIndex() < _selection.getStart()
diff --git a/tim/prune/gui/ProgressDialog.java b/tim/prune/gui/ProgressDialog.java
new file mode 100644
index 0000000..d09087e
--- /dev/null
+++ b/tim/prune/gui/ProgressDialog.java
@@ -0,0 +1,115 @@
+package tim.prune.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+
+import tim.prune.I18nManager;
+
+/**
+ * Class to show a simple progress dialog
+ * similar to swing's ProgressMonitor but with a few
+ * modifications
+ */
+public class ProgressDialog
+{
+ /** Parent frame */
+ private JFrame _parentFrame = null;
+ /** Key for title text */
+ private String _titleKey = null;
+ /** function dialog */
+ private JDialog _dialog = null;
+ /** Progress bar for function */
+ private JProgressBar _progressBar = null;
+ /** Cancel flag */
+ private boolean _cancelled = false;
+
+
+ /**
+ * Constructor
+ * @param inParentFrame parent frame
+ * @param inNameKey key for title
+ */
+ public ProgressDialog(JFrame inParentFrame, String inNameKey)
+ {
+ _parentFrame = inParentFrame;
+ _titleKey = inNameKey;
+ }
+
+ public void show()
+ {
+ if (_dialog == null)
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(_titleKey), false);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ }
+ _progressBar.setMinimum(0);
+ _progressBar.setMaximum(100);
+ _progressBar.setValue(0);
+ _progressBar.setIndeterminate(true);
+ _cancelled = false;
+ _dialog.setVisible(true);
+ }
+
+ /**
+ * Make the dialog components
+ * @return the GUI components for the dialog
+ */
+ private Component makeDialogComponents()
+ {
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BorderLayout());
+ dialogPanel.add(new JLabel(I18nManager.getText("confirm.running")), BorderLayout.NORTH);
+ _progressBar = new JProgressBar();
+ _progressBar.setPreferredSize(new Dimension(250, 30));
+ dialogPanel.add(_progressBar, BorderLayout.CENTER);
+ // Cancel button at the bottom
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ _cancelled = true;
+ _dialog.dispose();
+ }
+ });
+ buttonPanel.add(cancelButton);
+ dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+ return dialogPanel;
+ }
+
+ /** Set the maximum value of the progress bar */
+ public void setMaximum(int inMax) {
+ _progressBar.setMaximum(inMax);
+ _progressBar.setIndeterminate(inMax <= 1);
+ }
+
+ /** Set the current value of the progress bar */
+ public void setValue(int inValue) {
+ _progressBar.setValue(inValue);
+ }
+
+ /** Close the dialog */
+ public void dispose() {
+ _dialog.dispose();
+ }
+
+ /**
+ * @return true if cancel button was pressed
+ */
+ public boolean isCancelled() {
+ return _cancelled;
+ }
+}
diff --git a/tim/prune/gui/StatusIcon.java b/tim/prune/gui/StatusIcon.java
new file mode 100644
index 0000000..26adb1b
--- /dev/null
+++ b/tim/prune/gui/StatusIcon.java
@@ -0,0 +1,68 @@
+package tim.prune.gui;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+
+/**
+ * Little status icon for green tick (valid) or red cross (not valid)
+ */
+public class StatusIcon extends JLabel
+{
+ /** Current status */
+ private Status _currStatus = Status.BLANK;
+
+ private static ImageIcon _blankIcon = IconManager.getImageIcon(IconManager.ENTRY_NONE);
+ private static ImageIcon _validIcon = IconManager.getImageIcon(IconManager.ENTRY_VALID);
+ private static ImageIcon _invalidIcon = IconManager.getImageIcon(IconManager.ENTRY_INVALID);
+
+ /**
+ * Three possible states for icon
+ */
+ private enum Status {
+ BLANK,
+ VALID,
+ INVALID
+ }
+
+ /**
+ * Constructor
+ */
+ public StatusIcon()
+ {
+ super(_blankIcon);
+ _currStatus = Status.BLANK;
+ }
+
+ /**
+ * Set the status to blank
+ */
+ public void setStatusBlank()
+ {
+ if (_currStatus != Status.BLANK) {
+ setIcon(_blankIcon);
+ _currStatus = Status.BLANK;
+ }
+ }
+
+ /**
+ * Set the status to valid
+ */
+ public void setStatusValid()
+ {
+ if (_currStatus != Status.VALID) {
+ setIcon(_validIcon);
+ _currStatus = Status.VALID;
+ }
+ }
+
+ /**
+ * Set the status to not valid
+ */
+ public void setStatusInvalid()
+ {
+ if (_currStatus != Status.INVALID) {
+ setIcon(_invalidIcon);
+ _currStatus = Status.INVALID;
+ }
+ }
+}
diff --git a/tim/prune/gui/WholeNumberField.java b/tim/prune/gui/WholeNumberField.java
index 09f676d..ba953fc 100644
--- a/tim/prune/gui/WholeNumberField.java
+++ b/tim/prune/gui/WholeNumberField.java
@@ -57,7 +57,7 @@ public class WholeNumberField extends JTextField
*/
public WholeNumberField(int inMaxDigits)
{
- super("0");
+ super(inMaxDigits);
setDocument(new WholeNumberDocument(inMaxDigits));
}
diff --git a/tim/prune/gui/WizardLayout.java b/tim/prune/gui/WizardLayout.java
new file mode 100644
index 0000000..1a5a8f1
--- /dev/null
+++ b/tim/prune/gui/WizardLayout.java
@@ -0,0 +1,102 @@
+package tim.prune.gui;
+
+import java.awt.CardLayout;
+import java.awt.Component;
+import javax.swing.JPanel;
+
+/**
+ * Layout class enhancing the regular card layout to add the ability to
+ * see which is the current card, how many cards there are, previous / next etc
+ */
+public class WizardLayout extends CardLayout
+{
+ private JPanel _panel = null;
+ private int _currentCard = 0;
+ private int _numCards = 0;
+
+ /**
+ * Constructor
+ * @param inPanel panel controlled by this layout
+ */
+ public WizardLayout(JPanel inPanel)
+ {
+ super();
+ _panel = inPanel;
+ _panel.setLayout(this);
+ }
+
+ /**
+ * Add a card to this layout
+ * @param inCard
+ */
+ public void addCard(Component inCard)
+ {
+ _panel.add(inCard, "card" + _numCards);
+ _numCards++;
+ }
+
+ /**
+ * @return current card index (from 0)
+ */
+ public int getCurrentCardIndex() {
+ return _currentCard;
+ }
+
+ /**
+ * Go to the first card
+ */
+ public void showFirstCard()
+ {
+ first(_panel);
+ _currentCard = 0;
+ }
+
+ /**
+ * Go to the next card
+ */
+ public void showNextCard()
+ {
+ if (_currentCard < (_numCards-1))
+ {
+ next(_panel);
+ _currentCard++;
+ }
+ }
+
+ /**
+ * Go to the previous card
+ */
+ public void showPreviousCard()
+ {
+ if (_currentCard > 0)
+ {
+ previous(_panel);
+ _currentCard--;
+ }
+ }
+
+ /**
+ * @return true if this is the first card
+ */
+ public boolean isFirstCard() {
+ return _currentCard == 0;
+ }
+
+ /**
+ * @return true if this is the last card
+ */
+ public boolean isLastCard() {
+ return _currentCard == (_numCards-1);
+ }
+
+ /**
+ * @param inIndex index (from 0) of the card to show
+ */
+ public void showCard(int inIndex)
+ {
+ if (inIndex >= 0 && inIndex < _numCards) {
+ show(_panel, "card" + inIndex);
+ _currentCard = inIndex;
+ }
+ }
+}
diff --git a/tim/prune/gui/images/add_photo_icon.png b/tim/prune/gui/images/add_photo_icon.png
old mode 100755
new mode 100644
diff --git a/tim/prune/gui/images/add_textfile_icon.png b/tim/prune/gui/images/add_textfile_icon.png
old mode 100755
new mode 100644
diff --git a/tim/prune/gui/images/entry_invalid.gif b/tim/prune/gui/images/entry_invalid.gif
new file mode 100644
index 0000000000000000000000000000000000000000..8612eaff5994f2b70514365e717d6ce1597020c2
GIT binary patch
literal 84
zcmZ?wbhEHbWM|-DSj52apMhb&y?wpC{r~#<{qgbl_wWA?28usf7#SGY8FUzc0Hl_I
iSu$edYO{nHswagRBbYXr$%Mro6!z^}z~;)tU=0A^iW{c@
literal 0
HcmV?d00001
diff --git a/tim/prune/gui/images/entry_none.gif b/tim/prune/gui/images/entry_none.gif
new file mode 100644
index 0000000000000000000000000000000000000000..720eeec96fd325ccc057c756b2d32fdebbc02e48
GIT binary patch
literal 67
zcmZ?wbhEHbWM|-DIK;%j5O2qj7yJMJ|JwaEKoSlVf3h$#FfcRdFaQBaEdw)$#jZR5
L3{FjCV6X-NvS%8H
literal 0
HcmV?d00001
diff --git a/tim/prune/gui/images/entry_valid.gif b/tim/prune/gui/images/entry_valid.gif
new file mode 100644
index 0000000000000000000000000000000000000000..9ca8275e6263512bdba8a0ed8313042972a1a052
GIT binary patch
literal 78
zcmZ?wbhEHbWM|-DSj57>5O2qj7yJMJ|JwaEK#~Cl6o0ZXGBB_(=zs)3Y8jY?J$7x<
WI($&!mP}#{huijf$2z>27_0$JWEGhJ
literal 0
HcmV?d00001
diff --git a/tim/prune/gui/map/MapCanvas.java b/tim/prune/gui/map/MapCanvas.java
index 96ff223..a6d5a88 100644
--- a/tim/prune/gui/map/MapCanvas.java
+++ b/tim/prune/gui/map/MapCanvas.java
@@ -390,25 +390,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
int selectedPoint = _selection.getCurrentPointIndex();
if (selectedPoint >= 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
{
- int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(selectedPoint));
- int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(selectedPoint));
- int panX = 0;
- int panY = 0;
- if (px < PAN_DISTANCE) {
- panX = px - AUTOPAN_DISTANCE;
- }
- else if (px > (getWidth()-PAN_DISTANCE)) {
- panX = AUTOPAN_DISTANCE + px - getWidth();
- }
- if (py < PAN_DISTANCE) {
- panY = py - AUTOPAN_DISTANCE;
- }
- if (py > (getHeight()-PAN_DISTANCE)) {
- panY = AUTOPAN_DISTANCE + py - getHeight();
- }
- if (panX != 0 || panY != 0) {
- _mapPosition.pan(panX, panY);
- }
+ autopanToPoint(selectedPoint);
}
_prevSelectedPoint = selectedPoint;
}
@@ -446,7 +428,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
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));
@@ -469,6 +451,45 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
paintChildren(inG);
}
+ /**
+ * @return true if the currently selected point is visible, false if off-screen or nothing selected
+ */
+ private boolean isCurrentPointVisible()
+ {
+ if (_trackInfo.getCurrentPoint() == null) {return false;}
+ final int selectedPoint = _selection.getCurrentPointIndex();
+ final int xFromCentre = Math.abs(_mapPosition.getXFromCentre(_track.getX(selectedPoint)));
+ if (xFromCentre > (getWidth()/2)) {return false;}
+ final int yFromCentre = Math.abs(_mapPosition.getYFromCentre(_track.getY(selectedPoint)));
+ return yFromCentre < (getHeight()/2);
+ }
+
+ /**
+ * If the specified point isn't visible, pan to it
+ * @param inIndex index of selected point
+ */
+ private void autopanToPoint(int inIndex)
+ {
+ int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(inIndex));
+ int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(inIndex));
+ int panX = 0;
+ int panY = 0;
+ if (px < PAN_DISTANCE) {
+ panX = px - AUTOPAN_DISTANCE;
+ }
+ else if (px > (getWidth()-PAN_DISTANCE)) {
+ panX = AUTOPAN_DISTANCE + px - getWidth();
+ }
+ if (py < PAN_DISTANCE) {
+ panY = py - AUTOPAN_DISTANCE;
+ }
+ if (py > (getHeight()-PAN_DISTANCE)) {
+ panY = AUTOPAN_DISTANCE + py - getHeight();
+ }
+ if (panX != 0 || panY != 0) {
+ _mapPosition.pan(panX, panY);
+ }
+ }
/**
* Paint the map tiles and the points on to the _mapImage
@@ -550,8 +571,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
{
pointsPainted = paintPoints(g);
}
- catch (NullPointerException npe) { // ignore, probably due to data being changed during drawing
- }
+ catch (NullPointerException npe) {} // ignore, probably due to data being changed during drawing
+ catch (ArrayIndexOutOfBoundsException obe) {} // also ignore
// free g
g.dispose();
@@ -658,6 +679,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
int nameHeight = fm.getHeight();
if (anyWaypoints)
{
+ int numWaypoints = 0;
for (int i=0; i<_track.getNumPoints(); i++)
{
if (_track.getPoint(i).isWaypoint())
@@ -668,11 +690,18 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
{
inG.fillRect(px-3, py-3, 6, 6);
pointsPainted++;
+ numWaypoints++;
}
}
}
+ // Take more care with waypoint names if less than 100 are visible
+ final int numNameSteps = (numWaypoints > 100 ? 1 : 4);
+ final int numPointSteps = (numWaypoints > 1000 ? 2 : 1);
+
// Loop over points again, now draw names for waypoints
- for (int i=0; i<_track.getNumPoints(); i++)
+ int[] nameXs = {0, 0, 0, 0};
+ int[] nameYs = {0, 0, 0, 0};
+ for (int i=0; i<_track.getNumPoints(); i += numPointSteps)
{
if (_track.getPoint(i).isWaypoint())
{
@@ -685,19 +714,21 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
int nameWidth = fm.stringWidth(waypointName);
boolean drawnName = false;
// Make arrays for coordinates right left up down
- int[] nameXs = {px + 2, px - nameWidth - 2, px - nameWidth/2, px - nameWidth/2};
- int[] nameYs = {py + (nameHeight/2), py + (nameHeight/2), py - 2, py + nameHeight + 2};
- for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
+ nameXs[0] = px + 2; nameXs[1] = px - nameWidth - 2;
+ nameXs[2] = nameXs[3] = px - nameWidth/2;
+ nameYs[0] = nameYs[1] = py + (nameHeight/2);
+ nameYs[2] = py - 2; nameYs[3] = py + nameHeight + 2;
+ for (int extraSpace = 0; extraSpace < numNameSteps && !drawnName; extraSpace++)
{
// Shift arrays for coordinates right left up down
- nameXs[0] += 2; nameXs[1] -= 2;
- nameYs[2] -= 2; nameYs[3] += 2;
+ nameXs[0] += 3; nameXs[1] -= 3;
+ nameYs[2] -= 3; nameYs[3] += 3;
// Check each direction in turn right left up down
for (int a=0; a<4; a++)
{
if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < winWidth
&& nameYs[a] < winHeight && (nameYs[a] - nameHeight) > 0
- && !overlapsPoints(nameXs[a], nameYs[a], nameWidth, nameHeight, textColour))
+ && !MapUtils.overlapsPoints(_mapImage, nameXs[a], nameYs[a], nameWidth, nameHeight, textColour))
{
// Found a rectangle to fit - draw name here and quit
inG.drawString(waypointName, nameXs[a], nameYs[a]);
@@ -813,50 +844,6 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
}
}
- /**
- * Tests whether there are any dark pixels within the specified x,y rectangle
- * @param inX left X coordinate
- * @param inY bottom Y coordinate
- * @param inWidth width of rectangle
- * @param inHeight height of rectangle
- * @param inTextColour colour of text
- * @return true if the rectangle overlaps stuff too close to the given colour
- */
- private boolean overlapsPoints(int inX, int inY, int inWidth, int inHeight, Color inTextColour)
- {
- // each of the colour channels must be further away than this to count as empty
- final int BRIGHTNESS_LIMIT = 80;
- final int textRGB = inTextColour.getRGB();
- final int textLow = textRGB & 255;
- final int textMid = (textRGB >> 8) & 255;
- final int textHigh = (textRGB >> 16) & 255;
- try
- {
- // loop over x coordinate of rectangle
- for (int x=0; x> 8) & 255;
- int pixHigh = (pixelColor >> 16) & 255;
- //int fourthBit = (pixelColor >> 24) & 255; // alpha ignored
- // If colours are too close in any channel then it's an overlap
- if (Math.abs(pixLow-textLow) < BRIGHTNESS_LIMIT ||
- Math.abs(pixMid-textMid) < BRIGHTNESS_LIMIT ||
- Math.abs(pixHigh-textHigh) < BRIGHTNESS_LIMIT) {return true;}
- }
- }
- }
- catch (NullPointerException e) {
- // ignore null pointers, just return false
- }
- return false;
- }
-
/**
* Make a semi-transparent colour for drawing with
* @param inColour base colour (fully opaque)
@@ -906,7 +893,12 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
*/
public void zoomIn()
{
+ // See if selected point is currently visible, if so (and autopan on) then autopan after zoom to keep it visible
+ boolean wasVisible = _autopanCheckBox.isSelected() && isCurrentPointVisible();
_mapPosition.zoomIn();
+ if (wasVisible && !isCurrentPointVisible()) {
+ autopanToPoint(_selection.getCurrentPointIndex());
+ }
_recalculate = true;
repaint();
}
diff --git a/tim/prune/gui/map/MapUtils.java b/tim/prune/gui/map/MapUtils.java
index 78206a3..49d9467 100644
--- a/tim/prune/gui/map/MapUtils.java
+++ b/tim/prune/gui/map/MapUtils.java
@@ -1,7 +1,10 @@
package tim.prune.gui.map;
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+
/**
- * Class to manage coordinate conversions for maps
+ * Class to manage coordinate conversions and other stuff for maps
*/
public abstract class MapUtils
{
@@ -49,4 +52,50 @@ public abstract class MapUtils
double n = Math.PI * (1 - 2 * inY);
return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
}
+
+ /**
+ * Tests whether there are any dark pixels in the image within the specified x,y rectangle
+ * @param inImage image to test
+ * @param inX left X coordinate
+ * @param inY bottom Y coordinate
+ * @param inWidth width of rectangle
+ * @param inHeight height of rectangle
+ * @param inTextColour colour of text
+ * @return true if the rectangle overlaps stuff too close to the given colour
+ */
+ public static boolean overlapsPoints(BufferedImage inImage, int inX, int inY,
+ int inWidth, int inHeight, Color inTextColour)
+ {
+ // each of the colour channels must be further away than this to count as empty
+ final int BRIGHTNESS_LIMIT = 80;
+ final int textRGB = inTextColour.getRGB();
+ final int textLow = textRGB & 255;
+ final int textMid = (textRGB >> 8) & 255;
+ final int textHigh = (textRGB >> 16) & 255;
+ try
+ {
+ // loop over x coordinate of rectangle
+ for (int x=0; x> 8) & 255;
+ int pixHigh = (pixelColor >> 16) & 255;
+ //int fourthBit = (pixelColor >> 24) & 255; // alpha ignored
+ // If colours are too close in any channel then it's an overlap
+ if (Math.abs(pixLow-textLow) < BRIGHTNESS_LIMIT ||
+ Math.abs(pixMid-textMid) < BRIGHTNESS_LIMIT ||
+ Math.abs(pixHigh-textHigh) < BRIGHTNESS_LIMIT) {return true;}
+ }
+ }
+ }
+ catch (NullPointerException e) {
+ // ignore null pointers, just return false
+ }
+ return false;
+ }
}
diff --git a/tim/prune/gui/profile/AltitudeData.java b/tim/prune/gui/profile/AltitudeData.java
index 9c19862..9600b16 100644
--- a/tim/prune/gui/profile/AltitudeData.java
+++ b/tim/prune/gui/profile/AltitudeData.java
@@ -30,9 +30,9 @@ public class AltitudeData extends ProfileData
final double multFactor = _unitSet.getAltitudeUnit().getMultFactorFromStd();
if (_track != null)
{
- for (int i=0; i<_track.getNumPoints(); i++)
+ try
{
- try
+ for (int i=0; i<_track.getNumPoints(); i++)
{
DataPoint point = _track.getPoint(i);
if (point != null && point.hasAltitude())
@@ -43,15 +43,16 @@ public class AltitudeData extends ProfileData
if (value < _minValue || !_hasData) {_minValue = value;}
if (value > _maxValue || !_hasData) {_maxValue = value;}
- _hasData = true;
+ // if all values are zero then that's no data
+ _hasData = _hasData || (point.getAltitude().getValue() != 0);
_pointHasData[i] = true;
}
else _pointHasData[i] = false;
}
- catch (ArrayIndexOutOfBoundsException obe)
- {} // must be due to the track size changing during calculation
- // assume that a redraw will be triggered
}
+ catch (ArrayIndexOutOfBoundsException obe)
+ {} // must be due to the track size changing during calculation
+ // assume that a redraw will be triggered
}
}
diff --git a/tim/prune/gui/profile/ArbitraryData.java b/tim/prune/gui/profile/ArbitraryData.java
new file mode 100644
index 0000000..cf974a5
--- /dev/null
+++ b/tim/prune/gui/profile/ArbitraryData.java
@@ -0,0 +1,79 @@
+package tim.prune.gui.profile;
+
+import tim.prune.data.Field;
+import tim.prune.data.Track;
+import tim.prune.data.UnitSet;
+
+/**
+ * Class to provide a source of values for the profile chart
+ * using any arbitary (non-built-in) field, units unknown
+ */
+public class ArbitraryData extends ProfileData
+{
+ /** Field to use */
+ private Field _field = null;
+
+ /**
+ * Constructor
+ * @param inTrack track object
+ * @param inField field to use
+ */
+ public ArbitraryData(Track inTrack, Field inField)
+ {
+ super(inTrack);
+ _field = inField;
+ }
+
+ /**
+ * Get the data and populate the instance arrays
+ */
+ public void init(UnitSet inUnitSet)
+ {
+ setUnitSet(inUnitSet);
+ initArrays();
+ _hasData = false;
+ _minValue = _maxValue = 0.0;
+ if (_track != null)
+ {
+ for (int i=0; i<_track.getNumPoints(); i++)
+ {
+ // Get the value of the given field
+ boolean hasValue = false;
+ String value = _track.getPoint(i).getFieldValue(_field);
+ try
+ {
+ double dValue = Double.parseDouble(value);
+ _pointValues[i] = dValue;
+ if (dValue < _minValue || _minValue == 0.0) {_minValue = dValue;}
+ if (dValue > _maxValue) {_maxValue = dValue;}
+ hasValue = true;
+ _hasData = true;
+ }
+ catch (Exception e) {} // ignore nulls and non-numbers
+ _pointHasData[i] = hasValue;
+ }
+ }
+ }
+
+ /**
+ * @return name of field
+ */
+ public String getLabel()
+ {
+ return _field.getName();
+ }
+
+ /**
+ * @return the field object
+ */
+ public Field getField() {
+ return _field;
+ }
+
+ /**
+ * @return key for message when no values present
+ */
+ public String getNoDataKey() {
+ return "display.novalues";
+ }
+}
diff --git a/tim/prune/gui/profile/ProfileChart.java b/tim/prune/gui/profile/ProfileChart.java
index 770a211..732669a 100644
--- a/tim/prune/gui/profile/ProfileChart.java
+++ b/tim/prune/gui/profile/ProfileChart.java
@@ -15,6 +15,8 @@ import javax.swing.JPopupMenu;
import tim.prune.I18nManager;
import tim.prune.config.ColourScheme;
import tim.prune.config.Config;
+import tim.prune.data.Field;
+import tim.prune.data.FieldList;
import tim.prune.data.TrackInfo;
import tim.prune.gui.GenericDisplay;
@@ -23,6 +25,17 @@ import tim.prune.gui.GenericDisplay;
*/
public class ProfileChart extends GenericDisplay implements MouseListener
{
+ /** Inner class to handle popup menu clicks */
+ class MenuClicker implements ActionListener
+ {
+ private Field _field = null;
+ MenuClicker(Field inField) {_field = inField;}
+ /** React to menu click by changing the field */
+ public void actionPerformed(ActionEvent arg0) {
+ changeView(_field);
+ }
+ }
+
/** Current scale factor in x direction*/
private double _xScaleFactor = 0.0;
/** Data to show on chart */
@@ -40,8 +53,6 @@ public class ProfileChart extends GenericDisplay implements MouseListener
private static final Dimension MINIMUM_SIZE = new Dimension(200, 110);
/** Colour to use for text if no data found */
private static final Color COLOR_NODATA_TEXT = Color.GRAY;
- /** Chart type */
- private static enum ChartType {ALTITUDE, SPEED, VERT_SPEED};
/**
@@ -255,23 +266,38 @@ public class ProfileChart extends GenericDisplay implements MouseListener
altItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
- changeView(ChartType.ALTITUDE);
+ changeView(Field.ALTITUDE);
}});
_popup.add(altItem);
JMenuItem speedItem = new JMenuItem(I18nManager.getText("fieldname.speed"));
speedItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
- changeView(ChartType.SPEED);
+ changeView(Field.SPEED);
}});
_popup.add(speedItem);
JMenuItem vertSpeedItem = new JMenuItem(I18nManager.getText("fieldname.verticalspeed"));
vertSpeedItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
- changeView(ChartType.VERT_SPEED);
+ changeView(Field.VERTICAL_SPEED);
}});
_popup.add(vertSpeedItem);
+ // Go through track's master field list, see if any other fields to list
+ boolean addSeparator = true;
+ FieldList fields = _track.getFieldList();
+ for (int i=0; i 0) {
+ makePopup();
+ }
repaint();
}
@@ -346,19 +376,31 @@ public class ProfileChart extends GenericDisplay implements MouseListener
/**
* Called by clicking on popup menu to change the view
- * @param inType selected chart type
+ * @param inField field to show
*/
- private void changeView(ChartType inType)
+ private void changeView(Field inField)
{
- if (inType == ChartType.ALTITUDE && !(_data instanceof AltitudeData))
+ if (inField == Field.ALTITUDE)
{
- _data = new AltitudeData(_track);
+ if (!(_data instanceof AltitudeData)) {
+ _data = new AltitudeData(_track);
+ }
}
- else if (inType == ChartType.SPEED && !(_data instanceof SpeedData)) {
- _data = new SpeedData(_track);
+ else if (inField == Field.SPEED) {
+ if (!(_data instanceof SpeedData)) {
+ _data = new SpeedData(_track);
+ }
}
- else if (inType == ChartType.VERT_SPEED && !(_data instanceof VerticalSpeedData)) {
- _data = new VerticalSpeedData(_track);
+ else if (inField == Field.VERTICAL_SPEED) {
+ if (!(_data instanceof VerticalSpeedData)) {
+ _data = new VerticalSpeedData(_track);
+ }
+ }
+ else
+ {
+ if (!(_data instanceof ArbitraryData) || ((ArbitraryData)_data).getField() != inField) {
+ _data = new ArbitraryData(_track, inField);
+ }
}
_data.init(Config.getUnitSet());
repaint();
diff --git a/tim/prune/lang/prune-texts_af.properties b/tim/prune/lang/prune-texts_af.properties
index 2d0e54a..09d72de 100644
--- a/tim/prune/lang/prune-texts_af.properties
+++ b/tim/prune/lang/prune-texts_af.properties
@@ -95,7 +95,6 @@ function.show3d=3D Vertoon
function.distances=Afstande
function.fullrangedetails=Vol reeks besonderhede
function.setmapbg=Stel Kaart agtergrond
-function.setkmzimagesize=Stel KMZ beeld groote
function.setpaths=Stel program paaie
function.getgpsies=Kry GPS spore
function.lookupsrtm=Kry hoogtes vanaf SRTM
@@ -188,7 +187,7 @@ dialog.exportpov.cameraz=Kamera Z
dialog.exportpov.modelstyle=Model styl
dialog.exportpov.ballsandsticks=Balle en stokkies
dialog.exportpov.tubesandwalls=Buise en mure
-dialog.exportpov.warningtracksize=Hierdie spoor het 'n groot aantal punte, wat Java3D miskien nie kan vertoon.\nIs jy seker jy wil voortgaan?
+dialog.3d.warningtracksize=Hierdie spoor het 'n groot aantal punte, wat Java3D miskien nie kan vertoon.\nIs jy seker jy wil voortgaan?
dialog.exportsvg.text=Selekteer die parameters vir die SVG uitvoer
dialog.exportsvg.phi=Azimuth hoek \u03d5
dialog.exportsvg.theta=Opstandings angle \u03b8
diff --git a/tim/prune/lang/prune-texts_cz.properties b/tim/prune/lang/prune-texts_cz.properties
index 6c5a9db..b922f3b 100644
--- a/tim/prune/lang/prune-texts_cz.properties
+++ b/tim/prune/lang/prune-texts_cz.properties
@@ -7,7 +7,7 @@ menu.file.addphotos=P\u0159idat fotografie
menu.file.recentfiles=Naposledy otev\u0159en\u00e9
menu.file.save=Ulo\u017eit jako text
menu.file.exit=Konec
-menu.track=Trasa
+menu.track=Stopa
menu.track.undo=Undo
menu.track.clearundo=Vypr\u00e1zdnit pam\u011b\u0165 undo
menu.track.markrectangle=Ozna\u010dit body v obd\u00e9ln\u00edku
@@ -15,7 +15,7 @@ menu.track.deletemarked=Smazat ozna\u010den\u00e9 body
menu.track.rearrange=P\u0159euspo\u0159\u00e1dat z\u00e1jmov\u00e9 body
menu.track.rearrange.start=V\u0161e na po\u010d\u00e1tek
menu.track.rearrange.end=V\u0161e na konec
-menu.track.rearrange.nearest=Zarovnat body na trasu
+menu.track.rearrange.nearest=Zarovnat body na stopu
menu.range=Rozmez\u00ed
menu.range.all=Vybrat v\u0161e
menu.range.none=Zru\u0161it v\u00fdb\u011br
@@ -23,7 +23,7 @@ menu.range.start=Nastavit za\u010d\u00e1tek rozmez\u00ed
menu.range.end=Nastavit konec rozmez\u00ed
menu.range.average=St\u0159ed z v\u00fdb\u011bru
menu.range.reverse=Obr\u00e1tit rozmez\u00ed
-menu.range.mergetracksegments=Slou\u010dit \u010d\u00e1sti trasy
+menu.range.mergetracksegments=Slou\u010dit \u010d\u00e1sti stopy
menu.range.cutandmove=P\u0159en\u00e9st v\u00fdb\u011br
menu.point=Bod
menu.point.editpoint=Upravit bod
@@ -49,7 +49,7 @@ menu.map.zoomout=Odd\u00e1lit
menu.map.zoomfull=\u00dapln\u011b odd\u00e1lit
menu.map.newpoint=Vytvo\u0159it nov\u00fd bod
menu.map.drawpoints=Vytvo\u0159it n\u011bkolik bod\u016f
-menu.map.connect=Propojit body trasy
+menu.map.connect=Propojit body stopy
menu.map.autopan=Automatika zorn\u00e9ho pole
menu.map.showmap=Zobrazit mapu
menu.map.showscalebar=Zobrazit m\u011b\u0159\u00edtko
@@ -84,10 +84,11 @@ function.exportkml=Export KML
function.exportgpx=Export GPX
function.exportpov=Export POV
function.exportsvg=Export SVG
+function.exportimage=Export obrazu mapy
function.editwaypointname=Nastavit n\u00e1zev v\u00fdzna\u010dn\u00e9ho bodu
-function.compress=Komprimovat trasu
+function.compress=Komprimovat stopu
function.deleterange=Smazat rozmez\u00ed
-function.croptrack=O\u0159\u00edznout trasu
+function.croptrack=O\u0159\u00edznout stopu
function.interpolate=Interpolovat body
function.addtimeoffset=P\u0159idat \u010dasov\u00fd posun
function.addaltitudeoffset=P\u0159idat v\u00fd\u0161kov\u00fd posun
@@ -99,11 +100,12 @@ function.charts=Grafy
function.show3d=Trojrozm\u011brn\u011b
function.distances=Vzd\u00e1lenosti
function.fullrangedetails=Detaily rozmez\u00ed
+function.estimatetime=Odhad \u010dasu
+function.learnestimationparams=Anal\u00fdza stopy pro odhad \u010dasu
function.setmapbg=Nastavit pozad\u00ed
-function.setkmzimagesize=Nastavit velikost exportu KMZ
function.setpaths=Nastavit cestu k program\u016fm
-function.getgpsies=St\u00e1hnout trasy z Gpsies
-function.uploadgpsies=Nahr\u00e1t trasu na Gpsies
+function.getgpsies=St\u00e1hnout stopy z Gpsies
+function.uploadgpsies=Nahr\u00e1t stopu na Gpsies
function.lookupsrtm=Na\u010d\u00edst nadm. v\u00fd\u0161ku ze SRTM
function.getwikipedia=Hledat na Wikipedii podle vzd\u00e1lenosti
function.searchwikipedianames=Hledat na Wikipedii podle jm\u00e9na
@@ -159,8 +161,12 @@ dialog.openoptions.deliminfo.records=z\u00e1znam\u016f, s
dialog.openoptions.deliminfo.fields=poli
dialog.openoptions.deliminfo.norecords=\u017d\u00e1dn\u00e9 z\u00e1znamy
dialog.openoptions.altitudeunits=Jednotky v\u00fd\u0161ky
-dialog.open.contentsdoubled=Tento soubor obsahuje dv\u011b kopie ka\u017ed\u00e9ho bodu,\nv\u017edy jednou jako body trasy a jednou jako v\u00fdzna\u010dn\u00e9 body.
-dialog.selecttracks.intro=Vyberte trasu nebo trasy k na\u010dten\u00ed
+dialog.openoptions.speedunits=Jednotky rychlosti
+dialog.openoptions.vertspeedunits=Jednotky vertik\u00e1ln\u00ed rychlosti
+dialog.openoptions.vspeed.positiveup=Kladn\u00e1 rychlost znamen\u00e1 stoup\u00e1n\u00ed
+dialog.openoptions.vspeed.positivedown=Kladn\u00e1 rychlost znamen\u00e1 kles\u00e1n\u00ed
+dialog.open.contentsdoubled=Tento soubor obsahuje dv\u011b kopie ka\u017ed\u00e9ho bodu,\nv\u017edy jednou jako body stopy a jednou jako v\u00fdzna\u010dn\u00e9 body.
+dialog.selecttracks.intro=Vyberte stopu nebo stopy k na\u010dten\u00ed
dialog.selecttracks.noname=Bez n\u00e1zvu
dialog.jpegload.subdirectories=V\u010detn\u011b podadres\u00e1\u0159\u016f
dialog.jpegload.loadjpegswithoutcoords=V\u010detn\u011b fotografi\u00ed bez sou\u0159adnic
@@ -171,11 +177,35 @@ dialog.gpsload.nogpsbabel=Nenalezen program gpsbabel. Pokra\u010dovat?
dialog.gpsload.device=Ozna\u010den\u00ed za\u0159\u00edzen\u00ed
dialog.gpsload.format=Form\u00e1t
dialog.gpsload.getwaypoints=Na\u010d\u00edst v\u00fdzna\u010dn\u00e9 body
-dialog.gpsload.gettracks=Na\u010d\u00edst trasy
+dialog.gpsload.gettracks=Na\u010d\u00edst stopy
dialog.gpsload.save=Ulo\u017eit do souboru
dialog.gpssend.sendwaypoints=Poslat bod
-dialog.gpssend.sendtracks=Poslat trasy
-dialog.gpssend.trackname=N\u00e1zev trasy
+dialog.gpssend.sendtracks=Poslat stopy
+dialog.gpssend.trackname=N\u00e1zev stopy
+dialog.gpsbabel.filters=Filtry
+dialog.addfilter.title=P\u0159idat filtr
+dialog.gpsbabel.filter.discard=Vynech\u00e1n\u00ed
+dialog.gpsbabel.filter.simplify=Zjednodu\u0161en\u00ed
+dialog.gpsbabel.filter.distance=Vzd\u00e1lenost
+dialog.gpsbabel.filter.interpolate=Interpolace
+dialog.gpsbabel.filter.discard.intro=Body vynechat, kdy\u017e
+dialog.gpsbabel.filter.discard.hdop=hdop >
+dialog.gpsbabel.filter.discard.vdop=vdop >
+dialog.gpsbabel.filter.discard.numsats=po\u010det satelit\u016f <
+dialog.gpsbabel.filter.discard.nofix=bod nem\u00e1 fix
+dialog.gpsbabel.filter.discard.unknownfix=fix bodu je nezn\u00e1m\u00fd
+dialog.gpsbabel.filter.simplify.intro=Odstra\u0148ovat body dokud
+dialog.gpsbabel.filter.simplify.maxpoints=po\u010det bod\u016f <
+dialog.gpsbabel.filter.simplify.maxerror=nebo max. odchylka <
+dialog.gpsbabel.filter.simplify.crosstrack=nap\u0159\u00ed\u010d stopami
+dialog.gpsbabel.filter.simplify.length=rozd\u00edl d\u00e9lek
+dialog.gpsbabel.filter.simplify.relative=relativn\u011b k hdop
+dialog.gpsbabel.filter.distance.intro=Smazat body
+dialog.gpsbabel.filter.distance.distance=pokud vzd\u00e1lenost <
+dialog.gpsbabel.filter.distance.time=a \u010dasov\u00fd rozd\u00edl <
+dialog.gpsbabel.filter.interpolate.intro=P\u0159idat dal\u0161\u00ed body mezi
+dialog.gpsbabel.filter.interpolate.distance=pokud vzd\u00e1lenost >
+dialog.gpsbabel.filter.interpolate.time=nebo \u010dasov\u00fd rozd\u00edl >
dialog.saveoptions.title=Ulo\u017eit soubor
dialog.save.fieldstosave=Ulo\u017eit pole
dialog.save.table.field=Pole
@@ -192,7 +222,10 @@ dialog.exportkml.text=Nadpis dat
dialog.exportkml.altitude=V\u00fd\u0161ka nad hladinou mo\u0159e (pro letectv\u00ed)
dialog.exportkml.kmz=Komprimovat do souboru kmz
dialog.exportkml.exportimages=Vlo\u017eit n\u00e1hledy fotografi\u00ed
-dialog.exportkml.trackcolour=Barva trasy
+dialog.exportkml.imagesize=Velikost obr\u00e1zku
+dialog.exportkml.trackcolour=Barva stopy
+dialog.exportkml.standardkml=Standardn\u00ed KML
+dialog.exportkml.extendedkml=Roz\u0161\u00ed\u0159en\u00e9 KML s \u010dasov\u00fdmi zna\u010dkami
dialog.exportgpx.name=N\u00e1zev
dialog.exportgpx.desc=Popis
dialog.exportgpx.includetimestamps=Ulo\u017eit \u010dasov\u00e9 zna\u010dky
@@ -208,23 +241,35 @@ dialog.exportpov.cameraz=Kamera Z
dialog.exportpov.modelstyle=Model
dialog.exportpov.ballsandsticks=Koule a ty\u010dky
dialog.exportpov.tubesandwalls=Roury a st\u011bny
-dialog.exportpov.warningtracksize=Tato trasa sest\u00e1v\u00e1 z mnoha bod\u016f, kter\u00e9 mo\u017en\u00e1 Java3D nebude um\u011bt zobrazit.\nOpravdu chcete pokra\u010dovat?
+dialog.3d.warningtracksize=Tato stopa sest\u00e1v\u00e1 z mnoha bod\u016f, kter\u00e9 mo\u017en\u00e1 Java3D nebude um\u011bt zobrazit.\nOpravdu chcete pokra\u010dovat?
+dialog.exportpov.baseimage=Obr\u00e1zek jako podklad
+dialog.exportpov.cannotmakebaseimage=Nelze zapsat podklad
+dialog.baseimage.title=Podklad
+dialog.baseimage.useimage=Pou\u017e\u00edt obr\u00e1zek
+dialog.baseimage.mapsource=Zdroj mapy
+dialog.baseimage.zoom=Zv\u011bt\u0161en\u00ed
+dialog.baseimage.incomplete=Obr\u00e1zek ne\u00fapln\u00fd
+dialog.baseimage.tiles=Dla\u017edic
+dialog.baseimage.size=Velikost obr\u00e1zku
dialog.exportsvg.text=Zvolte parametry exportu do SVG
dialog.exportsvg.phi=Azimut \u03d5
dialog.exportsvg.theta=V\u00fd\u0161kov\u00fd \u00fahel \u03b8
dialog.exportsvg.gradients=Vypl\u0148ovat body barevn\u00fdm p\u0159echodem
+dialog.exportimage.noimagepossible=Aby bylo mo\u017en\u00e9 mapu ulo\u017eit jako obr\u00e1zek, je t\u0159eba st\u00e1hnout a ulo\u017eit dla\u017edice
+dialog.exportimage.drawtrack=Nakreslit stopu na mapu
+dialog.exportimage.textscalepercent=Zv\u011bt\u0161en\u00ed fontu (%)
dialog.pointtype.desc=Ulo\u017eit body n\u00e1sleduj\u00edc\u00edch typ\u016f:
-dialog.pointtype.track=Body trasy
+dialog.pointtype.track=Body stopy
dialog.pointtype.waypoint=V\u00fdzna\u010dn\u00e9 body
dialog.pointtype.photo=M\u00edsta s fotografiemi
dialog.pointtype.audio=M\u00edsta s audionahr\u00e1vkami
dialog.pointtype.selection=Jen v\u00fdb\u011br
dialog.confirmreversetrack.title=Potvr\u010fte obr\u00e1cen\u00ed
-dialog.confirmreversetrack.text=Tato trasa obsahuje \u010dasov\u00e9 zna\u010dky, jejich\u017e po\u0159ad\u00ed se obr\u00e1cen\u00edm zm\u011bn\u00ed.\nOpravdu chcete obr\u00e1tit v\u00fdb\u011br?
+dialog.confirmreversetrack.text=Tato stopa obsahuje \u010dasov\u00e9 zna\u010dky, jejich\u017e po\u0159ad\u00ed se obr\u00e1cen\u00edm zm\u011bn\u00ed.\nOpravdu chcete obr\u00e1tit v\u00fdb\u011br?
dialog.confirmcutandmove.title=Potvr\u010fte p\u0159esun
-dialog.confirmcutandmove.text=Tato trasa obsahuje \u010dasov\u00e9 zna\u010dky, jejich\u017e po\u0159ad\u00ed se p\u0159esunem zm\u011bn\u00ed.\nOpravdu chcete v\u00fdb\u011br p\u0159esunout?
+dialog.confirmcutandmove.text=Tato stopa obsahuje \u010dasov\u00e9 zna\u010dky, jejich\u017e po\u0159ad\u00ed se p\u0159esunem zm\u011bn\u00ed.\nOpravdu chcete v\u00fdb\u011br p\u0159esunout?
dialog.interpolate.parameter.text=Po\u010det bod\u016f, kter\u00e9 se maj\u00ed vlo\u017eit mezi ka\u017ed\u00e9 dva po sob\u011b jdouc\u00ed body
-dialog.interpolate.betweenwaypoints=Vlo\u017eit nov\u00e9 body trasy mezi v\u00fdzna\u010dn\u00fdmi body?
+dialog.interpolate.betweenwaypoints=Vlo\u017eit nov\u00e9 body stopy mezi v\u00fdzna\u010dn\u00fdmi body?
dialog.undo.title=Vr\u00e1tit akci (akce)
dialog.undo.pretext=Pros\u00edm vyberte akci (akce) k vr\u00e1cen\u00ed
dialog.undo.none.title=Nelze vr\u00e1tit
@@ -233,7 +278,9 @@ dialog.clearundo.title=Vypr\u00e1zdnit pam\u011b\u0165 undo
dialog.clearundo.text=Opravdu chcete vypr\u00e1zdnit pam\u011b\u0165 undo?\nNebude u\u017e mo\u017en\u00e9 akce vracet do p\u016fvodn\u00edho stavu!
dialog.pointedit.title=Upravit bod
dialog.pointedit.text=Vyberte pole k editaci a stiskn\u011bte 'Upravit'
+dialog.pointedit.intro=Vyberte pole k zobrazen\u00ed a editaci hodnoty
dialog.pointedit.table.field=Pole
+dialog.pointedit.nofield=Nen\u00ed vybr\u00e1no \u017e\u00e1dn\u00e9 pole
dialog.pointedit.table.value=Hodnota
dialog.pointedit.table.changed=Zm\u011bn\u011bno
dialog.pointedit.changevalue.text=Zadejte novou hodnotu pole
@@ -269,7 +316,7 @@ dialog.charts.screen=V\u00fdstup na obrazovku
dialog.charts.svg=V\u00fdstup do souboru SVG
dialog.charts.svgwidth=\u0160\u00ed\u0159ka SVG
dialog.charts.svgheight=V\u00fd\u0161ka SVG
-dialog.charts.needaltitudeortimes=Trasa mus\u00ed obsahovat bu\u010f informaci o nadmo\u0159sk\u00e9 v\u00fd\u0161ce nebo o \u010dase, aby bylo mo\u017en\u00e9 vytv\u00e1\u0159et grafy
+dialog.charts.needaltitudeortimes=Stopa mus\u00ed obsahovat bu\u010f informaci o nadmo\u0159sk\u00e9 v\u00fd\u0161ce nebo o \u010dase, aby bylo mo\u017en\u00e9 vytv\u00e1\u0159et grafy
dialog.charts.gnuplotnotfound=Nepoda\u0159ilo se nal\u00e9zt gnuplot na dan\u00e9m um\u00edst\u011bn\u00ed
dialog.distances.intro=P\u0159\u00edm\u00e9 vzd\u00e1lenosti mezi body
dialog.distances.column.from=Z bodu
@@ -279,6 +326,27 @@ dialog.distances.toofewpoints=Je t\u0159eba zadat v\u00edce bod\u016f, aby bylo
dialog.fullrangedetails.intro=Zobrazuji detaily vybran\u00e9ho rozmez\u00ed
dialog.fullrangedetails.coltotal=V\u010detn\u011b p\u0159eru\u0161en\u00ed
dialog.fullrangedetails.colsegments=Bez p\u0159eru\u0161en\u00ed
+dialog.estimatetime.details=Podrobnosti
+dialog.estimatetime.gentle=M\u00edrn\u00e9
+dialog.estimatetime.steep=Prudk\u00e9
+dialog.estimatetime.climb=Stoup\u00e1n\u00ed
+dialog.estimatetime.descent=Kles\u00e1n\u00ed
+dialog.estimatetime.parameters=Parametry
+dialog.estimatetime.parameters.timefor=\u010cas k
+dialog.estimatetime.results=V\u00fdsledky
+dialog.estimatetime.results.estimatedtime=Odhad \u010dasu
+dialog.estimatetime.results.actualtime=Aktu\u00e1ln\u00ed \u010das
+dialog.estimatetime.error.nodistance=Aby bylo mo\u017en\u00e9 odhadnout \u010das, je t\u0159eba vybrat body jedn\u00e9 stopy
+dialog.estimatetime.error.noaltitudes=V\u00fdb\u011br neobsahuje \u00fadaje o nadmo\u0159sk\u00e9 v\u00fd\u0161ce
+dialog.learnestimationparams.intro=Parametry vypo\u010d\u00edtan\u00e9 podle t\u00e9to stopy
+dialog.learnestimationparams.averageerror=Pr\u016fm\u011brn\u00e1 chyba
+dialog.learnestimationparams.combine=Tyto parametry je mo\u017en\u00e9 zkombinovat s aktu\u00e1ln\u00edmi hodnotami
+dialog.learnestimationparams.combinedresults=V\u00fdsledn\u00e9 hodnoty
+dialog.learnestimationparams.weight.100pccurrent=Ponechat st\u00e1vaj\u00edc\u00ed hodnoty
+dialog.learnestimationparams.weight.current=st\u00e1vaj\u00edc\u00ed
+dialog.learnestimationparams.weight.calculated=vypo\u010dten\u00e9
+dialog.learnestimationparams.weight.50pc=Pr\u016fm\u011br st\u00e1vaj\u00edc\u00edch a vypo\u010d\u00edtan\u00fdch hodnot
+dialog.learnestimationparams.weight.100pccalculated=Pou\u017e\u00edt vypo\u010d\u00edtan\u00e9 hodnoty
dialog.setmapbg.intro=Vyberte jeden ze zdroj\u016f map nebo p\u0159idejte nov\u00fd
dialog.addmapsource.title=P\u0159idat nov\u00fd zdroj map
dialog.addmapsource.sourcename=N\u00e1zev zdroje
@@ -287,15 +355,15 @@ dialog.addmapsource.layer2url=Voliteln\u011b URL druh\u00e9 vrstvy
dialog.addmapsource.maxzoom=Maxim\u00e1ln\u00ed zv\u011bt\u0161en\u00ed
dialog.addmapsource.cloudstyle=\u010c\u00edslo stylu
dialog.addmapsource.noname=Bez n\u00e1zvu
-dialog.gpsies.column.name=N\u00e1zev trasy
+dialog.gpsies.column.name=N\u00e1zev stopy
dialog.gpsies.column.length=D\u00e9lka
dialog.gpsies.description=Popis
dialog.gpsies.nodescription=Bez popisu
-dialog.gpsies.nonefound=Nenalezeny \u017e\u00e1dn\u00e9 trasy
+dialog.gpsies.nonefound=Nenalezeny \u017e\u00e1dn\u00e9 stopy
dialog.gpsies.username=U\u017eiv. jm\u00e9no k gpsies
dialog.gpsies.password=Heslo k gpsies
-dialog.gpsies.keepprivate=Trasu nezve\u0159ej\u0148ovat
-dialog.gpsies.confirmopenpage=Otev\u0159\u00edt nahranou trasu v internetov\u00e9m prohl\u00ed\u017ee\u010di?
+dialog.gpsies.keepprivate=Stopu nezve\u0159ej\u0148ovat
+dialog.gpsies.confirmopenpage=Otev\u0159\u00edt nahranou stopu v internetov\u00e9m prohl\u00ed\u017ee\u010di?
dialog.gpsies.activities=Aktivita
dialog.gpsies.activity.trekking=Turistika
dialog.gpsies.activity.walking=Ch\u016fze
@@ -331,7 +399,7 @@ dialog.correlate.options.timelimit=\u010casov\u00fd limit
dialog.correlate.options.nodistancelimit=Bez d\u00e9lkov\u00e9ho limitu
dialog.correlate.options.distancelimit=D\u00e9lkov\u00fd limit
dialog.correlate.options.correlate=Sladit
-dialog.correlate.alloutsiderange=V\u0161echny polo\u017eky le\u017e\u00ed mimo \u010dasov\u00e9 rozmez\u00ed trasy, tak\u017ee nemohou b\u00fdt slad\u011bny.\nPokuste se zm\u011bnit \u010dasov\u00fd posun nebo ru\u010dn\u011b sla\u010fte aspo\u0148 jednu polo\u017eku.
+dialog.correlate.alloutsiderange=V\u0161echny polo\u017eky le\u017e\u00ed mimo \u010dasov\u00e9 rozmez\u00ed stopy, tak\u017ee nemohou b\u00fdt slad\u011bny.\nPokuste se zm\u011bnit \u010dasov\u00fd posun nebo ru\u010dn\u011b sla\u010fte aspo\u0148 jednu polo\u017eku.
dialog.correlate.filetimes=\u010cas z\u00e1znamu souboru znamen\u00e1:
dialog.correlate.filetimes2=audionahr\u00e1vky
dialog.correlate.correltimes=Sladit tento okam\u017eik nahr\u00e1vky:
@@ -358,9 +426,9 @@ dialog.compress.douglaspeucker.title=Douglasova-Peuckerova komprese
dialog.compress.douglaspeucker.paramdesc=Povolen\u00e1 odchylka
dialog.compress.summarylabel=Bod\u016f ke smaz\u00e1n\u00ed
dialog.compress.confirm1=Bylo ozna\u010deno celkem
-dialog.compress.confirm2=bod\u016f.\nBody je mo\u017en\u00e9 smazat volbou Trasa->Smazat ozna\u010den\u00e9 body
+dialog.compress.confirm2=bod\u016f.\nBody je mo\u017en\u00e9 smazat volbou Stopa->Smazat ozna\u010den\u00e9 body
dialog.compress.confirmnone=Nebyly vybr\u00e1ny \u017e\u00e1dn\u00e9 body.
-dialog.deletemarked.nonefound=Nemohou b\u00fdt odstran\u011bny \u017e\u00e1dn\u00e9 body trasy
+dialog.deletemarked.nonefound=Nemohou b\u00fdt odstran\u011bny \u017e\u00e1dn\u00e9 body stopy
dialog.pastecoordinates.desc=Zadejte sou\u0159adnice
dialog.pastecoordinates.coords=Sou\u0159adnice
dialog.pastecoordinates.nothingfound=Pros\u00edm ov\u011b\u0159te sou\u0159adnice a zkuste znovu
@@ -405,11 +473,11 @@ dialog.checkversion.releasedate1=Tato verze byla vyd\u00e1na
dialog.checkversion.releasedate2=.
dialog.checkversion.download=Novou verzi m\u016f\u017eete st\u00e1hnout na adrese http://activityworkshop.net/software/gpsprune/download.html.
dialog.keys.intro=M\u00edsto my\u0161i m\u016f\u017eete pou\u017e\u00edvat n\u00e1sleduj\u00edc\u00ed kl\u00e1vesov\u00e9 zkratky
-dialog.keys.keylist=
\u0160ipky
Posunout mapu vlevo, vpravo, nahoru, dol\u016f
Ctrl + \u0161ipka vlevo, vpravo
Vybrat p\u0159edchoz\u00ed nebo n\u00e1sleduj\u00edc\u00ed bod
Ctrl + \u0161ipka nahoru, dol\u016f
P\u0159ibl\u00ed\u017eit, odd\u00e1lit
Ctrl + PgUp, PgDown
Vybrat p\u0159edchoz\u00ed, n\u00e1sleduj\u00edc\u00ed \u010d\u00e1st trasy
Ctrl + Home, End
Vybrat prvn\u00ed, posledn\u00ed bod
Del
Smazat aktu\u00e1ln\u00ed bod
+dialog.keys.keylist=
\u0160ipky
Posunout mapu vlevo, vpravo, nahoru, dol\u016f
Ctrl + \u0161ipka vlevo, vpravo
Vybrat p\u0159edchoz\u00ed nebo n\u00e1sleduj\u00edc\u00ed bod
Ctrl + \u0161ipka nahoru, dol\u016f
P\u0159ibl\u00ed\u017eit, odd\u00e1lit
Ctrl + PgUp, PgDown
Vybrat p\u0159edchoz\u00ed, n\u00e1sleduj\u00edc\u00ed \u010d\u00e1st stopy
Ctrl + Home, End
Vybrat prvn\u00ed, posledn\u00ed bod
Del
Smazat aktu\u00e1ln\u00ed bod
dialog.keys.normalmodifier=Ctrl
dialog.keys.macmodifier=Command
dialog.saveconfig.desc=N\u00e1sleduj\u00edc\u00ed volby mohou b\u00fdt ulo\u017eeny do konfigura\u010dn\u00edho souboru :
-dialog.saveconfig.prune.trackdirectory=Adres\u00e1\u0159 s trasami
+dialog.saveconfig.prune.trackdirectory=Adres\u00e1\u0159 s stopami
dialog.saveconfig.prune.photodirectory=Ad\u0159es\u00e1\u0159 s fotografiemi
dialog.saveconfig.prune.languagecode=K\u00f3d jazyka
dialog.saveconfig.prune.languagefile=Soubor jazyka
@@ -426,7 +494,7 @@ dialog.saveconfig.prune.kmzimagewidth=\u0160\u00ed\u0159ka bitmapy KMZ
dialog.saveconfig.prune.kmzimageheight=V\u00fd\u0161ka bitmapy KMZ
dialog.saveconfig.prune.colourscheme=Barevn\u00e9 sch\u00e9ma
dialog.saveconfig.prune.linewidth=Tlou\u0161\u0165ka \u010d\u00e1ry
-dialog.saveconfig.prune.kmltrackcolour=Barva trasy v KML
+dialog.saveconfig.prune.kmltrackcolour=Barva stopy v KML
dialog.saveconfig.prune.autosavesettings=Mo\u017enosti ukl\u00e1d\u00e1n\u00ed
dialog.setpaths.intro=Je-li to t\u0159eba, m\u016f\u017eete nastavit cesty k extern\u00edm aplikac\u00edm:
dialog.setpaths.found=Cesta nalezena?
@@ -471,16 +539,13 @@ dialog.diskcache.deleted1=Smaz\u00e1no
dialog.diskcache.deleted2=soubor\u016f z cache
dialog.deletefieldvalues.intro=Vyberte pole, kter\u00e9 se m\u00e1 z aktu\u00e1ln\u00edho rozmez\u00ed odstranit
dialog.deletefieldvalues.nofields=V tomto rozmez\u00ed nelze smazat \u017e\u00e1dn\u00e9 pole
-dialog.setlinewidth.text=Zvolte tlou\u0161\u0165ku \u010d\u00e1ry, kterou se nakresl\u00ed trasa (1-4)
+dialog.setlinewidth.text=Zvolte tlou\u0161\u0165ku \u010d\u00e1ry, kterou se nakresl\u00ed stopa (1-4)
dialog.downloadosm.desc=Potvr\u010fte, \u017ee se maj\u00ed k dan\u00e9 oblasti st\u00e1hnout data OSM:
dialog.searchwikipedianames.search=Vyhledat:
# 3d window
dialog.3d.title=Trojrozm\u011brn\u00e9 zobrazen\u00ed GpsPrune
dialog.3d.altitudefactor=Faktor zd\u016frazn\u011bn\u00ed v\u00fd\u0161ky
-dialog.3dlines.title=Linky m\u0159\u00ed\u017eky GpsPrune
-dialog.3dlines.empty=Nejsou \u017e\u00e1dn\u00e9 linky k zobrazen\u00ed!
-dialog.3dlines.intro=Toto jsou linky m\u0159\u00ed\u017eky trojrozm\u011brn\u00e9ho zobrazen\u00ed
# Confirm messages
confirm.loadfile=Data na\u010dtena ze souboru
@@ -489,7 +554,7 @@ confirm.save.ok2=bod\u016f do souboru
confirm.deletepoint.single=bod byl odstran\u011bn
confirm.deletepoint.multi=body byly odstran\u011bny
confirm.point.edit=bod zm\u011bn\u011bn
-confirm.mergetracksegments=\u010c\u00e1sti trasy spojeny
+confirm.mergetracksegments=\u010c\u00e1sti stopy spojeny
confirm.reverserange=Rozmez\u00ed obr\u00e1ceno
confirm.addtimeoffset=\u010casov\u00fd posun zm\u011bn\u011bn
confirm.addaltitudeoffset=V\u00fd\u0161kov\u00fd posun zm\u011bn\u011bn
@@ -529,7 +594,6 @@ button.cancel=Zru\u0161it
button.overwrite=P\u0159epsat
button.moveup=Posunout nahoru
button.movedown=Posunout dol\u016f
-button.showlines=Zobrazit linky m\u0159\u00ed\u017eky
button.edit=Editovat
button.exit=Konec
button.close=Zav\u0159\u00edt
@@ -552,6 +616,7 @@ button.browse=Proch\u00e1zet...
button.addnew=P\u0159idat nov\u00e9
button.delete=Smazat
button.manage=Upravit
+button.combine=Zkombinovat
# File types
filetype.txt=soubory TXT
@@ -562,14 +627,16 @@ filetype.kmz=soubory KMZ
filetype.gpx=soubory GPX
filetype.pov=soubory POV
filetype.svg=soubory SVG
+filetype.png=soubory PNG
filetype.audio=soubory MP3, OGG, WAV
# Display components
display.nodata=\u017d\u00e1dn\u00e1 data
-display.noaltitudes=Trasa neobsahuje informace o v\u00fd\u0161ce
-display.notimestamps=Trasa neobsahuje \u010dasov\u00e9 zna\u010dky
-details.trackdetails=Detaily trasy
-details.notrack=\u017d\u00e1dn\u00e1 trasa
+display.noaltitudes=Stopa neobsahuje informace o v\u00fd\u0161ce
+display.notimestamps=Stopa neobsahuje \u010dasov\u00e9 zna\u010dky
+display.novalues=Stopa neobsahuje hodnoty tohoto pole
+details.trackdetails=Detaily stopy
+details.notrack=\u017d\u00e1dn\u00e1 stopa
details.track.points=Body
details.track.file=Soubor
details.track.numfiles=Po\u010det soubor\u016f
@@ -638,21 +705,31 @@ units.feet=Stopy
units.feet.short=stop
units.kilometres=Kilometry
units.kilometres.short=km
+units.kilometresperhour=km za hodinu
units.kilometresperhour.short=km/h
units.miles=M\u00edle
units.miles.short=mil
+units.milesperhour=mil za hodinu
units.milesperhour.short=mph
units.nauticalmiles=N\u00e1mo\u0159n\u00ed m\u00edle
units.nauticalmiles.short=N.m.
units.nauticalmilesperhour.short=uzly
+units.metrespersec=metr\u016f za sekundu
units.metrespersec.short=m/s
+units.feetpersec=stop za sekundu
units.feetpersec.short=stop/s
units.hours=hodin
+units.minutes=minut
+units.seconds=sekund
units.degminsec=Deg-min-sec
units.degmin=Deg-min
units.deg=Stupn\u011b
units.iso8601=ISO 8601
+# How to combine conditions, such as filters
+logic.and=a
+logic.or=nebo
+
# External urls
url.googlemaps=maps.google.cz
wikipedia.lang=cs
@@ -672,11 +749,11 @@ undo.deletepoint=smazat bod
undo.removephoto=odebrat fotografii
undo.removeaudio=odebrat audionahr\u00e1vku
undo.deleterange=smazat rozmez\u00ed
-undo.croptrack=o\u0159\u00edznout trasu
-undo.deletemarked=zkomprimovat trasu
+undo.croptrack=o\u0159\u00edznout stopu
+undo.deletemarked=zkomprimovat stopu
undo.insert=vlo\u017eit body
undo.reverse=obr\u00e1tit rozmez\u00ed
-undo.mergetracksegments=slou\u010dit \u010d\u00e1sti trasy
+undo.mergetracksegments=slou\u010dit \u010d\u00e1sti stopy
undo.addtimeoffset=p\u0159idat \u010dasov\u00fd posun
undo.addaltitudeoffset=p\u0159idat v\u00fd\u0161kov\u00fd posun
undo.rearrangewaypoints=p\u0159euspo\u0159\u00e1dat body
@@ -721,7 +798,7 @@ error.undofailed.text=Nepoda\u0159ilo se vr\u00e1tit operaci
error.function.noop.title=Operace nic neprovedla
error.rearrange.noop=P\u0159euspo\u0159\u00e1d\u00e1n\u00ed nic neprovedlo
error.function.notavailable.title=Funkce nen\u00ed dostupn\u00e1
-error.function.nojava3d=Tato funkce vy\u017eaduje knihovnu Java3d,\ndostupnou na Sun.com.
+error.function.nojava3d=Tato funkce vy\u017eaduje knihovnu Java3d.
error.3d=P\u0159i trojrozm\u011brn\u00e9m zobrazen\u00ed do\u0161lo k chyb\u011b
error.readme.notfound=Nenalezen soubor readme
error.osmimage.dialogtitle=Chyba p\u0159i na\u010d\u00edt\u00e1n\u00ed mapov\u00fdch podklad\u016f
@@ -738,3 +815,4 @@ error.cache.notthere=Nepoda\u0159ilo se nal\u00e9zt adres\u00e1\u0159 s cache ma
error.cache.empty=Adres\u00e1\u0159 s cache map je pr\u00e1zdn\u00fd.
error.cache.cannotdelete=Nelze smazat soubory map.
error.interpolate.invalidparameter=Po\u010det bod\u016f mus\u00ed b\u00fdt mezi 1 a 1000
+error.learnestimationparams.failed=Na z\u00e1klad\u011b t\u00e9to stopy nelze vypo\u010d\u00edtat parametr.\nZkuste jin\u00e9 stopy.
diff --git a/tim/prune/lang/prune-texts_da.properties b/tim/prune/lang/prune-texts_da.properties
index b375fe3..ea394b9 100644
--- a/tim/prune/lang/prune-texts_da.properties
+++ b/tim/prune/lang/prune-texts_da.properties
@@ -80,6 +80,5 @@ function.show3d=3-D view
function.distances=Afstande
function.fullrangedetails=Vis alle detaljer
function.setmapbg=V\u00e6lg kort som baggrund
-function.setkmzimagesize=V\u00e6lg KMZ billedst\u00f8rrelse
function.setpaths=V\u00e6lg sti til programmer
function.getgpsies=Se liste af GPS-spor
diff --git a/tim/prune/lang/prune-texts_de.properties b/tim/prune/lang/prune-texts_de.properties
index ee57c21..4914e44 100644
--- a/tim/prune/lang/prune-texts_de.properties
+++ b/tim/prune/lang/prune-texts_de.properties
@@ -84,6 +84,7 @@ function.exportkml=KML exportieren
function.exportgpx=GPX exportieren
function.exportpov=POV exportieren
function.exportsvg=SVG exportieren
+function.exportimage=Bild exportieren
function.editwaypointname=Name des Punkts bearbeiten
function.compress=Track komprimieren
function.deleterange=Bereich l\u00f6schen
@@ -99,8 +100,9 @@ function.charts=Diagramme
function.show3d=3D Ansicht
function.distances=Entfernungen
function.fullrangedetails=Zus\u00e4tzliche Bereichdetails
+function.estimatetime=Zeit absch\u00e4tzen
+function.learnestimationparams=Zeitparameter erlernen
function.setmapbg=Karte Hintergrund setzen
-function.setkmzimagesize=Bildgr\u00f6\u00dfe im KMZ setzen
function.setpaths=Programmpfade setzen
function.getgpsies=Tracks bei GPSies.com herunterladen
function.uploadgpsies=Track zu GPSies.com hochladen
@@ -159,6 +161,10 @@ dialog.openoptions.deliminfo.records=Datens\u00e4tze, mit
dialog.openoptions.deliminfo.fields=Feldern
dialog.openoptions.deliminfo.norecords=Keine Datens\u00e4tze
dialog.openoptions.altitudeunits=Ma\u00dfeinheiten f\u00fcr die H\u00f6he
+dialog.openoptions.speedunits=Ma\u00dfeinheiten f\u00fcr die Geschwindigkeiten
+dialog.openoptions.vertspeedunits=Ma\u00dfeinheiten f\u00fcr vertikale Geschwindigkeiten
+dialog.openoptions.vspeed.positiveup=Positive Geschwindigkeiten aufw\u00e4rts
+dialog.openoptions.vspeed.positivedown=Positive Geschwindigkeiten abw\u00e4rts
dialog.open.contentsdoubled=Diese Datei enth\u00e4lt zwei Kopien jedes Punkts,\neinmal als Waypoint und einmal als Trackpunkt.
dialog.selecttracks.intro=W\u00e4hlen Sie den Track oder die Tracks aus, die Sie laden m\u00f6chten
dialog.selecttracks.noname=Unbenannt
@@ -175,7 +181,31 @@ dialog.gpsload.gettracks=Tracks laden
dialog.gpsload.save=Als Datei speichern
dialog.gpssend.sendwaypoints=Wegpunkte senden
dialog.gpssend.sendtracks=Tracks senden
-dialog.gpssend.trackname=Track Name
+dialog.gpssend.trackname=Trackname
+dialog.gpsbabel.filters=Filter
+dialog.addfilter.title=Filter einf\u00fcgen
+dialog.gpsbabel.filter.discard=Wegwerfen
+dialog.gpsbabel.filter.simplify=Vereinfachen
+dialog.gpsbabel.filter.distance=Distanz
+dialog.gpsbabel.filter.interpolate=Interpolieren
+dialog.gpsbabel.filter.discard.intro=Punkte wegwerfen, falls
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=Anzahl Satelliten <
+dialog.gpsbabel.filter.discard.nofix=Punkt kein Fix hat
+dialog.gpsbabel.filter.discard.unknownfix=Punkt unbekanntes Fix hat
+dialog.gpsbabel.filter.simplify.intro=Punkte entfernen bis
+dialog.gpsbabel.filter.simplify.maxpoints=Anzahl Punkte <
+dialog.gpsbabel.filter.simplify.maxerror=oder Fehlerdistanz <
+dialog.gpsbabel.filter.simplify.crosstrack=Distanz quer
+dialog.gpsbabel.filter.simplify.length=L\u00e4ngendifferenz
+dialog.gpsbabel.filter.simplify.relative=Relativ zum Hdop
+dialog.gpsbabel.filter.distance.intro=Punkte entfernen, die in der N\u00e4he von fr\u00fcheren Punkten sind
+dialog.gpsbabel.filter.distance.distance=Falls Distanz <
+dialog.gpsbabel.filter.distance.time=und Zeitdifferenz <
+dialog.gpsbabel.filter.interpolate.intro=Zus\u00e4tzliche Punkte hineinf\u00fcgen
+dialog.gpsbabel.filter.interpolate.distance=Falls Distanz >
+dialog.gpsbabel.filter.interpolate.time=oder Zeitdifferenz >
dialog.saveoptions.title=Datei speichern
dialog.save.fieldstosave=Zu speichernde Felder
dialog.save.table.field=Feld
@@ -192,7 +222,10 @@ dialog.exportkml.text=Titel f\u00fcr die Daten
dialog.exportkml.altitude=Absolute H\u00f6heninformation (f\u00fcr Luftfahrt)
dialog.exportkml.kmz=Daten in KMZ-Datei komprimieren
dialog.exportkml.exportimages=Vorschaubilder mit in KMZ-Datei exportieren
+dialog.exportkml.imagesize=Bildgr\u00f6\u00dfe
dialog.exportkml.trackcolour=Trackfarbe
+dialog.exportkml.standardkml=Standardes KML
+dialog.exportkml.extendedkml=Erweitertes KML mit Zeitstempeln
dialog.exportgpx.name=Name
dialog.exportgpx.desc=Beschreibung
dialog.exportgpx.includetimestamps=Zeitstempel mit exportieren
@@ -208,11 +241,23 @@ dialog.exportpov.cameraz=Kamera Z
dialog.exportpov.modelstyle=Modellstil
dialog.exportpov.ballsandsticks=B\u00e4lle und Stangen
dialog.exportpov.tubesandwalls=R\u00f6hren und W\u00e4nde
-dialog.exportpov.warningtracksize=Dieser Track hat sehr viele Punkte, die Java3D vielleicht nicht bearbeiten kann.\nM\u00f6chten Sie den Vorgang trotzdem fortsetzen?
+dialog.3d.warningtracksize=Dieser Track hat sehr viele Punkte, die Java3D vielleicht nicht bearbeiten kann.\nM\u00f6chten Sie den Vorgang trotzdem fortsetzen?
+dialog.exportpov.baseimage=Grundbild
+dialog.exportpov.cannotmakebaseimage=Bild kann nicht gespeichert werden
+dialog.baseimage.title=Kartenbild
+dialog.baseimage.useimage=Bild verwenden
+dialog.baseimage.mapsource=Kartenquelle
+dialog.baseimage.zoom=Zoomstufe
+dialog.baseimage.incomplete=Bild unvollst\u00e4ndig
+dialog.baseimage.tiles=Kacheln
+dialog.baseimage.size=Bildgr\u00f6\u00dfe
dialog.exportsvg.text=W\u00e4hlen Sie die Parameter f\u00fcr den SVG-Export aus
dialog.exportsvg.phi=Richtungswinkel \u03d5
dialog.exportsvg.theta=Neigungswinkel \u03b8
dialog.exportsvg.gradients=Farbverl\u00e4ufe verwenden
+dialog.exportimage.noimagepossible=Kartenbilder m\u00fcssen schon gespeichert werden bevor sie in einem Export verwendet werden k\u00f6nnen
+dialog.exportimage.drawtrack=Track auf der Karte zeichnen
+dialog.exportimage.textscalepercent=Text Skalierung (%)
dialog.pointtype.desc=Folgende Punkttypen speichern:
dialog.pointtype.track=Trackpunkte
dialog.pointtype.waypoint=Wegpunkte
@@ -233,7 +278,9 @@ dialog.clearundo.title=Undo-Liste l\u00f6schen
dialog.clearundo.text=Wollen Sie wirklich die Undo-Liste l\u00f6schen?\nAlle Undo- Informationen werden verloren gehen!
dialog.pointedit.title=Punkt bearbeiten
dialog.pointedit.text=W\u00e4hlen Sie die Felder aus, die Sie bearbeiten m\u00f6chten, und verwenden Sie den 'Bearbeiten'-Button, um den Wert zu \u00e4ndern
+dialog.pointedit.intro=W\u00e4hlen Sie die Felder aus um die Werte zu sehen und bearbeiten
dialog.pointedit.table.field=Feld
+dialog.pointedit.nofield=Keinen Feld ausgew\u00e4hlt
dialog.pointedit.table.value=Wert
dialog.pointedit.table.changed=Ge\u00e4ndert
dialog.pointedit.changevalue.text=Geben Sie den neuen Wert f\u00fcr dieses Feld ein
@@ -279,6 +326,27 @@ dialog.distances.toofewpoints=Diese Funktion braucht Wegpunkte, um die Distanzen
dialog.fullrangedetails.intro=Detaillierte Angaben zum markierten Bereich
dialog.fullrangedetails.coltotal=Mit L\u00fccken
dialog.fullrangedetails.colsegments=Ohne L\u00fccken
+dialog.estimatetime.details=Details
+dialog.estimatetime.gentle=Leicht
+dialog.estimatetime.steep=Steil
+dialog.estimatetime.climb=Aufstieg
+dialog.estimatetime.descent=Abstieg
+dialog.estimatetime.parameters=Parameter
+dialog.estimatetime.parameters.timefor=Zeit f\u00fcr
+dialog.estimatetime.results=Ergebnisse
+dialog.estimatetime.results.estimatedtime=Abgesch\u00e4tzte Zeit
+dialog.estimatetime.results.actualtime=Gebrauchte Zeit
+dialog.estimatetime.error.nodistance=Die Absch\u00e4tzungen brauchen zusammengebundete Punkte mit einer Distanz
+dialog.estimatetime.error.noaltitudes=Der Bereich enth\u00e4lt keine H\u00f6heninformation
+dialog.learnestimationparams.intro=Hier sind die Parameter die aus diesem Track berechnet wurden
+dialog.learnestimationparams.averageerror=Fehler
+dialog.learnestimationparams.combine=Diese Parameter k\u00f6nnen mit den aktuellen Werten zusammengeschlossen werden
+dialog.learnestimationparams.combinedresults=Zusammengeschlossenen Ergebnisse
+dialog.learnestimationparams.weight.100pccurrent=Aktuelle Werte behalten
+dialog.learnestimationparams.weight.current=aktuell
+dialog.learnestimationparams.weight.calculated=berechnet
+dialog.learnestimationparams.weight.50pc=Mittelwert zwischen aktuellen und berechneten Werten
+dialog.learnestimationparams.weight.100pccalculated=Neue berechnete Werte \u00fcbernehmen
dialog.setmapbg.intro=Eine der Quellen ausw\u00e4hlen oder eine neue hinzuf\u00fcgen
dialog.addmapsource.title=Neue Kartenquelle hinzuf\u00fcgen
dialog.addmapsource.sourcename=Name der Quelle
@@ -422,8 +490,7 @@ dialog.saveconfig.prune.exiftoolpath=ExifTool-Pfad
dialog.saveconfig.prune.mapsource=Kartenserver-Index
dialog.saveconfig.prune.mapsourcelist=Kartenserver
dialog.saveconfig.prune.diskcache=Kartenordner
-dialog.saveconfig.prune.kmzimagewidth=Bildbreite in KMZ
-dialog.saveconfig.prune.kmzimageheight=Bildh\u00f6he in KMZ
+dialog.saveconfig.prune.kmzimagewidth=Bildgr\u00f6\u00dfe in KMZ
dialog.saveconfig.prune.colourscheme=Farbschema
dialog.saveconfig.prune.linewidth=Liniedicke
dialog.saveconfig.prune.kmltrackcolour=KML-Trackfarbe
@@ -478,9 +545,6 @@ dialog.searchwikipedianames.search=Suche nach:
# 3d window
dialog.3d.title=GpsPrune-3D-Ansicht
dialog.3d.altitudefactor=Vervielfachungsfaktor f\u00fcr H\u00f6hen
-dialog.3dlines.title=GpsPrune-Gitterlinien
-dialog.3dlines.empty=Keine Linien zum Anzeigen!
-dialog.3dlines.intro=Hier sind die Linien f\u00fcr die 3D Ansicht
# Confirm messages
confirm.loadfile=Daten aus Datei geladen
@@ -529,7 +593,6 @@ button.cancel=Abbrechen
button.overwrite=\u00dcberschreiben
button.moveup=Nach oben
button.movedown=Nach unten
-button.showlines=Linien anzeigen
button.edit=Bearbeiten
button.exit=Beenden
button.close=Schlie\u00dfen
@@ -552,6 +615,7 @@ button.browse=Durchsuchen...
button.addnew=Hinzuf\u00fcgen
button.delete=Entfernen
button.manage=Verwalten
+button.combine=Zusammenschlie\u00dfen
# File types
filetype.txt=TXT-Dateien
@@ -562,12 +626,14 @@ filetype.kmz=KMZ-Dateien
filetype.gpx=GPX-Dateien
filetype.pov=POV-Dateien
filetype.svg=SVG-Dateien
+filetype.png=PNG-Dateien
filetype.audio=MP3-, OGG-, WAV-Dateien
# Display components
display.nodata=Keine Daten geladen
display.noaltitudes=Track enth\u00e4lt keine H\u00f6henangaben
display.notimestamps=Track enth\u00e4lt keine Zeitstempel
+display.novalues=Track enth\u00e4lt keine Daten f\u00fcr dieses Feld
details.trackdetails=Details des Tracks
details.notrack=Kein Track geladen
details.track.points=Punkte
@@ -636,18 +702,31 @@ units.metres=Meter
units.metres.short=m
units.kilometres=Kilometer
units.kilometres.short=km
+units.kilometresperhour=km pro Stunde
units.kilometresperhour.short=km/h
+units.miles=Meilen
+units.miles.short=Mi
+units.milesperhour=Meilen pro Stunde
+units.milesperhour.short=mph
units.nauticalmiles=Seemeilen
units.nauticalmiles.short=sm
units.nauticalmilesperhour.short=kn
+units.metrespersec=Meter pro Sekunde
units.metrespersec.short=m/s
+units.feetpersec=feet pro Sekunde
units.feetpersec.short=ft/s
units.hours=Std
+units.minutes=Minuten
+units.seconds=Sekunden
units.degminsec=Grad-Min-Sek
units.degmin=Grad-Min
units.deg=Grad
units.iso8601=ISO 8601
+# How to combine conditions, such as filters
+logic.and=und
+logic.or=oder
+
# External urls
url.googlemaps=maps.google.de
wikipedia.lang=de
@@ -716,7 +795,7 @@ error.undofailed.text=Operation konnte nicht r\u00fcckg\u00e4ngig gemacht werden
error.function.noop.title=Funktion hat nichts bewirkt
error.rearrange.noop=Die Neuanordnung der Punkte hatte keinen Effekt
error.function.notavailable.title=Funktion nicht verf\u00fcgbar
-error.function.nojava3d=Diese Funktion ben\u00f6tigt die Java3d-Library,\ndie bei Sun.com erh\u00e4ltlich ist.
+error.function.nojava3d=Diese Funktion ben\u00f6tigt die Java3d-Library.
error.3d=Ein Fehler ist bei der 3D Darstellung aufgetreten
error.readme.notfound=Liesmich-Datei nicht gefunden
error.osmimage.dialogtitle=Laden von Karten-Bildern fehlgeschlagen
@@ -733,3 +812,4 @@ error.cache.notthere=Der Ordner wurde nicht gefunden
error.cache.empty=Der Ordner ist leer
error.cache.cannotdelete=Es konnte keine Kacheln gel\u00f6scht werden
error.interpolate.invalidparameter=Die Anzahl der Punkte muss zwischen 1 und 1000 liegen
+error.learnestimationparams.failed=Mit diesem Track k\u00f6nnen die Parameter nicht berechnet werden.\nVersuchen Sie mit mehreren Tracks.
diff --git a/tim/prune/lang/prune-texts_de_CH.properties b/tim/prune/lang/prune-texts_de_CH.properties
index caa5bd6..fa2931f 100644
--- a/tim/prune/lang/prune-texts_de_CH.properties
+++ b/tim/prune/lang/prune-texts_de_CH.properties
@@ -83,6 +83,7 @@ function.exportkml=KML exportier\u00e4
function.exportgpx=GPX exportier\u00e4
function.exportpov=POV exportier\u00e4
function.exportsvg=SVG exportier\u00e4
+function.exportimage=Bild exportier\u00e4
function.editwaypointname=Waypoint Name editiere
function.compress=Track komprimier\u00e4
function.deleterange=Beriich l\u00f6sche
@@ -97,6 +98,8 @@ function.charts=Diagramme
function.show3d=Dr\u00fc\u00fc-D Aasicht
function.distances=Entf\u00e4rnige
function.fullrangedetails=Zues\u00e4tzlichi Beriichinfos
+function.estimatetime=Ziit absch\u00e4tze
+function.learnestimationparams=Ziitparameter erlerne
function.setmapbg=Karte Hintegrund setz\u00e4
function.getgpsies=Gpsies Tracks hol\u00e4
function.uploadgpsies=Date zum Gpsies uufalad\u00e4
@@ -116,7 +119,6 @@ function.removeaudio=Audiodatei entfern\u00e4
function.correlateaudios=Audios korrelier\u00e4
function.playaudio=Audiofile abspiel\u00e4
function.stopaudio=Abspielen abbr\u00e4ch\u00e4
-function.setkmzimagesize=Bildligr\u00f6sse inem KMZ setz\u00e4
function.setpaths=Programmepfade setz\u00e4
function.setcolours=Farben setz\u00e4
function.setlinewidth=Liniedicke setz\u00e4
@@ -154,6 +156,10 @@ dialog.openoptions.deliminfo.records=Rekords, mit
dialog.openoptions.deliminfo.fields=F\u00e4ldere
dialog.openoptions.deliminfo.norecords=Kei Rekords
dialog.openoptions.altitudeunits=H\u00f6chi Masseiheite
+dialog.openoptions.speedunits=Masseiheite f\u00fcr Geschwindigkeite
+dialog.openoptions.vertspeedunits=Masseiheite f\u00fcr vertikale Geschwindigkeite
+dialog.openoptions.vspeed.positiveup=Positive bed\u00fc\u00fctet uufe
+dialog.openoptions.vspeed.positivedown=Positive bed\u00fc\u00fctet abe
dialog.open.contentsdoubled=Dieses File h\u00e4t zwei Kopien von j\u00e4dem Punkt,\neimol als Waypoint und eimol als Trackpunkt.
dialog.selecttracks.intro=W\u00e4hlet Sie die Tracks uus zum lad\u00e4
dialog.selecttracks.noname=Unbenannt
@@ -172,6 +178,30 @@ dialog.gpssend.sendwaypoints=Waypoints schicke
dialog.gpssend.sendtracks=Tracks schicke
dialog.gpssend.trackname=Track Name
dialog.saveoptions.title=File speicher\u00e4
+dialog.gpsbabel.filters=Filter
+dialog.addfilter.title=Filter inna\u00fce
+dialog.gpsbabel.filter.discard=W\u00e4gwerfe
+dialog.gpsbabel.filter.simplify=Vereifache
+dialog.gpsbabel.filter.distance=Distanz
+dialog.gpsbabel.filter.interpolate=Interpoliere
+dialog.gpsbabel.filter.discard.intro=P\u00fcnkte wegwerfen, im Fall
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=Aazahl Satellite <
+dialog.gpsbabel.filter.discard.nofix=Punkt kei Fix h\u00e4t
+dialog.gpsbabel.filter.discard.unknownfix=Punkt unbekannti Fix h\u00e4t
+dialog.gpsbabel.filter.simplify.intro=P\u00fcnkte entf\u00e4rne bis
+dialog.gpsbabel.filter.simplify.maxpoints=Aazahl P\u00fcnkte <
+dialog.gpsbabel.filter.simplify.maxerror=oder F\u00e4hlerdistanz <
+dialog.gpsbabel.filter.simplify.crosstrack=Distanz quer
+dialog.gpsbabel.filter.simplify.length=L\u00e4ngediffer\u00e4nz
+dialog.gpsbabel.filter.simplify.relative=Relativ zum Hdop
+dialog.gpsbabel.filter.distance.intro=P\u00fcnkte entf\u00e4rne, die in der N\u00f6chi vo fr\u00fchere P\u00fcnkte sin
+dialog.gpsbabel.filter.distance.distance=im Fall Distanz <
+dialog.gpsbabel.filter.distance.time=und Ziitdiffer\u00e4nz <
+dialog.gpsbabel.filter.interpolate.intro=Zus\u00e4tzlichi P\u00fcnkte innat\u00fce
+dialog.gpsbabel.filter.interpolate.distance=im Fall Distanz >
+dialog.gpsbabel.filter.interpolate.time=oder Ziitdiffer\u00e4nz >
dialog.save.fieldstosave=F\u00e4lder zu speicher\u00e4
dialog.save.table.field=F\u00e4ld
dialog.save.table.hasdata=Het Date
@@ -187,7 +217,10 @@ dialog.exportkml.text=Titel f\u00fcr die Date
dialog.exportkml.altitude=Absolut H\u00f6chiinformation (f\u00fcrs Fliege)
dialog.exportkml.kmz=Date ins kmz File komprimier\u00e4
dialog.exportkml.exportimages=Bildli ins Kmz exportier\u00e4
+dialog.exportkml.imagesize=Bildligr\u00f6sse
dialog.exportkml.trackcolour=Trackfarb
+dialog.exportkml.standardkml=Standardes KML
+dialog.exportkml.extendedkml=Erwiitertes KML mit Ziitst\u00e4mple
dialog.exportgpx.name=Name
dialog.exportgpx.desc=Beschriibig
dialog.exportgpx.includetimestamps=Au Ziitst\u00e4mpel
@@ -203,11 +236,23 @@ dialog.exportpov.cameraz=Kamera Z
dialog.exportpov.modelstyle=Modellstil
dialog.exportpov.ballsandsticks=B\u00e4lle und Schtange
dialog.exportpov.tubesandwalls=R\u00f6hre und W\u00e4nde
-dialog.exportpov.warningtracksize=Dieser Track h\u00e4t mega viele P\u00fcnkte, die Java3D villiicht n\u00f6d chann bearbeite.\nSind Sie sicher, Sie wend trotzdem fortsetze?
+dialog.3d.warningtracksize=Dieser Track h\u00e4t mega viele P\u00fcnkte, die Java3D villiicht n\u00f6d chann bearbeite.\nSind Sie sicher, Sie wend trotzdem fortsetze?
+dialog.exportpov.baseimage=Grundbild
+dialog.exportpov.cannotmakebaseimage=Bild chann n\u00f6d gspeicheret werde
+dialog.baseimage.title=Kartenbild
+dialog.baseimage.useimage=Bild verw\u00e4nde
+dialog.baseimage.mapsource=Kartequ\u00e4lle
+dialog.baseimage.zoom=Zoomstufe
+dialog.baseimage.incomplete=Bild unvollst\u00e4ndig
+dialog.baseimage.tiles=Kachle
+dialog.baseimage.size=Bildgr\u00f6ssi
dialog.exportsvg.text=W\u00e4hlet Sie die Parameter f\u00fcrs SVG Export uus
dialog.exportsvg.phi=Richtigswinkel \u03D5
dialog.exportsvg.theta=Neigigswinkel \u03B8
dialog.exportsvg.gradients=Farbeverl\u00e4ufe verw\u00e4nde
+dialog.exportimage.noimagepossible=Kartebilder m\u00fcsset scho gspeicheret werde, bevor sie bim Export verwendet werde k\u00f6nne
+dialog.exportimage.drawtrack=Track uf d Karte zeichne
+dialog.exportimage.textscalepercent=Text Skalierig (%)
dialog.pointtype.desc=Folgende Punkttype speichere:
dialog.pointtype.track=Trackp\u00fcnkte
dialog.pointtype.waypoint=Waypoints
@@ -228,7 +273,9 @@ dialog.clearundo.title=Undo-Liste l\u00f6sch\u00e4
dialog.clearundo.text=Sind Sie sicher, Sie wend die Undo-Liste l\u00f6sche?\nAlle Undo Infos werdet verlore gah!
dialog.pointedit.title=Punkt editier\u00e4
dialog.pointedit.text=W\u00e4hlet Sie j\u00e4den F\u00e4ld uus zu editiere, und mitem 'Editier\u00e4' Chnopf den Wert \u00e4ndere
+dialog.pointedit.intro=W\u00e4hlet Sie j\u00e4den F\u00e4ld uus, um den Wert z'seh und z'\u00e4ndere
dialog.pointedit.table.field=F\u00e4ld
+dialog.pointedit.nofield=Kei F\u00e4ld uusgew\u00e4hlt
dialog.pointedit.table.value=Wert
dialog.pointedit.table.changed=Ge\u00e4ndert
dialog.pointedit.changevalue.text=Gebet Sie den neuen Wert f\u00fcr diesen F\u00e4ld ina
@@ -274,6 +321,27 @@ dialog.distances.toofewpoints=d'Funktion bruucht Waypoints um die Dischtanze z b
dialog.fullrangedetails.intro=Hier sind die Infos vonem aktuelli Beriich
dialog.fullrangedetails.coltotal=Inklusiv L\u00fccke
dialog.fullrangedetails.colsegments=Ohni L\u00fccke
+dialog.estimatetime.details=Details
+dialog.estimatetime.gentle=Liecht
+dialog.estimatetime.steep=Steil
+dialog.estimatetime.climb=Uufstieg
+dialog.estimatetime.descent=Abstieg
+dialog.estimatetime.parameters=Parameter
+dialog.estimatetime.parameters.timefor=Ziit f\u00fcr
+dialog.estimatetime.results=Resultate
+dialog.estimatetime.results.estimatedtime=Abgesch\u00e4tzti Ziit
+dialog.estimatetime.results.actualtime=Gebruuchti Ziit
+dialog.estimatetime.error.nodistance=D Absch\u00e4tzige bruuchet zamegebundeti P\u00fcnkt mitene Distanz
+dialog.estimatetime.error.noaltitudes=D Beriich h\u00e4t kei H\u00f6hi Date
+dialog.learnestimationparams.intro=Hier sin die Parameter die usem Track uusgr\u00e4chnet worde sin
+dialog.learnestimationparams.averageerror=Fehler
+dialog.learnestimationparams.combine=Diese Parameter k\u00f6nnet miten aktuelli Werte z\u00e4megschlosse werde
+dialog.learnestimationparams.combinedresults=Z\u00e4megschlossene Resultate
+dialog.learnestimationparams.weight.100pccurrent=Aktuelli Werte behalte
+dialog.learnestimationparams.weight.current=aktuell
+dialog.learnestimationparams.weight.calculated=uusgr\u00e4chnet
+dialog.learnestimationparams.weight.50pc=Mittelwert zw\u00fcschet aktuelli und uusgr\u00e4chneti Werte
+dialog.learnestimationparams.weight.100pccalculated=Neui uusgr\u00e4chneti Werte \u00fcberneh
dialog.setmapbg.intro=Eini von den Qu\u00e4llen uusw\u00e4hle, oder eini neui hinzuef\u00fcge
dialog.addmapsource.title=Neui Kartequ\u00e4lle hinzuef\u00fcge
dialog.addmapsource.sourcename=Sourcename
@@ -417,8 +485,7 @@ dialog.saveconfig.prune.exiftoolpath=Exiftool Pfad
dialog.saveconfig.prune.mapsource=Kartenserver Index
dialog.saveconfig.prune.mapsourcelist=Kartenservers
dialog.saveconfig.prune.diskcache=Kartenordner
-dialog.saveconfig.prune.kmzimagewidth=Bildbreiti im KMZ
-dialog.saveconfig.prune.kmzimageheight=Bildh\u00f6chi im KMZ
+dialog.saveconfig.prune.kmzimagewidth=Bildr\u00f6sse im KMZ
dialog.saveconfig.prune.colourscheme=Farbeschema
dialog.saveconfig.prune.linewidth=Liniedicke
dialog.saveconfig.prune.kmltrackcolour=KML Trackfarb
@@ -473,9 +540,6 @@ dialog.searchwikipedianames.search=Sueche na:
# 3d window
dialog.3d.title=GpsPrune Dr\u00fc\u00fc-d Aasicht
dialog.3d.altitudefactor=H\u00f6chivervilfachigsfaktor
-dialog.3dlines.title=GpsPrune Gitterlinie
-dialog.3dlines.empty=Kei Linie zum aazeig\u00e4!
-dialog.3dlines.intro=Hier sin die Linie f\u00fcr die dr\u00fc\u00fc-D Aasicht
# Confirm messages
confirm.loadfile=Date glade vom
@@ -525,7 +589,6 @@ button.cancel=Abbr\u00e4ch\u00e4
button.overwrite=Ãœberschriib\u00e4
button.moveup=Uuf\u00e4 schieb\u00e4
button.movedown=Aba schieb\u00e4
-button.showlines=Linie aazeig\u00e4
button.edit=Editier\u00e4
button.exit=Be\u00e4nd\u00e4
button.close=Schliess\u00e4
@@ -548,6 +611,7 @@ button.browse=Durasuech\u00e4...
button.addnew=Hinzuef\u00fcg\u00e4
button.delete=Entf\u00e4rn\u00e4
button.manage=Verwolt\u00e4
+button.combine=Z\u00e4meschliess\u00e4
# File types
filetype.txt=TXT Dateie
@@ -558,12 +622,14 @@ filetype.kmz=KMZ Dateie
filetype.gpx=GPX Dateie
filetype.pov=POV Dateie
filetype.svg=SVG Dateie
+filetype.png=PNG Dateie
filetype.audio=MP3, OGG, WAV Dateie
# Display components
display.nodata=Kei Date glade worde
display.noaltitudes=Track h\u00e4t kei H\u00f6hi Date
display.notimestamps=Track h\u00e4t kei Ziitst\u00e4mple
+display.novalues=Track h\u00e4t kei Date f\u00fcr s'F\u00e4ld
details.trackdetails=Details vom Track
details.notrack=Kei Track glade worde
details.track.points=P\u00fcnkte
@@ -631,18 +697,31 @@ units.metres=Meter
units.metres.short=m
units.kilometres=Kilometer
units.kilometres.short=km
+units.kilometresperhour=km pro Stund
units.kilometresperhour.short=kmh
+units.miles=Meile
+units.miles.short=Mi
+units.milesperhour=Meile pro Stund
+units.milesperhour.short=mph
units.nauticalmiles=Seemeile
units.nauticalmiles.short=sm
units.nauticalmilesperhour.short=kn
+units.metrespersec=Meter pro Sekunde
units.metrespersec.short=m/s
+units.feetpersec=feet pro Sekunde
units.feetpersec.short=ft/s
units.hours=Std
+units.minutes=Minute
+units.seconds=Sekunde
units.degminsec=Grad-Min-Sek
units.degmin=Grad-Min
units.deg=Grad
units.iso8601=ISO 8601
+# How to combine conditions, such as filters
+logic.and=und
+logic.or=oder
+
# External urls
url.googlemaps=maps.google.ch
wikipedia.lang=als
@@ -711,7 +790,7 @@ error.undofailed.text=Operation kann n\u00f6d r\u00fcckg\u00e4ngig gmacht werde
error.function.noop.title=Funktion h\u00e4t gar n\u00fc\u00fct gmacht
error.rearrange.noop=P\u00fcnkte Reorganisierig h\u00e4t kei Eff\u00e4kt gha
error.function.notavailable.title=Funktion n\u00f6d verf\u00fcegbar
-error.function.nojava3d=Sorry, d'Funktion brucht d Java3d Library,\nvo Sun.com erh\u00e4ltlech.
+error.function.nojava3d=Sorry, d'Funktion brucht d Java3d Library.
error.3d=N F\u00e4hler isch mitere 3d Darstellig ufgtr\u00e4te
error.readme.notfound=L\u00e4s mi File n\u00f6d gfunde
error.osmimage.dialogtitle=F\u00e4hle bim Bildli-Lade
@@ -728,3 +807,4 @@ error.cache.notthere=D Ordner isch n\u00f6d gfunde worde
error.cache.empty=D Ordner h\u00e4t n\u00fc\u00fct drinne
error.cache.cannotdelete=Es sin kei Kachle gl\u00f6scht worde
error.interpolate.invalidparameter=D'Aazahl P\u00fcnkt muess zw\u00fcschet 1 und 1000 sii
+error.learnestimationparams.failed=Mit dere Track k\u00f6nnet die Parameter n\u00f6d br\u00e4chnet werde.\nVersuechet Sie mit mehreri Tracks.
diff --git a/tim/prune/lang/prune-texts_en.properties b/tim/prune/lang/prune-texts_en.properties
index f2e70e1..0193cb8 100644
--- a/tim/prune/lang/prune-texts_en.properties
+++ b/tim/prune/lang/prune-texts_en.properties
@@ -84,6 +84,7 @@ function.exportkml=Export KML
function.exportgpx=Export GPX
function.exportpov=Export POV
function.exportsvg=Export SVG
+function.exportimage=Export image
function.editwaypointname=Edit waypoint name
function.compress=Compress track
function.deleterange=Delete range
@@ -99,6 +100,8 @@ function.charts=Charts
function.show3d=Three-D view
function.distances=Distances
function.fullrangedetails=Full range details
+function.estimatetime=Estimate time
+function.learnestimationparams=Learn time estimation parameters
function.getgpsies=Get Gpsies tracks
function.uploadgpsies=Upload track to Gpsies
function.lookupsrtm=Get altitudes from SRTM
@@ -121,7 +124,6 @@ function.correlateaudios=Correlate audios
function.playaudio=Play audio clip
function.stopaudio=Stop audio clip
function.setmapbg=Set map background
-function.setkmzimagesize=Set KMZ image size
function.setpaths=Set program paths
function.setcolours=Set colours
function.setlinewidth=Set line width
@@ -159,6 +161,10 @@ dialog.openoptions.deliminfo.records=records, with
dialog.openoptions.deliminfo.fields=fields
dialog.openoptions.deliminfo.norecords=No records
dialog.openoptions.altitudeunits=Altitude units
+dialog.openoptions.speedunits=Speed units
+dialog.openoptions.vertspeedunits=Vertical speed units
+dialog.openoptions.vspeed.positiveup=Positive speeds upwards
+dialog.openoptions.vspeed.positivedown=Positive speeds downwards
dialog.open.contentsdoubled=This file contains two copies of each point,\nonce as waypoints and once as track points.
dialog.selecttracks.intro=Select the track or tracks to load
dialog.selecttracks.noname=Unnamed
@@ -176,6 +182,30 @@ dialog.gpsload.save=Save to file
dialog.gpssend.sendwaypoints=Send waypoints
dialog.gpssend.sendtracks=Send tracks
dialog.gpssend.trackname=Track name
+dialog.gpsbabel.filters=Filters
+dialog.addfilter.title=Add filter
+dialog.gpsbabel.filter.discard=Discard
+dialog.gpsbabel.filter.simplify=Simplify
+dialog.gpsbabel.filter.distance=Distance
+dialog.gpsbabel.filter.interpolate=Interpolate
+dialog.gpsbabel.filter.discard.intro=Discard points if
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=Number of satellites <
+dialog.gpsbabel.filter.discard.nofix=Point has no fix
+dialog.gpsbabel.filter.discard.unknownfix=Point has unknown fix
+dialog.gpsbabel.filter.simplify.intro=Remove points until
+dialog.gpsbabel.filter.simplify.maxpoints=Number of points <
+dialog.gpsbabel.filter.simplify.maxerror=or error distance <
+dialog.gpsbabel.filter.simplify.crosstrack=cross-track
+dialog.gpsbabel.filter.simplify.length=length difference
+dialog.gpsbabel.filter.simplify.relative=relative to hdop
+dialog.gpsbabel.filter.distance.intro=Remove points if close to any previous point
+dialog.gpsbabel.filter.distance.distance=If distance <
+dialog.gpsbabel.filter.distance.time=and time difference <
+dialog.gpsbabel.filter.interpolate.intro=Add extra points between track points
+dialog.gpsbabel.filter.interpolate.distance=If distance >
+dialog.gpsbabel.filter.interpolate.time=or time difference >
dialog.saveoptions.title=Save file
dialog.save.fieldstosave=Fields to save
dialog.save.table.field=Field
@@ -192,7 +222,10 @@ dialog.exportkml.text=Title for the data
dialog.exportkml.altitude=Absolute altitudes (for aviation)
dialog.exportkml.kmz=Compress to make kmz file
dialog.exportkml.exportimages=Export image thumbnails to kmz
+dialog.exportkml.imagesize=Image size
dialog.exportkml.trackcolour=Track colour
+dialog.exportkml.standardkml=Standard KML
+dialog.exportkml.extendedkml=Extended KML with timestamps
dialog.exportgpx.name=Name
dialog.exportgpx.desc=Description
dialog.exportgpx.includetimestamps=Include timestamps
@@ -208,11 +241,23 @@ dialog.exportpov.cameraz=Camera Z
dialog.exportpov.modelstyle=Model style
dialog.exportpov.ballsandsticks=Balls and sticks
dialog.exportpov.tubesandwalls=Tubes and walls
-dialog.exportpov.warningtracksize=This track has a large number of points, which Java3D might not be able to display.\nAre you sure you want to continue?
+dialog.3d.warningtracksize=This track has a large number of points, which Java3D might not be able to display.\nAre you sure you want to continue?
+dialog.exportpov.baseimage=Base image
+dialog.exportpov.cannotmakebaseimage=Cannot write base image
+dialog.baseimage.title=Map image
+dialog.baseimage.useimage=Use image
+dialog.baseimage.mapsource=Map source
+dialog.baseimage.zoom=Zoom level
+dialog.baseimage.incomplete=Image incomplete
+dialog.baseimage.tiles=Tiles
+dialog.baseimage.size=Image size
dialog.exportsvg.text=Select the parameters for the SVG export
dialog.exportsvg.phi=Azimuth angle \u03D5
dialog.exportsvg.theta=Elevation angle \u03B8
dialog.exportsvg.gradients=Use gradients for shading
+dialog.exportimage.noimagepossible=Map images need to be cached to disk in order to use them for an export.
+dialog.exportimage.drawtrack=Draw track on map
+dialog.exportimage.textscalepercent=Text scale factor (%)
dialog.pointtype.desc=Save the following point types:
dialog.pointtype.track=Track points
dialog.pointtype.waypoint=Waypoints
@@ -232,12 +277,14 @@ dialog.undo.none.text=No operations to undo!
dialog.clearundo.title=Clear undo list
dialog.clearundo.text=Are you sure you want to clear the undo list?\nAll undo information will be lost!
dialog.pointedit.title=Edit point
-dialog.pointedit.text=Select each field to edit and use the 'Edit' button to change the value
+dialog.pointedit.text=
+dialog.pointedit.intro=Select each field in turn to view and change the value
dialog.pointedit.table.field=Field
+dialog.pointedit.nofield=No field selected
dialog.pointedit.table.value=Value
-dialog.pointedit.table.changed=Changed
-dialog.pointedit.changevalue.text=Enter the new value for this field
-dialog.pointedit.changevalue.title=Edit field
+dialog.pointedit.table.changed=
+dialog.pointedit.changevalue.text=
+dialog.pointedit.changevalue.title=
dialog.pointnameedit.name=Waypoint name
dialog.pointnameedit.uppercase=UPPER case
dialog.pointnameedit.lowercase=lower case
@@ -279,6 +326,27 @@ dialog.distances.toofewpoints=This function needs waypoints in order to calculat
dialog.fullrangedetails.intro=Here are the details for the selected range
dialog.fullrangedetails.coltotal=Including gaps
dialog.fullrangedetails.colsegments=Without gaps
+dialog.estimatetime.details=Details
+dialog.estimatetime.gentle=Gentle
+dialog.estimatetime.steep=Steep
+dialog.estimatetime.climb=Climb
+dialog.estimatetime.descent=Descent
+dialog.estimatetime.parameters=Parameters
+dialog.estimatetime.parameters.timefor=Time for
+dialog.estimatetime.results=Results
+dialog.estimatetime.results.estimatedtime=Estimated time
+dialog.estimatetime.results.actualtime=Actual time
+dialog.estimatetime.error.nodistance=The time estimates need connected track points, to give a distance
+dialog.estimatetime.error.noaltitudes=The selection doesn't include any altitude information
+dialog.learnestimationparams.intro=These are the parameters calculated from this track
+dialog.learnestimationparams.averageerror=Average error
+dialog.learnestimationparams.combine=These parameters can be combined with the current values
+dialog.learnestimationparams.combinedresults=Combined results
+dialog.learnestimationparams.weight.100pccurrent=Keep current values
+dialog.learnestimationparams.weight.current=current
+dialog.learnestimationparams.weight.calculated=calculated
+dialog.learnestimationparams.weight.50pc=Average of current values and calculated ones
+dialog.learnestimationparams.weight.100pccalculated=Use new calculated values
dialog.setmapbg.intro=Select one of the map sources, or add a new one
dialog.addmapsource.title=Add new map source
dialog.addmapsource.sourcename=Name of source
@@ -422,8 +490,7 @@ dialog.saveconfig.prune.exiftoolpath=Path to exiftool
dialog.saveconfig.prune.mapsource=Selected map source
dialog.saveconfig.prune.mapsourcelist=Map sources
dialog.saveconfig.prune.diskcache=Map cache
-dialog.saveconfig.prune.kmzimagewidth=KMZ image width
-dialog.saveconfig.prune.kmzimageheight=KMZ image height
+dialog.saveconfig.prune.kmzimagewidth=KMZ image size
dialog.saveconfig.prune.colourscheme=Colour scheme
dialog.saveconfig.prune.linewidth=Line width
dialog.saveconfig.prune.kmltrackcolour=KML track colour
@@ -478,9 +545,6 @@ dialog.searchwikipedianames.search=Search for:
# 3d window
dialog.3d.title=GpsPrune Three-d view
dialog.3d.altitudefactor=Altitude exaggeration factor
-dialog.3dlines.title=GpsPrune gridlines
-dialog.3dlines.empty=No gridlines to display!
-dialog.3dlines.intro=These are the gridlines for the three-d view
# Confirm messages
confirm.loadfile=Data loaded from file
@@ -529,7 +593,6 @@ button.cancel=Cancel
button.overwrite=Overwrite
button.moveup=Move up
button.movedown=Move down
-button.showlines=Show lines
button.edit=Edit
button.exit=Exit
button.close=Close
@@ -552,6 +615,7 @@ button.browse=Browse...
button.addnew=Add new
button.delete=Delete
button.manage=Manage
+button.combine=Combine
# File types
filetype.txt=TXT files
@@ -562,12 +626,14 @@ filetype.kmz=KMZ files
filetype.gpx=GPX files
filetype.pov=POV files
filetype.svg=SVG files
+filetype.png=PNG files
filetype.audio=MP3, OGG, WAV files
# Display components
display.nodata=No data loaded
display.noaltitudes=Track data does not include altitudes
display.notimestamps=Track data does not include timestamps
+display.novalues=Track data does not include values for this field
details.trackdetails=Track details
details.notrack=No track loaded
details.track.points=Points
@@ -638,21 +704,31 @@ units.feet=Feet
units.feet.short=ft
units.kilometres=Kilometres
units.kilometres.short=km
+units.kilometresperhour=km per hour
units.kilometresperhour.short=km/h
units.miles=Miles
units.miles.short=mi
+units.milesperhour=miles per hour
units.milesperhour.short=mph
units.nauticalmiles=Nautical miles
units.nauticalmiles.short=N.m.
units.nauticalmilesperhour.short=kts
+units.metrespersec=metres per second
units.metrespersec.short=m/s
+units.feetpersec=feet per second
units.feetpersec.short=ft/s
units.hours=hours
+units.minutes=minutes
+units.seconds=seconds
units.degminsec=Deg-min-sec
units.degmin=Deg-min
units.deg=Degrees
units.iso8601=ISO 8601
+# How to combine conditions, such as filters
+logic.and=and
+logic.or=or
+
# External urls
url.googlemaps=maps.google.co.uk
wikipedia.lang=en
@@ -721,7 +797,7 @@ error.undofailed.text=Failed to undo operation
error.function.noop.title=Function had no effect
error.rearrange.noop=Rearranging points had no effect
error.function.notavailable.title=Function not available
-error.function.nojava3d=This function requires the Java3d library,\navailable from Sun.com.
+error.function.nojava3d=This function requires the Java3d library.
error.3d=An error occurred with the 3d display
error.readme.notfound=Readme file not found
error.osmimage.dialogtitle=Error loading map images
@@ -738,3 +814,4 @@ error.cache.notthere=The tile cache directory was not found
error.cache.empty=The tile cache directory is empty
error.cache.cannotdelete=No tiles could be deleted
error.interpolate.invalidparameter=The number of points must be between 1 and 1000
+error.learnestimationparams.failed=Cannot learn the parameters from this track.\nTry loading more tracks.
diff --git a/tim/prune/lang/prune-texts_en_US.properties b/tim/prune/lang/prune-texts_en_US.properties
index b2b2fc2..928d027 100644
--- a/tim/prune/lang/prune-texts_en_US.properties
+++ b/tim/prune/lang/prune-texts_en_US.properties
@@ -12,6 +12,7 @@ dialog.setcolours.intro=Click on a color patch to change the color
# Measurement units
units.metres=Meters
units.kilometres=Kilometers
+units.metrespersec=meters per second
# External urls
url.googlemaps=maps.google.com
diff --git a/tim/prune/lang/prune-texts_es.properties b/tim/prune/lang/prune-texts_es.properties
index 77f8893..2d2f3a5 100644
--- a/tim/prune/lang/prune-texts_es.properties
+++ b/tim/prune/lang/prune-texts_es.properties
@@ -97,7 +97,6 @@ function.show3d=Mostrar en 3-D
function.distances=Distancias
function.fullrangedetails=Detalles adicionales de rango
function.setmapbg=Configurar fondo de mapa
-function.setkmzimagesize=Configurar tama\u00f1os de las im\u00e1genes KMZ
function.setpaths=Configurar rutas del programas
function.getgpsies=Bajar ruta de Gpsies
function.uploadgpsies=Subir recorrido a Gpsies
@@ -188,6 +187,7 @@ dialog.exportkml.text=Descripci\u00f3n para los datos
dialog.exportkml.altitude=Absoluta altitudes (para aviaci\u00f3n)
dialog.exportkml.kmz=Comprimir al archivo kmz
dialog.exportkml.exportimages=Exportar fotos al kmz
+dialog.exportkml.imagesize=Tama\u00f1os de las im\u00e1genes
dialog.exportkml.trackcolour=Color del track
dialog.exportgpx.name=Nombre
dialog.exportgpx.desc=Descripci\u00f3n
@@ -204,7 +204,7 @@ dialog.exportpov.cameraz=C\u00e1mara Z
dialog.exportpov.modelstyle=Estilo
dialog.exportpov.ballsandsticks=Balas en palos
dialog.exportpov.tubesandwalls=Tubos y paredes
-dialog.exportpov.warningtracksize=Este track contiene un gran numero de puntos. Puede ser que Java3D no los pueda visualizar. Est\u00e1 seguro de que desea continuar?
+dialog.3d.warningtracksize=Este track contiene un gran numero de puntos. Puede ser que Java3D no los pueda visualizar. Est\u00e1 seguro de que desea continuar?
dialog.exportsvg.text=Seleccione los par\u00e1metros para exportar a SVG
dialog.exportsvg.phi=\u00c1ngulo de azimuth \u03d5
dialog.exportsvg.theta=\u00c1ngulo de elevaci\u00f3n
@@ -465,9 +465,6 @@ dialog.searchwikipedianames.search=Buscar:
# 3d window
dialog.3d.title=GpsPrune vista 3-D
dialog.3d.altitudefactor=Factor de exageraci\u00f3n de altura
-dialog.3dlines.title=Cuadr\u00edcula GpsPrune
-dialog.3dlines.empty=¡No hay ninguna cuadr\u00edcula!
-dialog.3dlines.intro=Informaci\u00f3n de la cuadr\u00edcula
# Confirm messages
confirm.loadfile=Dato cargado de
@@ -515,7 +512,6 @@ button.cancel=Cancelar
button.overwrite=Sobreescribir
button.moveup=Mover hacia arriba
button.movedown=Mover hacia abajo
-button.showlines=Mostrar cuadr\u00edcula
button.edit=Editar
button.exit=Salir
button.close=Cerrar
@@ -701,7 +697,7 @@ error.undofailed.text=No ha sido posible deshacer la operaci\u00f3n
error.function.noop.title=La funci\u00f3n no se ha efectuado
error.rearrange.noop=Reordenaci\u00f3n de puntos no se ha efectuado
error.function.notavailable.title=Funci\u00f3n no disponible
-error.function.nojava3d=Esta funci\u00f3n requiere la librer\u00eda Java3d,\ndisponible en Sun.com.
+error.function.nojava3d=Esta funci\u00f3n requiere la librer\u00eda Java3d.
error.3d=Ha ocurrido un error con la funci\u00f3n 3-D
error.readme.notfound=Archivo readme no encontrado
error.osmimage.dialogtitle=Error al cargar el mapa
diff --git a/tim/prune/lang/prune-texts_fr.properties b/tim/prune/lang/prune-texts_fr.properties
index ab11b33..83045ea 100644
--- a/tim/prune/lang/prune-texts_fr.properties
+++ b/tim/prune/lang/prune-texts_fr.properties
@@ -100,7 +100,6 @@ function.show3d=Montrer en 3D
function.distances=Distances
function.fullrangedetails=Montrer tous les d\u00e9tails
function.setmapbg=D\u00e9finir le fond de carte
-function.setkmzimagesize=D\u00e9finir la taille de l'image KMZ
function.setpaths=D\u00e9finir les chemins des programmes
function.getgpsies=R\u00e9cup\u00e9rer les traces Gpsies
function.uploadgpsies=T\u00e9l\u00e9charger la trace sur Gpsies
@@ -192,6 +191,7 @@ dialog.exportkml.text=Titre pour les donn\u00e9es
dialog.exportkml.altitude=Altitudes absolues (pour l'aviation)
dialog.exportkml.kmz=Compresser au format kmz
dialog.exportkml.exportimages=Exporter les vignettes au format kmz
+dialog.exportkml.imagesize=Taille des images
dialog.exportkml.trackcolour=Couleur de la trace
dialog.exportgpx.name=Nom
dialog.exportgpx.desc=L\u00e9gende
@@ -208,7 +208,14 @@ dialog.exportpov.cameraz=Cam\u00e9ra Z
dialog.exportpov.modelstyle=Style du mod\u00e8le
dialog.exportpov.ballsandsticks=Points et b\u00e2tons
dialog.exportpov.tubesandwalls=Tubes et murs
-dialog.exportpov.warningtracksize=Cette trace poss\u00e8de un grand nombre de points, Java3D peut ne pas pouvoir l'afficher.\n\u00cates-vous s\u00fbr de vouloir continuer ?
+dialog.3d.warningtracksize=Cette trace poss\u00e8de un grand nombre de points, Java3D peut ne pas pouvoir l'afficher.\n\u00cates-vous s\u00fbr de vouloir continuer ?
+dialog.baseimage.title=Image de la carte
+dialog.baseimage.useimage=Utiliser image
+dialog.baseimage.mapsource=Source de cartes
+dialog.baseimage.zoom=Zoom
+dialog.baseimage.incomplete=Image incompl\u00e8te
+dialog.baseimage.tiles=Tuiles
+dialog.baseimage.size=Taille de l'image
dialog.exportsvg.text=S\u00e9lectionner les param\u00e8tres de l'export SVG
dialog.exportsvg.phi=Angle d'azimuth \u03d5
dialog.exportsvg.theta=Angle d'\u00e9l\u00e9vation \u03b8
@@ -470,6 +477,7 @@ dialog.diskcache.deleteall=Efface toute les tuiles
dialog.diskcache.deleted1=Effac\u00e9
dialog.diskcache.deleted2=tuiles du cache
dialog.deletefieldvalues.intro=Choisir le champ \u00e0 effacer pour l'\u00e9tendue actuelle
+dialog.deletefieldvalues.nofields=L'\u00e9tendue actuelle n'a pas de champs \u00e0 effacer
dialog.setlinewidth.text=Entrer l'\u00e9paisseur des lignes des traces (1-4)
dialog.downloadosm.desc=Confirmer le t\u00e9l\u00e9chargement des donn\u00e9es OSM brutes pour la zone indiqu\u00e9e :
dialog.searchwikipedianames.search=Chercher :
@@ -477,9 +485,6 @@ dialog.searchwikipedianames.search=Chercher :
# 3d window
dialog.3d.title=Vue 3D de GpsPrune
dialog.3d.altitudefactor=Facteur d'exag\u00e9ration de l'altitude
-dialog.3dlines.title=Grille de GpsPrune
-dialog.3dlines.empty=Pas de grille \u00e0 afficher !
-dialog.3dlines.intro=Ceci est la grille pour la vue 3D
# Confirm messages
confirm.loadfile=Donn\u00e9es charg\u00e9es depuis le fichier
@@ -528,7 +533,6 @@ button.cancel=Annuler
button.overwrite=\u00c9craser
button.moveup=Monter
button.movedown=Descendre
-button.showlines=Montrer les lignes
button.edit=\u00c9diter
button.exit=Terminer
button.close=Fermer
@@ -720,7 +724,7 @@ error.undofailed.text=\u00c9chec de l'op\u00e9ration d'annulation
error.function.noop.title=Fonction sans effet
error.rearrange.noop=R\u00e9arrangement des points sans effet
error.function.notavailable.title=Function non-disponible
-error.function.nojava3d=Cette fonction n\u00e9cessite la librairie Java3d,\ndisponible sur Sun.com.
+error.function.nojava3d=Cette fonction n\u00e9cessite la librairie Java3d.
error.3d=Un probl\u00e8me est survenu avec l'affichage 3D
error.readme.notfound=Fichier Lisez-moi introuvable
error.osmimage.dialogtitle=Erreur au chargement des portions de cartes
diff --git a/tim/prune/lang/prune-texts_hu.properties b/tim/prune/lang/prune-texts_hu.properties
index 837f9d9..0764c74 100644
--- a/tim/prune/lang/prune-texts_hu.properties
+++ b/tim/prune/lang/prune-texts_hu.properties
@@ -97,7 +97,6 @@ function.show3d=3D n\u00e9zet
function.distances=T\u00e1vols\u00e1gok
function.fullrangedetails=Teljes tartom\u00e1ny r\u00e9szletei
function.setmapbg=H\u00e1tt\u00e9rk\u00e9p be\u00e1ll\u00edt\u00e1sa
-function.setkmzimagesize=KMZ k\u00e9pm\u00e9ret be\u00e1ll\u00edt\u00e1sa
function.setpaths=Program\u00fatvonalak be\u00e1ll\u00edt\u00e1sa
function.getgpsies=Gpsies nyomvonalak let\u00f6lt\u00e9se
function.uploadgpsies=Nyomvonal felt\u00f6lt\u00e9se Gpsiesra
@@ -204,7 +203,7 @@ dialog.exportpov.cameraz=Z kamera
dialog.exportpov.modelstyle=Modell st\u00edlusa
dialog.exportpov.ballsandsticks=Goly\u00f3k \u00e9s botok
dialog.exportpov.tubesandwalls=Cs\u00f6vek \u00e9s falak
-dialog.exportpov.warningtracksize=Ez a nyomvonal nagy sz\u00e1m\u00fa pontot tartalmaz, amelyet a Java3D nem biztos, hogy meg tud jelen\u00edteni.\nBiztos benne, hogy folytatni szeretn\u00e9?
+dialog.3d.warningtracksize=Ez a nyomvonal nagy sz\u00e1m\u00fa pontot tartalmaz, amelyet a Java3D nem biztos, hogy meg tud jelen\u00edteni.\nBiztos benne, hogy folytatni szeretn\u00e9?
dialog.exportsvg.text=Param\u00e9terek kiv\u00e1laszt\u00e1sa az SVG exporthoz
dialog.exportsvg.phi=Ir\u00e1nysz\u00f6g \u03d5
dialog.exportsvg.theta=Emel\u00e9s sz\u00f6ge \u03b8
@@ -465,9 +464,6 @@ dialog.searchwikipedianames.search=Keres\u00e9s erre:
# 3d window
dialog.3d.title=GpsPrune 3D n\u00e9zet
dialog.3d.altitudefactor=Magass\u00e1gi ny\u00fajt\u00e1si t\u00e9nyez\u0151
-dialog.3dlines.title=GpsPrune r\u00e1csvonalak
-dialog.3dlines.empty=Nincsenek megjelen\u00edthet\u0151 r\u00e1csvonalak!
-dialog.3dlines.intro=Ezek a r\u00e1csvonalak a 3D n\u00e9zethez
# Confirm messages
confirm.loadfile=Adatok f\u00e1jlb\u00f3l bet\u00f6ltve
@@ -515,7 +511,6 @@ button.cancel=M\u00e9gse
button.overwrite=Fel\u00fcl\u00edr\u00e1s
button.moveup=Mozgat\u00e1s feljebb
button.movedown=Mozgat\u00e1s lejjebb
-button.showlines=Sorok megjelen\u00edt\u00e9se
button.edit=Szerkeszt\u00e9s
button.exit=Kil\u00e9p\u00e9s
button.close=Bez\u00e1r\u00e1s
@@ -701,7 +696,7 @@ error.undofailed.text=A m\u0171velet visszavon\u00e1sa nem siker\u00fclt
error.function.noop.title=A funkci\u00f3 nem eredm\u00e9nyezett v\u00e1ltoz\u00e1st
error.rearrange.noop=A pontok \u00fajrarendez\u00e9se nem eredm\u00e9nyezett v\u00e1ltoz\u00e1st
error.function.notavailable.title=A funkci\u00f3 nem \u00e9rhet\u0151 el
-error.function.nojava3d=Ehhez a funkci\u00f3hoz a Java3d f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r sz\u00fcks\u00e9ges,\n amely a Sun.com webhelyr\u0151l \u00e9rhet\u0151 el.
+error.function.nojava3d=Ehhez a funkci\u00f3hoz a Java3d f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r sz\u00fcks\u00e9ges.
error.3d=Hiba t\u00f6rt\u00e9nt a 3d megjelen\u00edt\u00e9ssel
error.readme.notfound=Az olvassel f\u00e1jl nem tal\u00e1lhat\u00f3
error.osmimage.dialogtitle=Hiba a t\u00e9rk\u00e9p bet\u00f6lt\u00e9sekor
diff --git a/tim/prune/lang/prune-texts_it.properties b/tim/prune/lang/prune-texts_it.properties
index 989110a..07027a0 100644
--- a/tim/prune/lang/prune-texts_it.properties
+++ b/tim/prune/lang/prune-texts_it.properties
@@ -100,7 +100,6 @@ function.show3d=Mostra in 3D
function.distances=Mostra distanze
function.fullrangedetails=Mostra dettagli
function.setmapbg=Configura sfondo mappa
-function.setkmzimagesize=Configura dimensione immagine KMZ
function.setpaths=Configura percorsi programmi
function.getgpsies=Ottieni traccie da Gpsies
function.uploadgpsies=Carica traccia su Gpsies
@@ -208,7 +207,8 @@ dialog.exportpov.cameraz=Camera Z
dialog.exportpov.modelstyle=Stile del modello
dialog.exportpov.ballsandsticks=Palle e bacchette
dialog.exportpov.tubesandwalls=Tubi e pareti
-dialog.exportpov.warningtracksize=Questa traccia ha un elevato numero di punti, e Java3D potrebbe non essere in grado di visualizzarli.\nSei sicuro di voler continuare?
+dialog.3d.warningtracksize=Questa traccia ha un elevato numero di punti, e Java3D potrebbe non essere in grado di visualizzarli.\nSei sicuro di voler continuare?
+dialog.exportkml.imagesize=Dimensione immagine
dialog.exportsvg.text=Seleziona i parametri per esportare in SVG
dialog.exportsvg.phi=Angolo orizzontale \u03d5
dialog.exportsvg.theta=Angolo di elevazione \u03b8
@@ -470,6 +470,7 @@ dialog.diskcache.deleteall=Cancellare tutti tasselli
dialog.diskcache.deleted1=Cancellati
dialog.diskcache.deleted2=files dal cache
dialog.deletefieldvalues.intro=Selezione il campo da cancellare dall'intervallo corrente
+dialog.deletefieldvalues.nofields=Nell'intervallo selezionato non ci sono campi da cancellare
dialog.setlinewidth.text=Specifica il tratteggio delle linee per disegnare la traccia (1-4)
dialog.downloadosm.desc=Conferma lo scarico dei dati raw OSM per l'area specificata:
dialog.searchwikipedianames.search=Cerca per:
@@ -477,9 +478,6 @@ dialog.searchwikipedianames.search=Cerca per:
# 3d window
dialog.3d.title=Visione GpsPrune in 3D
dialog.3d.altitudefactor=Fattore di moltiplicazione della quota
-dialog.3dlines.title=Griglia di GpsPrune
-dialog.3dlines.empty=Nessuna griglia mostrata!
-dialog.3dlines.intro=Queste sono le linee della griglia per la visione 3D
# Confirm messages
confirm.loadfile=Dati caricati da file
@@ -528,7 +526,6 @@ button.cancel=Annulla
button.overwrite=Sovrascrivi
button.moveup=Sposta in alto
button.movedown=Sposta in basso
-button.showlines=Mostra linee
button.edit=Modifica
button.exit=Esci
button.close=Chiudi
@@ -672,7 +669,6 @@ undo.removephoto=rimuovi foto
undo.removeaudio=rimuovi riprese audio
undo.deleterange=cancella l'intervallo
undo.croptrack=taglia la traccia
-undo.deletemarked=
undo.insert=inserisci punti
undo.reverse=inverti l'intervallo
undo.mergetracksegments=unisci segmenti traccia
@@ -720,7 +716,7 @@ error.undofailed.text=Impossibile annullare l'operazione
error.function.noop.title=La funzione non ha avuto effetto
error.rearrange.noop=La riorganizzazione dei punto non ha avuto effetto
error.function.notavailable.title=Funzione non disponibile
-error.function.nojava3d=Questa funzione richiede la libreria Java3d,\ndisponibile all'indirizzo Sun.com.
+error.function.nojava3d=Questa funzione richiede la libreria Java3d.
error.3d=\u00c8 avvenuto un errore nella visualizzazione 3D
error.readme.notfound=Non ho trovato il file Leggimi
error.osmimage.dialogtitle=Errore nel caricamento nelle immagini della mappa
diff --git a/tim/prune/lang/prune-texts_ja.properties b/tim/prune/lang/prune-texts_ja.properties
index 33b54c9..9739f87 100644
--- a/tim/prune/lang/prune-texts_ja.properties
+++ b/tim/prune/lang/prune-texts_ja.properties
@@ -54,6 +54,26 @@ menu.map.autopan=\u81ea\u52d5\u79fb\u52d5
menu.map.showmap=\u5730\u56f3\u3092\u8868\u793a
menu.map.showscalebar=\u7e2e\u5c3a\u8868\u793a
+# Alt keys for menus
+altkey.menu.file=F
+altkey.menu.track=T
+altkey.menu.range=R
+altkey.menu.point=P
+altkey.menu.view=V
+altkey.menu.photo=O
+altkey.menu.audio=A
+altkey.menu.settings=S
+altkey.menu.help=H
+
+# Ctrl shortcuts for menu items
+shortcut.menu.file.open=O
+shortcut.menu.file.load=L
+shortcut.menu.file.save=S
+shortcut.menu.track.undo=Z
+shortcut.menu.edit.compress=C
+shortcut.menu.range.all=A
+shortcut.menu.help.help=H
+
# Functions
function.open=\u30d5\u30a1\u30a4\u30eb\u3092\u958b\u304f
function.importwithgpsbabel=GPSBabel\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u30a4\u30f3\u30dd\u30fc\u30c8
@@ -63,6 +83,7 @@ function.exportkml=KML\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
function.exportgpx=GPX\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
function.exportpov=POV\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
function.exportsvg=SVG\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
+function.exportimage=\u30a4\u30e1\u30fc\u30b8\u3092\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
function.editwaypointname=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u306e\u540d\u524d\u3092\u7de8\u96c6
function.compress=\u30c8\u30e9\u30c3\u30af\u3092\u5727\u7e2e
function.deleterange=\u7bc4\u56f2\u3092\u524a\u9664
@@ -78,7 +99,6 @@ function.show3d=3-D\u30d3\u30e5\u30fc
function.distances=\u8ddd\u96e2
function.fullrangedetails=\u5168\u7bc4\u56f2\u8a73\u7d30
function.setmapbg=\u80cc\u666f\u5730\u56f3
-function.setkmzimagesize=KML \u30a4\u30e1\u30fc\u30b8\u30b5\u30a4\u30ba
function.setpaths=\u5916\u90e8\u30d7\u30ed\u30b0\u30e9\u30e0\u30d1\u30b9\u3092\u8a2d\u5b9a
function.getgpsies=Gpsies\u30c8\u30e9\u30c3\u30af\u3092\u5f97\u308b
function.uploadgpsies=Gpsies\u306b\u30c8\u30e9\u30c3\u30af\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
@@ -170,6 +190,7 @@ dialog.exportkml.altitude=\u7d76\u5bfe\u9ad8\u5ea6\uff08\u822a\u7a7a\u7528\uff09
dialog.exportkml.kmz=KMZ\u30d5\u30a1\u30a4\u30eb\u3092\u5727\u7e2e
dialog.exportkml.exportimages=KMZ\u3078\u753b\u50cf\u306e\u7e2e\u5c0f\u7248\u3092\u4fdd\u5b58
dialog.exportkml.trackcolour=\u30c8\u30e9\u30c3\u30af\u306e\u8272
+dialog.exportkml.standardkml=\u6a19\u6e96 KML
dialog.exportgpx.name=\u540d\u524d
dialog.exportgpx.desc=\u8a18\u8ff0
dialog.exportgpx.includetimestamps=\u6642\u9593\u8a18\u9332\u3092\u542b\u3080
@@ -185,7 +206,7 @@ dialog.exportpov.cameraz=\u30ab\u30e1\u30e9 Z
dialog.exportpov.modelstyle=\u30e2\u30c7\u30eb\u30b9\u30bf\u30a4\u30eb
dialog.exportpov.ballsandsticks=\u7403\u3068\u68d2
dialog.exportpov.tubesandwalls=\u7ba1\u3068\u58c1
-dialog.exportpov.warningtracksize=\u3053\u306e\u30c8\u30e9\u30c3\u30af\u306f\u975e\u5e38\u306b\u591a\u304f\u306e\u70b9\u304c\u3042\u308b\u306e\u3067\u3001Java3D \u3067\u306f\u8868\u793a\u3057\u5207\u308c\u306a\u3044\u3068\u601d\u308f\u308c\u307e\u3059\u3002\n\u7d9a\u3051\u307e\u3059\u304b\uff1f
+dialog.3d.warningtracksize=\u3053\u306e\u30c8\u30e9\u30c3\u30af\u306f\u975e\u5e38\u306b\u591a\u304f\u306e\u70b9\u304c\u3042\u308b\u306e\u3067\u3001Java3D \u3067\u306f\u8868\u793a\u3057\u5207\u308c\u306a\u3044\u3068\u601d\u308f\u308c\u307e\u3059\u3002\n\u7d9a\u3051\u307e\u3059\u304b\uff1f
dialog.pointtype.desc=\u6b21\u306e\u70b9\u3092\u4fdd\u5b58\u3057\u307e\u3059\uff1a
dialog.pointtype.track=\u30c8\u30e9\u30c3\u30af\u30dd\u30a4\u30f3\u30c8
dialog.pointtype.waypoint=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8
@@ -407,9 +428,6 @@ dialog.searchwikipedianames.search=\u53f3\u8a18\u3092\u691c\u7d22:
# 3d window
dialog.3d.title=GpsPrune 3D \u8868\u793a
-dialog.3dlines.title=GpsPrune \u683c\u5b50\u7dda
-dialog.3dlines.empty=\u683c\u5b50\u7dda\u304c\u8868\u793a\u3055\u308c\u307e\u305b\u3093
-dialog.3dlines.intro=\u3053\u308c\u3089\u304c 3D \u8868\u793a\u7528\u306e\u683c\u5b50\u7dda\u3067\u3059\u3002
# Confirm messages
confirm.loadfile=\u30d5\u30a1\u30a4\u30eb\u304b\u3089\u30c7\u30fc\u30bf\u3092\u8aad\u307f\u8fbc\u3080
@@ -457,7 +475,6 @@ button.cancel=\u53d6\u6d88
button.overwrite=\u4e0a\u66f8\u304d
button.moveup=\u4e0a\u3078
button.movedown=\u4e0b\u3078
-button.showlines=\u7dda\u3092\u8868\u793a
button.edit=\u7de8\u96c6
button.exit=\u7d42\u4e86
button.close=\u9589\u3058\u308b
@@ -489,6 +506,7 @@ filetype.kmz=KMZ\u30d5\u30a1\u30a4\u30eb
filetype.gpx=GPX\u30d5\u30a1\u30a4\u30eb
filetype.pov=POV\u30d5\u30a1\u30a4\u30eb
filetype.svg=SVG\u30d5\u30a1\u30a4\u30eb
+filetype.png=PNG\u30d5\u30a1\u30a4\u30eb
filetype.audio=MP3,OGG,WAV\u30d5\u30a1\u30a4\u30eb
# Display components
@@ -569,6 +587,7 @@ units.milesperhour.short=mph
units.metrespersec.short=m/s
units.feetpersec.short=ft/s
units.hours=\u6642\u9593
+units.seconds=\u79d2
units.degminsec=\u5ea6-\u5206-\u79d2
units.degmin=\u5ea6-\u5206
units.deg=\u5ea6
diff --git a/tim/prune/lang/prune-texts_ko.properties b/tim/prune/lang/prune-texts_ko.properties
index 68c5cd7..b2b4596 100644
--- a/tim/prune/lang/prune-texts_ko.properties
+++ b/tim/prune/lang/prune-texts_ko.properties
@@ -94,7 +94,6 @@ function.show3d=3\ucc28\uc6d0 \ubcf4\uae30
function.distances=\uac70\ub9ac
function.fullrangedetails=\uc5f0\uacb0\uc120 \uc0c1\uc138 \uc815\ubcf4 \ubcf4\uae30
function.setmapbg=\ubc30\uacbd \uc9c0\ub3c4 \uc9c0\uc815
-function.setkmzimagesize=KMZ \uadf8\ub9bc \ud06c\uae30 \uc9c0\uc815
function.setpaths=\uc678\ubd80\ud504\ub85c\uadf8\ub7a8 \uc9c0\uc815
function.getgpsies=gpsies\uc5d0\uc11c \ud2b8\ub799\ubaa9\ub85d \uc5bb\uae30
function.uploadgpsies=gpsies\ub85c \ud2b8\ub799 \uc62c\ub9ac\uae30
@@ -197,7 +196,7 @@ dialog.exportpov.cameraz=\uce74\uba54\ub77c\uc758 Z \uc88c\ud45c
dialog.exportpov.modelstyle=\ubaa8\ub378 \uc2a4\ud0c0\uc77c
dialog.exportpov.ballsandsticks=\ub9c9\ub300\uae30\uc640 \uacf5
dialog.exportpov.tubesandwalls=\ubcbd\uacfc \ud29c\ube0c
-dialog.exportpov.warningtracksize=\uc774 \ud2b8\ub799\uc740 \uc9c0\uc810\uc774 \ub108\ubb34 \ub9ce\uc544 Java3D\uac00 \ud45c\ud604 \ubabb\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4. /n \uadf8\ub798\ub3c4 \uacc4\uc18d \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
+dialog.3d.warningtracksize=\uc774 \ud2b8\ub799\uc740 \uc9c0\uc810\uc774 \ub108\ubb34 \ub9ce\uc544 Java3D\uac00 \ud45c\ud604 \ubabb\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4. /n \uadf8\ub798\ub3c4 \uacc4\uc18d \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
dialog.exportsvg.text=SVG\ub85c \ub0b4\ubcf4\ub0bc \ud30c\ub77c\ubbf8\ud130\ub97c \uc120\ud0dd\ud558\uc138\uc694
dialog.exportsvg.phi=\ubc29\uc704\uac01(\u03d5)
dialog.exportsvg.theta=\uace0\ub3c4(\u03b8)
@@ -442,9 +441,6 @@ dialog.searchwikipedianames.search=\ucc3e\uae30
# 3d window
dialog.3d.title=GpsPrune 3D \ubcf4\uae30
dialog.3d.altitudefactor=\uace0\ub3c4 \uacfc\uc7a5 \uacc4\uc218
-dialog.3dlines.title=GpsPrune \uaca9\uc790\uc120
-dialog.3dlines.empty=\uaca9\uc790\uc120 \uc5c6\uc774 \ud45c\uc2dc\ud558\uae30
-dialog.3dlines.intro=3D \ubcf4\uae30\ub97c \uc704\ud55c \uaca9\uc790\uc120\uc785\ub2c8\ub2e4.
# Confirm messages
confirm.loadfile=\ud30c\uc77c\uc5d0\uc11c \uc790\ub8cc\ub97c \ubd88\ub7ec\uc654\uc5b4\uc694.
@@ -492,7 +488,6 @@ button.cancel=\ucde8\uc18c
button.overwrite=\ub36e\uc5b4\uc4f0\uae30
button.moveup=\uc704\ub85c
button.movedown=\uc544\ub798\ub85c
-button.showlines=\uc120 \ubcf4\uae30
button.edit=\uc218\uc815
button.exit=\ub098\uac00\uae30
button.close=\ub2eb\uae30
diff --git a/tim/prune/lang/prune-texts_nl.properties b/tim/prune/lang/prune-texts_nl.properties
index 5a75c2a..12c9307 100644
--- a/tim/prune/lang/prune-texts_nl.properties
+++ b/tim/prune/lang/prune-texts_nl.properties
@@ -84,6 +84,7 @@ function.exportkml=Export KML
function.exportgpx=Export GPX
function.exportpov=Export POV
function.exportsvg=Export SVG
+function.exportimage=Bestand exporteren
function.editwaypointname=Hernoem waypoint
function.compress=Route comprimeren
function.deleterange=Verwijder reeks
@@ -99,8 +100,9 @@ function.charts=Diagram
function.show3d=3D beeld
function.distances=Afstanden
function.fullrangedetails=Reeks details
+function.estimatetime=Geschatte tijd
+function.learnestimationparams=Parameters voor geschatte tijd
function.setmapbg=Instellen kaart achtergrond
-function.setkmzimagesize=Instellen KMZ afbeelding grootte
function.setpaths=Instellen programmapaden
function.getgpsies=Routes van Gpsies ophalen
function.uploadgpsies=Upload routes naar Gpsies
@@ -159,6 +161,10 @@ dialog.openoptions.deliminfo.records=bestanden, met
dialog.openoptions.deliminfo.fields=velden
dialog.openoptions.deliminfo.norecords=Geen bestanden
dialog.openoptions.altitudeunits=Hoogte eenheden
+dialog.openoptions.speedunits=Snelheid eenheden
+dialog.openoptions.vertspeedunits=Verticale snelheid eenheden
+dialog.openoptions.vspeed.positiveup=Positieve snelheid omhoog
+dialog.openoptions.vspeed.positivedown=Positieve snelheid omlaag
dialog.open.contentsdoubled=Dit bestand bevat twee kopie\u00ebn van ieder punt,\neen keer als waypoint en een keer als punt
dialog.selecttracks.intro=Selecteer route of routes om te laden
dialog.selecttracks.noname=Onbenoemd
@@ -176,6 +182,30 @@ dialog.gpsload.save=Opslaan naar bestand
dialog.gpssend.sendwaypoints=Verstuur waypoint
dialog.gpssend.sendtracks=Verstuur routes
dialog.gpssend.trackname=Routenaam
+dialog.gpsbabel.filters=Filters
+dialog.addfilter.title=Filter toevoegen
+dialog.gpsbabel.filter.discard=Verwijderen
+dialog.gpsbabel.filter.simplify=Vereenvoudigen
+dialog.gpsbabel.filter.distance=Afstand
+dialog.gpsbabel.filter.interpolate=Interpoleren
+dialog.gpsbabel.filter.discard.intro=Verwijder punten indien
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop <
+dialog.gpsbabel.filter.discard.numsats=Aantal satellieten <
+dialog.gpsbabel.filter.discard.nofix=Punt heeft geen gps fix
+dialog.gpsbabel.filter.discard.unknownfix=Punt heeft onbekende gps fix
+dialog.gpsbabel.filter.simplify.intro=Verwijder punten tot
+dialog.gpsbabel.filter.simplify.maxpoints=Aantal punten <
+dialog.gpsbabel.filter.simplify.maxerror=of fout-afstand <
+dialog.gpsbabel.filter.simplify.crosstrack=cross-track
+dialog.gpsbabel.filter.simplify.length=lengteverschil
+dialog.gpsbabel.filter.simplify.relative=relatief aan hdop
+dialog.gpsbabel.filter.distance.intro=Verwijder punten indien nabij enig eerder punt
+dialog.gpsbabel.filter.distance.distance=Indien afstand <
+dialog.gpsbabel.filter.distance.time=en tijdsverschil <
+dialog.gpsbabel.filter.interpolate.intro=Voeg extra punten toe tussen route punten
+dialog.gpsbabel.filter.interpolate.distance=Indien afstand >
+dialog.gpsbabel.filter.interpolate.time=of tijdsverschil >
dialog.saveoptions.title=Bestand opslaan
dialog.save.fieldstosave=Velden op te slaan
dialog.save.table.field=Veld
@@ -192,7 +222,10 @@ dialog.exportkml.text=Titel voor de data
dialog.exportkml.altitude=Absolute hoogten (voor luchtvaart)
dialog.exportkml.kmz=Comprimeren voor kmz bestand
dialog.exportkml.exportimages=Exporteer thumbnails naar kzm
+dialog.exportkml.imagesize=Afbeeldinggrootte
dialog.exportkml.trackcolour=Routekleur
+dialog.exportkml.standardkml=Standaard KML
+dialog.exportkml.extendedkml=Uitgebreide KML met tijden
dialog.exportgpx.name=Naam
dialog.exportgpx.desc=Omschrijving
dialog.exportgpx.includetimestamps=Tijden meenemen
@@ -208,11 +241,23 @@ dialog.exportpov.cameraz=Camera Z
dialog.exportpov.modelstyle=Model stijl
dialog.exportpov.ballsandsticks=Balletjes en stokjes
dialog.exportpov.tubesandwalls=Buizen en muren
-dialog.exportpov.warningtracksize=Deze route heeft een groot aantal punten. Java3D kan deze mogelijk niet tonen.\nWeet u zeker dat u door wilt gaan?
+dialog.3d.warningtracksize=Deze route heeft een groot aantal punten. Java3D kan deze mogelijk niet tonen.\nWeet u zeker dat u door wilt gaan?
+dialog.exportpov.baseimage=Basisafbeelding
+dialog.exportpov.cannotmakebaseimage=Kan basisafbeelding niet opslaan
+dialog.baseimage.title=Basisafbeelding
+dialog.baseimage.useimage=Gebruik afbeelding
+dialog.baseimage.mapsource=Kaartbron
+dialog.baseimage.zoom=Zoom niveau
+dialog.baseimage.incomplete=Afbeelding onvolledig
+dialog.baseimage.tiles=Tegels
+dialog.baseimage.size=Afbeeldinggrootte
dialog.exportsvg.text=Selecteer de camera hoeken voor SVG export
dialog.exportsvg.phi=Azimut hoek \u03d5
dialog.exportsvg.theta=Stijgingshoek \u03b8
dialog.exportsvg.gradients=Gebruik gradaties voor schaduw
+dialog.exportimage.noimagepossible=Kaartafbeeldingen dienen gecached te worden naar disk om ze te kunnen exporteren.
+dialog.exportimage.drawtrack=Teken route op kaart
+dialog.exportimage.textscalepercent=Tekstschaal factor (%)
dialog.pointtype.desc=Sla de volgende punttypen op:
dialog.pointtype.track=Routepunten
dialog.pointtype.waypoint=Waypoints
@@ -233,7 +278,9 @@ dialog.clearundo.title=Ongedaan-maken lijst wissen
dialog.clearundo.text=Weet u zeker dat u de ongedaan-maken lijst wilt wissen?\nAlle informatie zal verloren gaan!
dialog.pointedit.title=Wijzig punt
dialog.pointedit.text=Selecteer ieder te wijzigen veld en gebruik de "Wijzigen" knop om de waarden aan te passen.
+dialog.pointedit.intro=Selecteer telkens een veld om de waarde te zien en te wijzigen
dialog.pointedit.table.field=Veld
+dialog.pointedit.nofield=Geen veld geselecteerd
dialog.pointedit.table.value=Waarde
dialog.pointedit.table.changed=Gewijzigd
dialog.pointedit.changevalue.text=Geef de nieuwe waarde voor dit veld
@@ -279,6 +326,27 @@ dialog.distances.toofewpoints=Deze functie heeft waypoints nodig om de afstand e
dialog.fullrangedetails.intro=Dit zijn de details van de geselecteerde reeks
dialog.fullrangedetails.coltotal=Inclusief hiaten
dialog.fullrangedetails.colsegments=Zonder hiaten
+dialog.estimatetime.details=Details
+dialog.estimatetime.gentle=Licht
+dialog.estimatetime.steep=Steil
+dialog.estimatetime.climb=Klimmen
+dialog.estimatetime.descent=Afdaling
+dialog.estimatetime.parameters=Parameters
+dialog.estimatetime.parameters.timefor=Tijd voor
+dialog.estimatetime.results=Resultaten
+dialog.estimatetime.results.estimatedtime=Geschatte tijd
+dialog.estimatetime.results.actualtime=Daadwerkelijke tijd
+dialog.estimatetime.error.nodistance=Tijdschattingen hebben geconnecteerde routepunten nodig om een afstand te bepalen
+dialog.estimatetime.error.noaltitudes=Deze selectie bevat geen hoogteinformatie
+dialog.learnestimationparams.intro=Dit zijn de parameters berekend voor deze route
+dialog.learnestimationparams.averageerror=Gemiddelde fout
+dialog.learnestimationparams.combine=Deze parameters kunnen gecombineerd worden met de huidige waarden
+dialog.learnestimationparams.combinedresults=Gecombineerde resultaten
+dialog.learnestimationparams.weight.100pccurrent=Behoud huidige waarden
+dialog.learnestimationparams.weight.current=huidig
+dialog.learnestimationparams.weight.calculated=berekend
+dialog.learnestimationparams.weight.50pc=Gemiddelde van huidige en berekende waarden
+dialog.learnestimationparams.weight.100pccalculated=Gebruik nieuwe berekende waarden
dialog.setmapbg.intro=Selecteer een kaart-bron, of voeg een nieuwe bron toe.
dialog.addmapsource.title=Nieuwe kaart-bron toevoegen
dialog.addmapsource.sourcename=Naam van de bron
@@ -478,9 +546,6 @@ dialog.searchwikipedianames.search=Zoeken naar:
# 3d window
dialog.3d.title=GpsPrune in 3D
dialog.3d.altitudefactor=Hoogte overdrijvingsfactor
-dialog.3dlines.title=GpsPrune raster
-dialog.3dlines.empty=Geen raster om af te beelden
-dialog.3dlines.intro=Dit is het raster voor 3D
# Confirm messages
confirm.loadfile=Data van schijf geladen
@@ -529,7 +594,6 @@ button.cancel=Annuleren
button.overwrite=Overschrijven
button.moveup=Omhoog
button.movedown=Omlaag
-button.showlines=Toon raster
button.edit=Wijzigen
button.exit=Afsluiten
button.close=Sluiten
@@ -552,6 +616,7 @@ button.browse=Browse...
button.addnew=Nieuwe toevoegen
button.delete=Verwijderen
button.manage=Beheer
+button.combine=Samenvoegen
# File types
filetype.txt=TXT bestand
@@ -562,12 +627,14 @@ filetype.kmz=KMZ bestand
filetype.gpx=GPX bestand
filetype.pov=POV bestand
filetype.svg=SVG bestand
+filetype.png=PNG bestand
filetype.audio=MP3, OGG, WAV bestanden
# Display components
display.nodata=Geen gegevens geladen
display.noaltitudes=Route gegevens bevatten geen hoogte
display.notimestamps=Route gegevens bevatten geen tijdinformatie
+display.novalues=Route gegevens bevatten geen waarden voor dit veld
details.trackdetails=Route details
details.notrack=Geen route geladen
details.track.points=Punten
@@ -638,21 +705,31 @@ units.feet=Voet
units.feet.short=ft
units.kilometres=Kilometers
units.kilometres.short=km
+units.kilometresperhour=km per uur
units.kilometresperhour.short=km/u
units.miles=Mijlen
units.miles.short=mi
+units.milesperhour=mijlen per uur
units.milesperhour.short=mph
units.nauticalmiles=Nautische mijlen
units.nauticalmiles.short=N.m.
units.nauticalmilesperhour.short=Kn
+units.metrespersec=meters per seconde
units.metrespersec.short=m/s
+units.feetpersec=feet per seconde
units.feetpersec.short=ft/s
units.hours=uren
+units.minutes=minuten
+units.seconds=seconden
units.degminsec=Grd-min-sec
units.degmin=Grd-min
units.deg=Graden
units.iso8601=ISO 8601
+# How to combine conditions, such as filters
+logic.and=en
+logic.or=of
+
# External urls
url.googlemaps=maps.google.nl
wikipedia.lang=nl
@@ -721,7 +798,7 @@ error.undofailed.text=Kon actie niet terugdraaien
error.function.noop.title=Functie had geen effect
error.rearrange.noop=Herschikken van punten had geen effect
error.function.notavailable.title=Functie niet beschikbaar
-error.function.nojava3d=Deze functie heeft Java3d nodig,\nverkrijgbaar bij sun.com.
+error.function.nojava3d=Deze functie heeft Java3d nodig.
error.3d=Er is een fout opgetreden bij de 3d afbeelding
error.readme.notfound=Leesmij bestand niet gevonden
error.osmimage.dialogtitle=Fout bij inlezen kaart afbeeldingen
@@ -738,3 +815,4 @@ error.cache.notthere=De tegelcache map niet gevonden
error.cache.empty=De tegelcache map is leeg
error.cache.cannotdelete=Er konden geen tegels verwijderd worden
error.interpolate.invalidparameter=Aantal punten moet tussen 1 en 1000 liggen
+error.learnestimationparams.failed=Kan geen parameters bepalen van deze route.\nProbeer meer routes te laden.
diff --git a/tim/prune/lang/prune-texts_pl.properties b/tim/prune/lang/prune-texts_pl.properties
index 2ff8980..7972f59 100644
--- a/tim/prune/lang/prune-texts_pl.properties
+++ b/tim/prune/lang/prune-texts_pl.properties
@@ -80,10 +80,11 @@ function.open=Otw\u00f3rz
function.importwithgpsbabel=Importuj plik z GPSBabel
function.loadfromgps=\u0141aduj z GPS
function.sendtogps=Wy\u015blij dane do urz\u0105dzenia GPS
-function.exportkml=Eksportuj KML
+function.exportkml=Eksportuj jako KML
function.exportgpx=Eksportuj jako GPX
function.exportpov=Eksportuj jako POV
function.exportsvg=Eksportuj jako SVG
+function.exportimage=Eksportuj jako obraz
function.editwaypointname=Zmie\u0144 nazw\u0119 punktu po\u015bredniego
function.compress=Kompresuj \u015bcie\u017ck\u0119
function.deleterange=Usu\u0144 zakres
@@ -99,8 +100,8 @@ function.charts=Wykres
function.show3d=Poka\u017c model 3D
function.distances=Odleg\u0142o\u015bci
function.fullrangedetails=Wszystkie detale
+function.estimatetime=Przewidywany czas
function.setmapbg=Wybierz map\u0119 t\u0142a
-function.setkmzimagesize=Ustaw rozmiar zdj\u0119\u0107 w KMZ
function.setpaths=Ustaw \u015bcie\u017cki do program\u00f3w
function.getgpsies=Pobierz \u015bcie\u017cki z Gpsies
function.uploadgpsies=Wy\u015blij \u015bcie\u017cki do Gpsies
@@ -159,6 +160,8 @@ dialog.openoptions.deliminfo.records=rekordy, z
dialog.openoptions.deliminfo.fields=pola
dialog.openoptions.deliminfo.norecords=Brak rekord\u00f3w
dialog.openoptions.altitudeunits=Jednostki wysoko\u015bci
+dialog.openoptions.speedunits=Jednostki pr\u0119dko\u015bci
+dialog.openoptions.vertspeedunits=Jednostki pr\u0119dko\u015bci pionowej
dialog.open.contentsdoubled=Ten plik zawiera dwie kopie ka\u017cdego punktu.\nRaz jako punkt po\u015bredni, a raz jako punkt \u015bcie\u017cki.
dialog.selecttracks.intro=Wybierz \u015bcie\u017ck\u0119 lub \u015bcie\u017cki
dialog.selecttracks.noname=Nienazwane
@@ -176,6 +179,30 @@ dialog.gpsload.save=Zapisz do pliku
dialog.gpssend.sendwaypoints=Wy\u015blij punkty po\u015brednie
dialog.gpssend.sendtracks=Wy\u015blij \u015bcie\u017cki
dialog.gpssend.trackname=Nazwa \u015bcie\u017cki
+dialog.gpsbabel.filters=Filtry
+dialog.addfilter.title=Dodaj filtr
+dialog.gpsbabel.filter.discard=Odrzu\u0107
+dialog.gpsbabel.filter.simplify=Upro\u015b\u0107
+dialog.gpsbabel.filter.distance=Odleg\u0142o\u015b\u0107
+dialog.gpsbabel.filter.interpolate=Wstaw pomi\u0119dzy
+dialog.gpsbabel.filter.discard.intro=Odrzu\u0107 punkty je\u015bli
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=Ilo\u015b\u0107 satelit\u00f3w <
+dialog.gpsbabel.filter.discard.nofix=Punkt bez fix-a
+dialog.gpsbabel.filter.discard.unknownfix=Punkt z nieznanym fix-em
+dialog.gpsbabel.filter.simplify.intro=Usuwaj punkty dop\u00f3ki
+dialog.gpsbabel.filter.simplify.maxpoints=Ilo\u015b\u0107 punkt\u00f3w <
+dialog.gpsbabel.filter.simplify.maxerror=lub b\u0142\u0105d odleg\u0142o\u015bci
+dialog.gpsbabel.filter.simplify.crosstrack=skrzy\u017cowane \u015bcie\u017cki
+dialog.gpsbabel.filter.simplify.length=d\u0142ugo\u015b\u0107 r\u00f3\u017cnicy
+dialog.gpsbabel.filter.simplify.relative=powi\u0105zan z Hdop
+dialog.gpsbabel.filter.distance.intro=Usu\u0144 punkty je\u015bli poprzednie punkt
+dialog.gpsbabel.filter.distance.distance=Je\u015bli odleg\u0142o\u015b\u0107 <
+dialog.gpsbabel.filter.distance.time=i r\u00f3\u017cnica w czasie <
+dialog.gpsbabel.filter.interpolate.intro=Dodaj ekstra punkty pomi\u0119dzy punktami \u015bcie\u017cki
+dialog.gpsbabel.filter.interpolate.distance=Je\u015bli odleg\u0142o\u015b\u0107 >
+dialog.gpsbabel.filter.interpolate.time=lub r\u00f3\u017cnica czasu >
dialog.saveoptions.title=Zapisz plik
dialog.save.fieldstosave=Pola do zapisu
dialog.save.table.field=Pole
@@ -192,7 +219,10 @@ dialog.exportkml.text=Tytu\u0142 dla danych
dialog.exportkml.altitude=Do\u0142\u0105cz wysoko\u015bci (dla cel\u00f3w lotniczych)
dialog.exportkml.kmz=Skompresuj do pliku KMZ
dialog.exportkml.exportimages=Eksportuj miniaturki zdj\u0119\u0107 do KMZ
+dialog.exportkml.imagesize=Rozmiar zdj\u0119\u0107
dialog.exportkml.trackcolour=Kolor \u015bcie\u017cki
+dialog.exportkml.standardkml=Standardowy KML
+dialog.exportkml.extendedkml=Standardowy KML ze znacznikami czasu (no, *extended*, not standard!)
dialog.exportgpx.name=Nazwa
dialog.exportgpx.desc=Opis
dialog.exportgpx.includetimestamps=Do\u0142\u0105cz znaczniki czasu
@@ -208,11 +238,23 @@ dialog.exportpov.cameraz=Kamera Z
dialog.exportpov.modelstyle=Styl modelu
dialog.exportpov.ballsandsticks=Kule i pa\u0142ki
dialog.exportpov.tubesandwalls=Rurki i \u015bciany
-dialog.exportpov.warningtracksize=Ta \u015bcie\u017cka ma bardzo wiele punkt\u00f3w, kt\u00f3rych Java3D mo\u017ce nie wy\u015bwietli\u0107.\nCzy chcesz kontynuowa\u0107?
+dialog.3d.warningtracksize=Ta \u015bcie\u017cka ma bardzo wiele punkt\u00f3w, kt\u00f3rych Java3D mo\u017ce nie wy\u015bwietli\u0107.\nCzy chcesz kontynuowa\u0107?
+dialog.exportpov.baseimage=Obraz podk\u0142adu
+dialog.exportpov.cannotmakebaseimage=Nie mo\u017cna zapisa\u0107 obrazu podk\u0142adu
+dialog.baseimage.title=Obrazu podk\u0142adu
+dialog.baseimage.useimage=U\u017cyj obrazu
+dialog.baseimage.mapsource=\u0179r\u00f3d\u0142o map
+dialog.baseimage.zoom=Poziom zbli\u017cenia
+dialog.baseimage.incomplete=Obraz niekompletny
+dialog.baseimage.tiles=Kafelki
+dialog.baseimage.size=Rozmiar obrazu
dialog.exportsvg.text=Wybierz parametry eksportu do pliku SVG
dialog.exportsvg.phi=azymut \u03d5
dialog.exportsvg.theta=K\u0105t wzniesienia \u03b8
dialog.exportsvg.gradients=U\u017cyj gradientu jako wype\u0142nienia
+dialog.exportimage.noimagepossible=Obrazy map musz\u0105 zosta\u0107 zapisane na dysku przed ich eksportem
+dialog.exportimage.drawtrack=Rysuj \u015bcie\u017ck\u0119 na mapie
+dialog.exportimage.textscalepercent=Wsp\u00f3\u0142czynnik skali tekstu (%)
dialog.pointtype.desc=Zapisz punkty nast\u0119puj\u0105cych typ\u00f3w:
dialog.pointtype.track=punkty \u015bcie\u017cki
dialog.pointtype.waypoint=punkty po\u015brednie
@@ -233,7 +275,9 @@ dialog.clearundo.title=Wyczy\u015b\u0107 list\u0119 zmian
dialog.clearundo.text=Czy na pewno chcesz wyczy\u015bci\u0107 list\u0119 zmian?\nWszystkie informacje o zmianach b\u0119d\u0105 utracone!
dialog.pointedit.title=Edytuj punkt
dialog.pointedit.text=Zaznacz wszystkie pola do edycji i u\u017cyj przycisku 'Edytuj' by zmieni\u0107 warto\u015bci
+dialog.pointedit.intro=Zaznacz wszystkie pola by zmieni\u0107 warto\u015bci
dialog.pointedit.table.field=Pole
+dialog.pointedit.nofield=Nie wybrano \u017cadnego pola
dialog.pointedit.table.value=Warto\u015b\u0107
dialog.pointedit.table.changed=Zmieniony
dialog.pointedit.changevalue.text=Wprowad\u017a now\u0105 warto\u015b\u0107 tego pola
@@ -279,6 +323,17 @@ dialog.distances.toofewpoints=Ta funkcja wymaga przynajmniej dw\u00f3ch punkt\u0
dialog.fullrangedetails.intro=Szczeg\u00f3\u0142y wybranego zakresu
dialog.fullrangedetails.coltotal=Z lukami
dialog.fullrangedetails.colsegments=Bez luk
+dialog.estimatetime.details=Szczeg\u00f3\u0142y
+dialog.estimatetime.gentle=\u0141agodnie
+dialog.estimatetime.steep=Stromo
+dialog.estimatetime.parameters=Parametry
+dialog.estimatetime.parameters.timefor=Czas dla
+dialog.estimatetime.results=Wynik
+dialog.estimatetime.results.estimatedtime=Czas przewidywany
+dialog.estimatetime.results.actualtime=Czas bie\u017c\u0105cy
+dialog.learnestimationparams.averageerror=B\u0142\u0105d \u015bredni
+dialog.learnestimationparams.weight.current=bie\u017c\u0105ce
+dialog.learnestimationparams.weight.calculated=obliczone
dialog.setmapbg.intro=Wybierz dostawc\u0119 map t\u0142a lub dodaj nowego
dialog.addmapsource.title=Dodaj dostawc\u0119 map
dialog.addmapsource.sourcename=Nazwa dostawcy
@@ -478,9 +533,6 @@ dialog.searchwikipedianames.search=Szukaj
# 3d window
dialog.3d.title=GpsPrune widok tr\u00f3jwymiarowy
dialog.3d.altitudefactor=Wsp\u00f3\u0142czynnik skalowania wysoko\u015bci
-dialog.3dlines.title=Linie siatki
-dialog.3dlines.empty=Brak siatki do wy\u015bwietlenia!
-dialog.3dlines.intro=Linie siatki w widoku 3D
# Confirm messages
confirm.loadfile=Za\u0142adowano dane z pliku
@@ -529,7 +581,6 @@ button.cancel=Anuluj
button.overwrite=Nadpisz
button.moveup=Do g\u00f3ry
button.movedown=Na d\u00f3\u0142
-button.showlines=Poka\u017c linie
button.edit=Edycja
button.exit=Zako\u0144cz
button.close=Zamknij
@@ -552,6 +603,7 @@ button.browse=Przegl\u0105daj...
button.addnew=Dodaj nowy
button.delete=Usu\u0144
button.manage=Zarz\u0105dzaj
+button.combine=Po\u0142\u0105cz
# File types
filetype.txt=Pliki TXT
@@ -562,12 +614,14 @@ filetype.kmz=Pliki KMZ
filetype.gpx=Pliki GPX
filetype.pov=Pliki POV
filetype.svg=Pliki SVG
+filetype.png=Pliki PNG
filetype.audio=Pliki MP3, OGG, WAV
# Display components
display.nodata=Nie za\u0142adowano danych
display.noaltitudes=\u015acie\u017cki nie zawieraj\u0105 informacji o wysoko\u015bci
display.notimestamps=\u015acie\u017cki nie zawieraj\u0105 informacji o czasie
+display.novalues=\u015acie\u017cki nie zawieraj\u0105 warto\u015bci dla tego pola
details.trackdetails=Szczeg\u00f3\u0142y \u015bcie\u017cki
details.notrack=Brak za\u0142adowanych \u015bcie\u017cek
details.track.points=Punkty
@@ -638,21 +692,31 @@ units.feet=Stopy
units.feet.short=ft
units.kilometres=Kilometry
units.kilometres.short=km
+units.kilometresperhour=km na godzin\u0119
units.kilometresperhour.short=km/h
units.miles=Mile
units.miles.short=mi
+units.milesperhour=mil na godzin\u0119
units.milesperhour.short=mi/h
units.nauticalmiles=Mile morskie
units.nauticalmiles.short=Mm
units.nauticalmilesperhour.short=w.
+units.metrespersec=metry (metr\u00f3w) na sekund\u0119
units.metrespersec.short=m/s
+units.feetpersec=stopy (st\u00f3p) na sekund\u0119
units.feetpersec.short=ft/s
units.hours=Godziny
+units.minutes=minuty
+units.seconds=sekundy
units.degminsec=Sto-min-sek
units.degmin=Sto-min
units.deg=Stopnie
units.iso8601=ISO 8601
+# How to combine conditions, such as filters
+logic.and=i
+logic.or=lub
+
# External urls
url.googlemaps=maps.google.pl
wikipedia.lang=pl
@@ -721,7 +785,7 @@ error.undofailed.text=Nie mo\u017cna cofn\u0105\u0107
error.function.noop.title=Funkcja nie ma skutku
error.rearrange.noop=Przestawienie punkt\u00f3w nie przyniesie skutku
error.function.notavailable.title=Funkcja nie dost\u0119pna
-error.function.nojava3d=Ta funkcja wymaga biblioteki Java3d,\ndost\u0119pnej na Sun.com.
+error.function.nojava3d=Ta funkcja wymaga biblioteki Java3d.
error.3d=Nast\u0105pi\u0142 b\u0142\u0105d z wy\u015bwietlaniem 3D
error.readme.notfound=Nie znaleziono pliku Readme
error.osmimage.dialogtitle=B\u0142\u0105d przy \u0142adowaniu obraz\u00f3w map
diff --git a/tim/prune/lang/prune-texts_pt.properties b/tim/prune/lang/prune-texts_pt.properties
index 1220049..1d0e3ef 100644
--- a/tim/prune/lang/prune-texts_pt.properties
+++ b/tim/prune/lang/prune-texts_pt.properties
@@ -97,7 +97,6 @@ function.show3d=Visualizar 3D
function.distances=Dist\u00e2ncias
function.fullrangedetails=Todos os detalhes
function.setmapbg=Definir como fundo do mapa
-function.setkmzimagesize=Definir tamanho da imagem KMZ
function.setpaths=Definir caminhos do programa
function.getgpsies=Obter rotas Gpsies
function.uploadgpsies=Enviar rotas para o Gpsies
@@ -204,7 +203,8 @@ dialog.exportpov.cameraz=Z da C\u00e2mera
dialog.exportpov.modelstyle=Estilo do modelo
dialog.exportpov.ballsandsticks=Bolas e galhos
dialog.exportpov.tubesandwalls=Tubos e muros
-dialog.exportpov.warningtracksize=Esta rota possui um grande n\u00famero de pontos, os quais o Java3D n\u00e3o ser\u00e1 capaz de exibir.\n Voc\u00ea tem certeza que deseja continuar?
+dialog.3d.warningtracksize=Esta rota possui um grande n\u00famero de pontos, os quais o Java3D n\u00e3o ser\u00e1 capaz de exibir.\n Voc\u00ea tem certeza que deseja continuar?
+dialog.exportkml.imagesize=Tamanho da imagem
dialog.exportsvg.text=Selecione os par\u00e2metros para a exporta\u00e7\u00e3o para o SVG
dialog.exportsvg.phi=\u00c2ngulo do azimute \u03d5
dialog.exportsvg.theta=\u00c2ngulo da eleva\u00e7\u00e3o \u03b8
@@ -465,9 +465,6 @@ dialog.searchwikipedianames.search=Procurar por:
# 3d window
dialog.3d.title=Vista 3D do GpsPrune
dialog.3d.altitudefactor=Fator de exagera\u00e7\u00e3o de altitude
-dialog.3dlines.title=Linhas da grade do GpsPrune
-dialog.3dlines.empty=Nenhuma linha de grade para exibir!
-dialog.3dlines.intro=Estas s\u00e3o as linhas da grade para a vista 3D.
# Confirm messages
confirm.loadfile=Dados carregados do arquivo
@@ -515,7 +512,6 @@ button.cancel=Cancelar
button.overwrite=Sobrescrever
button.moveup=Mover acima
button.movedown=Mover abaixo
-button.showlines=Mostrar linhas
button.edit=Editar
button.exit=Sair
button.close=Fechar
@@ -702,7 +698,7 @@ error.undofailed.text=Falha para desfazer opera\u00e7\u00e3o
error.function.noop.title=Fun\u00e7\u00e3o sem nenhum efeito
error.rearrange.noop=Rearruma\u00e7\u00e3o de pontos n\u00e3o teve efeito
error.function.notavailable.title=Fun\u00e7\u00e3o n\u00e3o dispon\u00edvel
-error.function.nojava3d=Esta fun\u00e7\u00e3o precisa da biblioteca Java3d,\ndispon\u00edvel em Sun.com
+error.function.nojava3d=Esta fun\u00e7\u00e3o precisa da biblioteca Java3d
error.3d=Um erro ocorreu com a exibi\u00e7\u00e3o 3D
error.readme.notfound=Arquivo Leiame n\u00e3o encontrado
error.osmimage.dialogtitle=Erro ao carregar imagens do mapa
diff --git a/tim/prune/lang/prune-texts_ru.properties b/tim/prune/lang/prune-texts_ru.properties
index c169538..fc85128 100644
--- a/tim/prune/lang/prune-texts_ru.properties
+++ b/tim/prune/lang/prune-texts_ru.properties
@@ -100,7 +100,6 @@ function.show3d=3D-\u0432\u0438\u0434
function.distances=\u0420\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f
function.fullrangedetails=\u0414\u0435\u0442\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u043e \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0443
function.setmapbg=\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u043a\u0430\u0440\u0442\u0443-\u043f\u043e\u0434\u043b\u043e\u0436\u043a\u0443
-function.setkmzimagesize=\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0440 KMZ-\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f
function.setpaths=\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u0443\u0442\u0438 \u043a \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430\u043c
function.getgpsies=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0442\u0440\u0435\u043a\u0438
function.uploadgpsies=\u0412\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0442\u0440\u0435\u043a \u043d\u0430 gpsies.com
@@ -208,7 +207,7 @@ dialog.exportpov.cameraz=\u041a\u0430\u043c\u0435\u0440\u0430 Z
dialog.exportpov.modelstyle=\u0421\u0442\u0438\u043b\u044c \u043c\u043e\u0434\u0435\u043b\u0438
dialog.exportpov.ballsandsticks=\u041c\u044f\u0447\u0438 \u0438 \u043f\u0430\u043b\u043e\u0447\u043a\u0438
dialog.exportpov.tubesandwalls=\u0422\u0440\u0443\u0431\u044b \u0438 \u0441\u0442\u0435\u043d\u044b
-dialog.exportpov.warningtracksize=\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0432 \u0442\u0440\u0435\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u043e\u0447\u0435\u043a - Java3D \u043c\u043e\u0436\u0435\u0442 \u0435\u0433\u043e \u043d\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c!\n\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c?
+dialog.3d.warningtracksize=\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0432 \u0442\u0440\u0435\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u043e\u0447\u0435\u043a - Java3D \u043c\u043e\u0436\u0435\u0442 \u0435\u0433\u043e \u043d\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c!\n\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c?
dialog.exportsvg.text=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0430 SVG
dialog.exportsvg.phi=\u0410\u0437\u0438\u043c\u0443\u0442 \u03d5
dialog.exportsvg.theta=\u0423\u0433\u043e\u043b \u03b8
@@ -230,7 +229,7 @@ dialog.undo.pretext=\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430
dialog.undo.none.title=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
dialog.undo.none.text=\u041d\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u043c\u0435\u043d\u044b
dialog.clearundo.title=\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u043c\u0435\u043d\u044b
-dialog.clearundo.text=\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u043c\u0435\u043d\u044b? n\u0421\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0438\u0449\u0435\u043d \u043d\u0430\u0432\u0441\u0435\u0433\u0434\u0430!
+dialog.clearundo.text=\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u043c\u0435\u043d\u044b?\n\u0421\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0438\u0449\u0435\u043d \u043d\u0430\u0432\u0441\u0435\u0433\u0434\u0430!
dialog.pointedit.title=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u0447\u043a\u0443
dialog.pointedit.text=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043e\u043b\u0435 \u0434\u043b\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 «\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c» \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f
dialog.pointedit.table.field=\u041f\u043e\u043b\u0435
@@ -478,9 +477,6 @@ dialog.searchwikipedianames.search=\u041f\u043e\u0438\u0441\u043a \u0434\u043b\u
# 3d window
dialog.3d.title=GpsPrune 3D-\u0432\u0438\u0434
dialog.3d.altitudefactor=\u041a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u043f\u043e \u0432\u044b\u0441\u043e\u0442\u0435
-dialog.3dlines.title=\u0421\u0435\u0442\u043a\u0430 GpsPrune
-dialog.3dlines.empty=\u041d\u0435\u0442 \u0441\u0435\u0442\u043a\u0438 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f!
-dialog.3dlines.intro=\u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0441\u0435\u0442\u043a\u0438 3D-\u0432\u0438\u0434\u0430
# Confirm messages
confirm.loadfile=\u0414\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u044b
@@ -529,7 +525,6 @@ button.cancel=\u041e\u0442\u043c\u0435\u043d\u0430
button.overwrite=\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c
button.moveup=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432\u0432\u0435\u0440\u0445
button.movedown=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u0437
-button.showlines=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043b\u0438\u043d\u0438\u0438
button.edit=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c
button.exit=\u0412\u044b\u0445\u043e\u0434
button.close=\u0417\u0430\u043a\u0440\u044b\u0442\u044c
@@ -562,6 +557,7 @@ filetype.kmz=KMZ \u0444\u0430\u0439\u043b\u044b
filetype.gpx=GPX \u0444\u0430\u0439\u043b\u044b
filetype.pov=POV \u0444\u0430\u0439\u043b\u044b
filetype.svg=SVG \u0444\u0430\u0439\u043b\u044b
+filetype.png=PNG \u0444\u0430\u0439\u043b\u044b
filetype.audio=MP3, OGG, WAV \u0444\u0430\u0439\u043b\u044b
# Display components
diff --git a/tim/prune/lang/prune-texts_tr.properties b/tim/prune/lang/prune-texts_tr.properties
index d098366..da661a9 100644
--- a/tim/prune/lang/prune-texts_tr.properties
+++ b/tim/prune/lang/prune-texts_tr.properties
@@ -87,7 +87,6 @@ function.show3d=3B g\u00fcr\u00fcnt\u00fcs\u00fc
function.distances=Uzakl\u0131klar
function.fullrangedetails=S\u0131ran\u0131n b\u00fct\u00fcn ayr\u0131nt\u0131lar
function.setmapbg=Arkafonun haritas\u0131 se\u00e7
-function.setkmzimagesize=KMZ resim boyutu ayarla
function.setpaths=Uygulamalar\u0131n yollar\u0131 ayarla
function.getgpsies=Gpsies.com'dan yolu al
function.duplicatepoint=Noktay\u0131 kopyala
@@ -157,6 +156,7 @@ dialog.exportkml.text=Verinin ba\u015fl\u0131\u011f\u0131
dialog.exportkml.altitude=Absolut y\u00fckseklikleri (u\u00e7u\u015f i\u00e7in)
dialog.exportkml.kmz=kmz dosyas\u0131 olu\u015fturmak i\u00e7in s\u0131k\u0131\u015ft\u0131r
dialog.exportkml.exportimages=Fotolar\u0131n t\u0131rnak resimleri kmz dosyada dahil et
+dialog.exportkml.imagesize=Resim boyutu
dialog.exportkml.trackcolour=\u0130z rengi
dialog.exportgpx.name=Ad\u0131
dialog.exportgpx.desc=A\u00e7\u0131klama
@@ -303,7 +303,6 @@ button.cancel=\u0130ptal
button.overwrite=\u00dczerinde yaz
button.moveup=Yukar\u0131
button.movedown=A\u015fa\u011f\u0131
-button.showlines=Çizgiler g\u00f6r\u00fcnt\u00fcle
button.edit=D\u00fczenle
button.exit=Ç\u0131k\u0131\u015f
button.close=Kapat
diff --git a/tim/prune/lang/prune-texts_zh.properties b/tim/prune/lang/prune-texts_zh.properties
index 6068744..d193691 100644
--- a/tim/prune/lang/prune-texts_zh.properties
+++ b/tim/prune/lang/prune-texts_zh.properties
@@ -84,6 +84,7 @@ function.exportkml=\u8f93\u51faKML\u6587\u4ef6
function.exportgpx=\u8f93\u51faGPX\u6587\u4ef6
function.exportpov=\u8f93\u51faPOV\u6587\u4ef6
function.exportsvg=\u8f93\u51faSVG\u6587\u4ef6
+function.exportimage=\u8f93\u51fa\u56fe\u50cf
function.editwaypointname=\u7f16\u8f91\u822a\u70b9\u540d
function.compress=\u538b\u7f29\u8f68\u8ff9(\u6807\u8bb0\u8981\u5220\u9664\u822a\u70b9)
function.deleterange=\u5220\u9664\u8f68\u8ff9\u70b9\u6bb5
@@ -92,15 +93,16 @@ function.interpolate=\u91cd\u53e0\u8f68\u8ff9\u70b9
function.addtimeoffset=\u52a0\u5165\u65f6\u95f4\u5dee
function.addaltitudeoffset=\u52a0\u5165\u9ad8\u5ea6\u504f\u79fb
function.convertnamestotimes=\u822a\u70b9\u540d\u79f0\u8f6c\u4e3a\u65f6\u95f4
-function.deletefieldvalues=\u5220\u9664\u533a\u57df\u6570\u503c
+function.deletefieldvalues=\u5220\u9664\u5b57\u6bb5\u503c
function.findwaypoint=\u67e5\u627e\u822a\u70b9
function.pastecoordinates=\u8f93\u5165\u65b0\u5750\u6807
function.charts=\u56fe\u8868
function.show3d=3D\u89c6\u56fe
function.distances=\u8ddd\u79bb
function.fullrangedetails=\u5168\u822a\u6bb5\u8be6\u7ec6\u4fe1\u606f
+function.estimatetime=\u4f30\u8ba1\u65f6\u95f4
+function.learnestimationparams=\u4f7f\u7528\u5f53\u524d\u8f68\u8ff9\u53c2\u6570\u4f30\u8ba1\u65f6\u95f4
function.setmapbg=\u80cc\u666f\u5730\u56fe
-function.setkmzimagesize=\u8bbe\u7f6eKMZ\u56fe\u50cf\u5c3a\u5bf8
function.setpaths=\u8bbe\u7f6e\u7a0b\u5e8f\u8def\u5f84
function.getgpsies=\u83b7\u53d6Gpsies\u8f68\u8ff9
function.uploadgpsies=\u4e0a\u4f20\u8f68\u8ff9\u5230Gpsies
@@ -146,10 +148,10 @@ dialog.deletephoto.deletepoint=\u5220\u9664\u94fe\u63a5\u5230\u7167\u7247\u7684\
dialog.deleteaudio.deletepoint=\u5220\u9664\u94fe\u63a5\u5230\u97f3\u9891\u7684\u8f68\u8ff9\u70b9\uff1f
dialog.openoptions.title=\u6253\u5f00\u9009\u9879
dialog.openoptions.filesnippet=\u63d0\u53d6\u6587\u4ef6\u7247\u6bb5
-dialog.load.table.field=\u6570\u636e\u6bb5
+dialog.load.table.field=\u5b57\u6bb5
dialog.load.table.datatype=\u6570\u636e\u7c7b\u578b
dialog.load.table.description=\u63cf\u8ff0
-dialog.delimiter.label=\u6570\u636e\u6bb5\u5206\u9694\u7b26
+dialog.delimiter.label=\u5b57\u6bb5\u5206\u9694\u7b26
dialog.delimiter.comma=\u9017\u53f7
dialog.delimiter.tab=Tab
dialog.delimiter.space=\u7a7a\u683c
@@ -159,6 +161,10 @@ dialog.openoptions.deliminfo.records=\u6761\u8bb0\u5f55\uff0c
dialog.openoptions.deliminfo.fields=\u6570\u636e\u6bb5
dialog.openoptions.deliminfo.norecords=\u65e0\u7eaa\u5f55
dialog.openoptions.altitudeunits=\u9ad8\u5ea6\u5355\u4f4d
+dialog.openoptions.speedunits=\u901f\u5ea6\u5355\u4f4d
+dialog.openoptions.vertspeedunits=\u5782\u76f4\u901f\u5ea6\u5355\u4f4d
+dialog.openoptions.vspeed.positiveup=\u4e0a\u5347\u4e3a\u6b63
+dialog.openoptions.vspeed.positivedown=\u4e0b\u964d\u4e3a\u6b63
dialog.open.contentsdoubled=\u6587\u4ef6\u542b\u6709\u4e24\u5957\u70b9\u4fe1\u606f\uff0c\u4e00\u5957\u822a\u70b9\u4fe1\u606f\u548c\u4e00\u5957\u8f68\u8ff9\u70b9\u4fe1\u606f
dialog.selecttracks.intro=\u9009\u62e9\u8981\u5bfc\u5165\u7684\u8f68\u8ff9
dialog.selecttracks.noname=\u672a\u547d\u540d
@@ -176,6 +182,30 @@ dialog.gpsload.save=\u4fdd\u5b58\u5230\u6587\u4ef6
dialog.gpssend.sendwaypoints=\u53d1\u9001\u822a\u70b9
dialog.gpssend.sendtracks=\u53d1\u9001\u8f68\u8ff9
dialog.gpssend.trackname=\u8f68\u8ff9\u540d
+dialog.gpsbabel.filters=\u7b5b\u9009\u6761\u4ef6
+dialog.addfilter.title=\u6dfb\u52a0\u7b5b\u9009\u6761\u4ef6
+dialog.gpsbabel.filter.discard=\u820d\u5f03
+dialog.gpsbabel.filter.simplify=\u7b80\u5316
+dialog.gpsbabel.filter.distance=\u8ddd\u79bb
+dialog.gpsbabel.filter.interpolate=\u63d2\u503c
+dialog.gpsbabel.filter.discard.intro=\u820d\u5f03\u8f68\u8ff9\u70b9\u5f53
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=\u536b\u661f\u6570 <
+dialog.gpsbabel.filter.discard.nofix=\u8f68\u8ff9\u70b9\u672a\u5b9a\u4f4d
+dialog.gpsbabel.filter.discard.unknownfix=\u8f68\u8ff9\u70b9\u5b9a\u4f4d\u4e0d\u660e
+dialog.gpsbabel.filter.simplify.intro=\u5220\u9664\u6240\u6709\u8f68\u8ff9\u70b9\u76f4\u5230
+dialog.gpsbabel.filter.simplify.maxpoints=\u8f68\u8ff9\u70b9\u6570 <
+dialog.gpsbabel.filter.simplify.maxerror=\u6216\u8bef\u5dee\u8ddd\u79bb <
+dialog.gpsbabel.filter.simplify.crosstrack=\u504f\u822a
+dialog.gpsbabel.filter.simplify.length=\u957f\u5ea6\u5dee
+dialog.gpsbabel.filter.simplify.relative=\u76f8\u5bf9hdop
+dialog.gpsbabel.filter.distance.intro=\u79fb\u9664\u4e0e\u524d\u4e00\u70b9\u8ddd\u79bb\u592a\u8fd1\u7684\u8f68\u8ff9\u70b9
+dialog.gpsbabel.filter.distance.distance=\u5f53\u8ddd\u79bb <
+dialog.gpsbabel.filter.distance.time=\u4e14\u65f6\u95f4\u5dee <
+dialog.gpsbabel.filter.interpolate.intro=\u63d2\u5165\u4e2d\u95f4\u70b9
+dialog.gpsbabel.filter.interpolate.distance=\u5f53\u8ddd\u79bb >
+dialog.gpsbabel.filter.interpolate.time=\u6216\u65f6\u95f4\u5dee >
dialog.saveoptions.title=\u4fdd\u5b58
dialog.save.fieldstosave=\u4fdd\u5b58\u6570\u636e\u6bb5
dialog.save.table.field=\u6570\u636e\u6bb5
@@ -192,7 +222,10 @@ dialog.exportkml.text=\u6570\u636e\u540d\u79f0
dialog.exportkml.altitude=\u7edd\u5bf9\u9ad8\u5ea6(\u822a\u7a7a\u7528)
dialog.exportkml.kmz=\u538b\u7f29\u6210KMZ\u6587\u4ef6
dialog.exportkml.exportimages=\u8f93\u51fa\u7f29\u7565\u56fe\u81f3KMZ
+dialog.exportkml.imagesize=\u56fe\u50cf\u5c3a\u5bf8
dialog.exportkml.trackcolour=\u8f68\u8ff9\u989c\u8272
+dialog.exportkml.standardkml=\u6807\u51c6KML
+dialog.exportkml.extendedkml=\u5e26\u65f6\u95f4\u6233\u8bb0\u7684\u6269\u5c55KML
dialog.exportgpx.name=\u540d\u79f0
dialog.exportgpx.desc=\u63cf\u8ff0
dialog.exportgpx.includetimestamps=\u5305\u542b\u65f6\u95f4
@@ -208,11 +241,23 @@ dialog.exportpov.cameraz=\u76f8\u673aZ\u5750\u6807
dialog.exportpov.modelstyle=\u6a21\u578b\u7c7b\u578b
dialog.exportpov.ballsandsticks=\u7403\u6746\u6a21\u578b
dialog.exportpov.tubesandwalls=\u7ba1\u5899\u6a21\u578b
-dialog.exportpov.warningtracksize=\u8f68\u8ff9\u542b\u6709\u592a\u591a\u822a\u70b9\uff0cJAVA3D\u53ef\u80fd\u65e0\u6cd5\u663e\u793a\u3002\n\u662f\u5426\u7ee7\u7eed\uff1f
+dialog.3d.warningtracksize=\u8f68\u8ff9\u542b\u6709\u592a\u591a\u822a\u70b9\uff0cJAVA3D\u53ef\u80fd\u65e0\u6cd5\u663e\u793a\u3002\n\u662f\u5426\u7ee7\u7eed\uff1f
+dialog.exportpov.baseimage=\u57fa\u7840\u56fe
+dialog.exportpov.cannotmakebaseimage=\u65e0\u6cd5\u4fdd\u5b58\u57fa\u7840\u56fe
+dialog.baseimage.title=\u8bbe\u7f6e\u57fa\u7840\u56fe
+dialog.baseimage.useimage=\u4f7f\u7528\u6b64\u56fe\u50cf
+dialog.baseimage.mapsource=\u5730\u56fe\u6e90
+dialog.baseimage.zoom=\u7f29\u653e\u7ea7\u522b
+dialog.baseimage.incomplete=\u56fe\u50cf\u4e0d\u5b8c\u6574
+dialog.baseimage.tiles=\u5730\u56fe\u5757
+dialog.baseimage.size=\u56fe\u50cf\u5c3a\u5bf8
dialog.exportsvg.text=\u9009\u62e9\u8f93\u51faSVG\u6587\u4ef6\u7684\u53c2\u6570
dialog.exportsvg.phi=\u65b9\u4f4d\u89d2
dialog.exportsvg.theta=\u4ef0\u89d2
dialog.exportsvg.gradients=\u4f7f\u7528\u6e10\u53d8\u8272
+dialog.exportimage.noimagepossible=\u8f93\u51fa\u7684\u5730\u56fe\u56fe\u50cf\u9996\u5148\u9700\u8981\u7f13\u5b58\u5728\u672c\u5730\u78c1\u76d8\u4e0a
+dialog.exportimage.drawtrack=\u7ed8\u51fa\u8f68\u8ff9
+dialog.exportimage.textscalepercent=\u6587\u5b57\u7f29\u653e\u6bd4\u4f8b (%)
dialog.pointtype.desc=\u4fdd\u5b58\u4e0b\u5217\u70b9\uff1a
dialog.pointtype.track=\u8f68\u8ff9\u70b9
dialog.pointtype.waypoint=\u822a\u70b9
@@ -232,12 +277,10 @@ dialog.undo.none.text=\u65e0\u64cd\u4f5c\u53ef\u64a4\u9500
dialog.clearundo.title=\u6e05\u9664\u64a4\u9500\u64cd\u4f5c\u6e05\u5355
dialog.clearundo.text=\u662f\u5426\u786e\u5b9e\u8981\u6e05\u9664\u64a4\u9500\u64cd\u4f5c\u6e05\u5355\uff1f\n\u64a4\u9500\u64cd\u4f5c\u4fe1\u606f\u4f1a\u4e22\u5931\uff01
dialog.pointedit.title=\u7f16\u8f91\u8f68\u8ff9\u70b9
-dialog.pointedit.text=\u9009\u62e9\u8981\u7f16\u8f91\u7684\u533a\u57df\u5e76\u7528\u201c\u7f16\u8f91\u201d\u952e\u6539\u53d8\u6570\u503c
-dialog.pointedit.table.field=\u6570\u636e\u6bb5
+dialog.pointedit.intro=\u4f9d\u6b21\u9009\u4e2d\u5b57\u6bb5\u4ee5\u67e5\u770b\u53ca\u8c03\u6574\u6570\u503c
+dialog.pointedit.table.field=\u5b57\u6bb5
+dialog.pointedit.nofield=\u672a\u9009\u4e2d\u5b57\u6bb5
dialog.pointedit.table.value=\u6570\u503c
-dialog.pointedit.table.changed=\u5df2\u6539\u53d8
-dialog.pointedit.changevalue.text=\u8f93\u5165\u65b0\u6570\u503c
-dialog.pointedit.changevalue.title=\u7f16\u8f91\u6570\u636e\u6bb5
dialog.pointnameedit.name=\u822a\u70b9\u540d\u79f0
dialog.pointnameedit.uppercase=\u4e0a\u6863\u952e
dialog.pointnameedit.lowercase=\u4e0b\u6863\u952e
@@ -279,6 +322,27 @@ dialog.distances.toofewpoints=\u9700\u8981\u822a\u70b9\u6765\u8ba1\u7b97\u8ddd\u
dialog.fullrangedetails.intro=\u822a\u6bb5\u8be6\u60c5
dialog.fullrangedetails.coltotal=\u5305\u542b\u95f4\u65ad
dialog.fullrangedetails.colsegments=\u4e0d\u5305\u542b\u95f4\u65ad
+dialog.estimatetime.details=\u8be6\u60c5
+dialog.estimatetime.gentle=\u5e73\u7f13
+dialog.estimatetime.steep=\u9661\u5ced
+dialog.estimatetime.climb=\u4e0a\u5347
+dialog.estimatetime.descent=\u4e0b\u964d
+dialog.estimatetime.parameters=\u53c2\u6570
+dialog.estimatetime.parameters.timefor=\u9700\u65f6
+dialog.estimatetime.results=\u7ed3\u679c
+dialog.estimatetime.results.estimatedtime=\u4f30\u8ba1\u65f6\u95f4
+dialog.estimatetime.results.actualtime=\u5b9e\u9645\u7528\u65f6
+dialog.estimatetime.error.nodistance=\u4f30\u8ba1\u65f6\u95f4\u9700\u8981\u8fde\u7eed\u7684\u8f68\u8ff9
+dialog.estimatetime.error.noaltitudes=\u9009\u4e2d\u90e8\u5206\u4e0d\u5305\u542b\u9ad8\u5ea6\u4fe1\u606f
+dialog.learnestimationparams.intro=\u6b64\u8f68\u8ff9\u8ba1\u7b97\u5f97\u5230\u7684\u53c2\u6570\u4e3a
+dialog.learnestimationparams.averageerror=\u5e73\u5747\u8bef\u5dee
+dialog.learnestimationparams.combine=\u8fd9\u4e9b\u53c2\u6570\u53ef\u4e0e\u5f53\u524d\u6570\u503c\u5408\u5e76
+dialog.learnestimationparams.combinedresults=\u5408\u5e76\u7ed3\u679c
+dialog.learnestimationparams.weight.100pccurrent=\u4fdd\u6301\u5f53\u524d\u6570\u503c
+dialog.learnestimationparams.weight.current=\u5f53\u524d\u503c
+dialog.learnestimationparams.weight.calculated=\u8ba1\u7b97\u503c
+dialog.learnestimationparams.weight.50pc=\u5e73\u5747\u503c
+dialog.learnestimationparams.weight.100pccalculated=\u4f7f\u7528\u65b0\u8ba1\u7b97\u503c
dialog.setmapbg.intro=\u8bf7\u9009\u62e9\u5730\u56fe\uff0c\u6216\u6dfb\u52a0\u5730\u56fe
dialog.addmapsource.title=\u6dfb\u52a0\u5730\u56fe
dialog.addmapsource.sourcename=\u5730\u56fe\u6765\u6e90\u540d\u79f0
@@ -364,7 +428,7 @@ dialog.deletemarked.nonefound=\u65e0\u6cd5\u5220\u9664\u6570\u636e\u70b9
dialog.pastecoordinates.desc=\u5728\u6b64\u8f93\u5165\u6216\u7c98\u8d34\u5750\u6807\u70b9
dialog.pastecoordinates.coords=\u5750\u6807\u70b9
dialog.pastecoordinates.nothingfound=\u8bf7\u68c0\u67e5\u5750\u6807\u6570\u636e\u5e76\u91cd\u8bd5
-dialog.help.help=\u66f4\u591a\u4fe1\u606f\u548c\u7528\u6cd5\uff0c\u8bf7\u53c2\u8003\u7f51\u7ad9\nhttp://activityworkshop.net/software/gpsprune/
+dialog.help.help=\u66f4\u591a\u4fe1\u606f\u548c\u7528\u6cd5\uff0c\u8bf7\u53c2\u8003\u7f51\u7ad9\nhttp://gpsprune.activityworkshop.net///
dialog.about.version=\u7248\u672c
dialog.about.build=Build
dialog.about.summarytext1=GpsPrune\u662f\u4e00\u4e2a\u4eceGPS\u4e2d\u5bfc\u5165\u6570\u636e\uff0c\u663e\u793a\u6570\u636e\u548c\u7f16\u8f91\u6570\u636e\u7684\u8f6f\u4ef6
@@ -403,7 +467,7 @@ dialog.checkversion.newversion1=\u53d1\u73b0\u65b0\u7248\u672c\uff01\u6700\u65b0
dialog.checkversion.newversion2=
dialog.checkversion.releasedate1=\u65b0\u7248\u672c\u53d1\u884c\u4e8e
dialog.checkversion.releasedate2=
-dialog.checkversion.download=\u4e0b\u8f7d\u6700\u65b0\u7248\u672c\uff0c\u8bf7\u767b\u9646\u7f51\u7ad9\uff1a\nhttp:activityworkshop.net/software/gpsprune/download.html
+dialog.checkversion.download=\u4e0b\u8f7d\u6700\u65b0\u7248\u672c\uff0c\u8bf7\u767b\u9646\u7f51\u7ad9\uff1a\nhttp:gpsprune.activityworkshop.net/download.html
dialog.keys.intro=\u53ef\u7528\u4e0b\u5217\u5feb\u6377\u952e\u66ff\u4ee3\u9f20\u6807
dialog.keys.keylist=
\u7bad\u5934
\u4e0a\u4e0b\u5de6\u53f3\u79fb\u52a8\u5730\u56fe
Ctrl + \u5de6\u53f3\u7bad\u5934
\u9009\u53d6\u524d\uff0c\u540e\u70b9
Ctrl + \u4e0a\u4e0b\u7bad\u5934
\u653e\u5927\u7f29\u5c0f
Ctrl + PgUp, PgDown
\u9009\u62e9\u524d\u540e\u6bb5
Ctrl + Home, End
\u9009\u62e9\u9996\u672b\u70b9
Del
\u5220\u9664\u5f53\u524d\u70b9
dialog.keys.normalmodifier=Ctrl
@@ -469,8 +533,8 @@ dialog.diskcache.maximumage=\u6700\u957f\u65f6\u95f4(\u5929)
dialog.diskcache.deleteall=\u5220\u9664\u6240\u6709\u5730\u56fe\u5757
dialog.diskcache.deleted1=\u5df2\u5220\u9664
dialog.diskcache.deleted2=\u7f13\u5b58\u5185\u6587\u4ef6
-dialog.deletefieldvalues.intro=\u9009\u62e9\u5f53\u524d\u8303\u56f4\u5185\u8981\u5220\u9664\u7684\u6570\u636e
-dialog.deletefieldvalues.nofields=\u9009\u5b9a\u8303\u56f4\u5185\u6ca1\u6709\u8981\u5220\u9664\u7684\u6570\u636e
+dialog.deletefieldvalues.intro=\u9009\u62e9\u5f53\u524d\u8303\u56f4\u5185\u8981\u5220\u9664\u7684\u5b57\u6bb5
+dialog.deletefieldvalues.nofields=\u9009\u5b9a\u8303\u56f4\u5185\u6ca1\u6709\u8981\u5220\u9664\u7684\u5b57\u6bb5
dialog.setlinewidth.text=\u8f93\u5165\u8f68\u8ff9\u7ebf\u5bbd\u50cf\u7d20\u503c(1-4)
dialog.downloadosm.desc=\u786e\u8ba4\u4eceOSM\u4e0b\u8f7d\u8be5\u5730\u533a\u539f\u59cb\u6570\u636e:
dialog.searchwikipedianames.search=\u67e5\u627e:
@@ -478,9 +542,6 @@ dialog.searchwikipedianames.search=\u67e5\u627e:
# 3d window
dialog.3d.title=GpsPrune 3D \u663e\u793a
dialog.3d.altitudefactor=\u9ad8\u5ea6\u653e\u5927\u7cfb\u6570
-dialog.3dlines.title=GpsPrune \u7f51\u683c\u7ebf
-dialog.3dlines.empty=\u65e0\u6cd5\u663e\u793a\u7f51\u683c\u7ebf
-dialog.3dlines.intro=3D \u7f51\u683c\u7ebf
# Confirm messages
confirm.loadfile=\u6570\u636e\u5df2\u4ece\u6587\u4ef6\u5bfc\u5165
@@ -529,7 +590,6 @@ button.cancel=\u53d6\u6d88
button.overwrite=\u8986\u76d6
button.moveup=\u4e0a\u79fb
button.movedown=\u4e0b\u79fb
-button.showlines=\u663e\u793a\u7ebf\u6761
button.edit=\u7f16\u8f91
button.exit=\u9000\u51fa
button.close=\u5173\u95ed
@@ -552,6 +612,7 @@ button.browse=\u6d4f\u89c8...
button.addnew=\u6dfb\u52a0
button.delete=\u5220\u9664
button.manage=\u7ba1\u7406
+button.combine=\u5408\u5e76
# File types
filetype.txt=TXT\u6587\u4ef6
@@ -562,12 +623,14 @@ filetype.kmz=KMZ\u6587\u4ef6
filetype.gpx=GPX\u6587\u4ef6
filetype.pov=POV\u6587\u4ef6
filetype.svg=SVG\u6587\u4ef6
+filetype.png=PNG\u6587\u4ef6
filetype.audio=WAV,OGG,MP3\u6587\u4ef6
# Display components
display.nodata=\u65e0\u6570\u636e
display.noaltitudes=\u8f68\u8ff9\u6570\u636e\u4e0d\u542b\u9ad8\u5ea6\u4fe1\u606f
display.notimestamps=\u8f68\u8ff9\u6570\u636e\u672a\u542b\u65f6\u95f4\u4fe1\u606f
+display.novalues=\u8f68\u8ff9\u6570\u636e\u4e0d\u4fdd\u542b\u6b64\u5b57\u6bb5
details.trackdetails=\u8f68\u8ff9\u4fe1\u606f
details.notrack=\u65e0\u8f68\u8ff9
details.track.points=\u8f68\u8ff9\u70b9
@@ -638,21 +701,31 @@ units.feet=\u82f1\u5c3a
units.feet.short=\u82f1\u5c3a
units.kilometres=\u5343\u7c73
units.kilometres.short=\u5343\u7c73
+units.kilometresperhour=\u5343\u7c73/\u65f6
units.kilometresperhour.short=\u5343\u7c73/\u65f6
units.miles=\u82f1\u91cc
units.miles.short=\u82f1\u91cc
+units.milesperhour=\u82f1\u91cc/\u65f6
units.milesperhour.short=\u82f1\u91cc/\u65f6
units.nauticalmiles=\u6d77\u91cc
units.nauticalmiles.short=\u6d77\u91cc
units.nauticalmilesperhour.short=\u6d77\u91cc/\u65f6
+units.metrespersec=\u7c73/\u79d2
units.metrespersec.short=\u7c73/\u79d2
+units.feetpersec=\u82f1\u5c3a/\u79d2
units.feetpersec.short=\u82f1\u5c3a/\u79d2
units.hours=\u5c0f\u65f6
+units.minutes=\u5206
+units.seconds=\u79d2
units.degminsec=\u5ea6-\u5206-\u79d2
units.degmin=\u5ea6-\u5206
units.deg=\u5ea6
units.iso8601=ISO 8601
+# How to combine conditions, such as filters
+logic.and=\u4e0e
+logic.or=\u6216
+
# External urls
url.googlemaps=ditu.google.cn
wikipedia.lang=zh
@@ -721,7 +794,7 @@ error.undofailed.text=\u64a4\u9500\u64cd\u4f5c\u5931\u8d25
error.function.noop.title=\u529f\u80fd\u65e0\u6548
error.rearrange.noop=\u91cd\u65b0\u914d\u7f6e\u822a\u70b9\u65e0\u6548
error.function.notavailable.title=\u65e0\u6b64\u529f\u80fd
-error.function.nojava3d=\u6b64\u529f\u80fd\u9700\u8981 Java 3D\uff0c\u53ef\u4eceSun.com\u83b7\u5f97
+error.function.nojava3d=\u6b64\u529f\u80fd\u9700\u8981 Java 3D
error.3d=3D \u663e\u793a\u9519\u8bef
error.readme.notfound=\u627e\u4e0d\u5230\u7248\u672c\u4fe1\u606f\u6587\u4ef6
error.osmimage.dialogtitle=\u5bfc\u5165\u5730\u56fe\u65f6\u9519\u8bef
@@ -738,3 +811,4 @@ error.cache.notthere=\u672a\u627e\u5230\u533a\u57df\u6570\u636e\u7f13\u5b58\u658
error.cache.empty=\u533a\u57df\u6570\u636e\u6587\u4ef6\u5939\u7a7a
error.cache.cannotdelete=\u65e0\u53ef\u5220\u9664\u533a\u57df\u6570\u636e
error.interpolate.invalidparameter=\u8f93\u5165\u70b9\u6570\u91cf\u5fc5\u987b\u57281\u52301000\u4e4b\u95f4
+error.learnestimationparams.failed=\u65e0\u6cd5\u4ece\u6b64\u8f68\u8ff9\u5f97\u5230\u53c2\u6570\u3002 \n \u5c1d\u8bd5\u8f7d\u5165\u66f4\u591a\u8f68\u8ff9\u3002
diff --git a/tim/prune/load/BabelFileFormats.java b/tim/prune/load/BabelFileFormats.java
index ed7e627..2d1c9e8 100644
--- a/tim/prune/load/BabelFileFormats.java
+++ b/tim/prune/load/BabelFileFormats.java
@@ -3,7 +3,7 @@ package tim.prune.load;
/**
* Class to manage the list of file formats supported by Gpsbabel
* (older versions of gpsbabel might not support all of these, of course).
- * Certain supported formats such as txt, csv, gpx are not included here
+ * Certain supported formats such as txt, csv are not included here
* as GpsPrune can already load them directly.
*/
public abstract class BabelFileFormats
@@ -111,6 +111,7 @@ public abstract class BabelFileFormats
"GPSman", "gpsman", null,
"GPSPilot Tracker for Palm/OS", "gpspilot", null,
"gpsutil", "gpsutil", null,
+ "GPX", "gpx", ".gpx",
"HikeTech", "hiketech", null,
"Holux (gm-100) .wpo Format", "holux", null,
"Holux M-241 (MTK based) Binary File Format", "m241-bin", null,
diff --git a/tim/prune/load/BabelLoadFromFile.java b/tim/prune/load/BabelLoadFromFile.java
index f7866ef..1897e0c 100644
--- a/tim/prune/load/BabelLoadFromFile.java
+++ b/tim/prune/load/BabelLoadFromFile.java
@@ -25,6 +25,7 @@ import tim.prune.config.Config;
import tim.prune.data.SourceInfo;
import tim.prune.data.SourceInfo.FILE_TYPE;
import tim.prune.gui.GuiGridLayout;
+import tim.prune.load.babel.BabelFilterPanel;
/**
@@ -158,6 +159,15 @@ public class BabelLoadFromFile extends BabelLoader
_saveCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
mainPanel.add(_saveCheckbox);
+ // Filter panel
+ _filterPanel = new BabelFilterPanel(_parentFrame);
+ // Give filter panel the contents of the config
+ String filter = Config.getConfigString(Config.KEY_GPSBABEL_FILTER);
+ if (filter != null) {
+ _filterPanel.setFilterString(filter);
+ }
+ mainPanel.add(_filterPanel);
+
// progress bar (initially invisible)
_progressBar = new JProgressBar(0, 10);
mainPanel.add(_progressBar);
@@ -216,6 +226,10 @@ public class BabelLoadFromFile extends BabelLoader
*/
protected void saveConfigValues()
{
- // nothing needed
+ // Save the filter string (but don't remove it if it's now blank)
+ final String filter = _filterPanel.getFilterString();
+ if (filter != null && !filter.equals("")) {
+ Config.setConfigString(Config.KEY_GPSBABEL_FILTER, filter);
+ }
}
}
diff --git a/tim/prune/load/BabelLoadFromGps.java b/tim/prune/load/BabelLoadFromGps.java
index 8c4b4dd..130ca79 100644
--- a/tim/prune/load/BabelLoadFromGps.java
+++ b/tim/prune/load/BabelLoadFromGps.java
@@ -26,6 +26,7 @@ import tim.prune.I18nManager;
import tim.prune.config.Config;
import tim.prune.data.SourceInfo;
import tim.prune.data.SourceInfo.FILE_TYPE;
+import tim.prune.load.babel.BabelFilterPanel;
/**
* Class to manage the loading of data from a GPS device using GpsBabel
@@ -128,6 +129,15 @@ public class BabelLoadFromGps extends BabelLoader
_saveCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
mainPanel.add(_saveCheckbox);
+ // Filter panel
+ _filterPanel = new BabelFilterPanel(_parentFrame);
+ // Give filter panel the contents of the config
+ String filter = Config.getConfigString(Config.KEY_GPSBABEL_FILTER);
+ if (filter != null) {
+ _filterPanel.setFilterString(filter);
+ }
+ mainPanel.add(_filterPanel);
+
// progress bar (initially invisible)
_progressBar = new JProgressBar(0, 10);
mainPanel.add(_progressBar);
@@ -169,7 +179,9 @@ public class BabelLoadFromGps extends BabelLoader
{
final String device = _deviceField.getText().trim();
final String format = _formatField.getText().trim();
+ final String filter = _filterPanel.getFilterString();
Config.setConfigString(Config.KEY_GPS_DEVICE, device);
Config.setConfigString(Config.KEY_GPS_FORMAT, format);
+ Config.setConfigString(Config.KEY_GPSBABEL_FILTER, filter);
}
}
diff --git a/tim/prune/load/BabelLoader.java b/tim/prune/load/BabelLoader.java
index b970665..7ebd7de 100644
--- a/tim/prune/load/BabelLoader.java
+++ b/tim/prune/load/BabelLoader.java
@@ -20,8 +20,8 @@ import tim.prune.ExternalTools;
import tim.prune.GenericFunction;
import tim.prune.I18nManager;
import tim.prune.config.Config;
-import tim.prune.data.Altitude;
import tim.prune.data.SourceInfo;
+import tim.prune.load.babel.BabelFilterPanel;
import tim.prune.load.xml.XmlFileLoader;
import tim.prune.load.xml.XmlHandler;
import tim.prune.save.GpxExporter;
@@ -42,6 +42,7 @@ public abstract class BabelLoader extends GenericFunction implements Runnable
protected JProgressBar _progressBar = null;
protected File _saveFile = null;
protected boolean _cancelled = false;
+ protected BabelFilterPanel _filterPanel = null;
/**
@@ -91,7 +92,11 @@ public abstract class BabelLoader extends GenericFunction implements Runnable
/** Do any subclass-specific dialog initialisation necessary */
- protected void initDialog() {}
+ protected void initDialog()
+ {
+ // GPSBabel filter, if any
+ _filterPanel.setFilterString(Config.getConfigString(Config.KEY_GPSBABEL_FILTER));
+ }
/**
* @param inStart true if the dialog is restarting
@@ -229,9 +234,8 @@ public abstract class BabelLoader extends GenericFunction implements Runnable
if (errorMessage.length() > 0) {throw new Exception(errorMessage);}
// Send data back to app
- _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(), Altitude.Format.METRES,
- getSourceInfo(),
- handler.getTrackNameList());
+ _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(), null,
+ getSourceInfo(), handler.getTrackNameList());
}
}
@@ -242,33 +246,55 @@ public abstract class BabelLoader extends GenericFunction implements Runnable
*/
private String[] getCommandArray()
{
- String[] commands = null;
+ ArrayList commandList = new ArrayList();
+ // Firstly the command for gpsbabel itself
final String command = Config.getConfigString(Config.KEY_GPSBABEL_PATH);
+ commandList.add(command);
+ // Then whether to load waypoints or track points
final boolean loadWaypoints = _waypointCheckbox.isSelected();
final boolean loadTrack = _trackCheckbox.isSelected();
- if (loadWaypoints && loadTrack) {
- // Both waypoints and track points selected
- commands = new String[] {command, "-w", "-t", "-i", getInputFormat(),
- "-f", getFilePath(), "-o", "gpx", "-F", "-"};
+ if (loadWaypoints) {
+ commandList.add("-w");
}
- else
+ if (loadTrack) {
+ commandList.add("-t");
+ }
+ // Input format
+ commandList.add("-i");
+ commandList.add(getInputFormat());
+ // File path
+ commandList.add("-f");
+ commandList.add(getFilePath());
+ // Filters, if any
+ final String filter = _filterPanel.getFilterString();
+ if (filter != null && !filter.equals(""))
{
- // Only waypoints OR track points selected
- commands = new String[] {command, "-w", "-i", getInputFormat(),
- "-f", getFilePath(), "-o", "gpx", "-F", "-"};
- if (loadTrack) {
- commands[1] = "-t";
+ for (String arg : filter.split(" "))
+ {
+ if (arg.length() > 0) {
+ commandList.add(arg);
+ }
}
}
+ // Output format
+ commandList.add("-o");
+ commandList.add("gpx");
+ // Where to
+ commandList.add("-F");
+ String whereTo = "-";
// Do we want to save the gpx straight to file?
- if (_saveCheckbox.isSelected()) {
+ if (_saveCheckbox.isSelected())
+ {
// Select file to save to
_saveFile = GpxExporter.chooseGpxFile(_parentFrame);
if (_saveFile != null) {
- commands[commands.length-1] = _saveFile.getAbsolutePath();
+ whereTo = _saveFile.getAbsolutePath();
}
}
- return commands;
+ commandList.add(whereTo);
+ // Convert to string array
+ String[] args = new String[] {};
+ return commandList.toArray(args);
}
/**
diff --git a/tim/prune/load/ComponentHider.java b/tim/prune/load/ComponentHider.java
new file mode 100644
index 0000000..45eeccd
--- /dev/null
+++ b/tim/prune/load/ComponentHider.java
@@ -0,0 +1,59 @@
+package tim.prune.load;
+
+import java.awt.Component;
+import java.util.ArrayList;
+
+import tim.prune.data.Field;
+
+/**
+ * Class to hold a list of Components and fields,
+ * and then enable or disable them (setEnabled) according
+ * to whether those fields are available or not
+ */
+public class ComponentHider
+{
+ /**
+ * Inner class to hold each Component and its Field
+ */
+ static class ComponentPair
+ {
+ public Component _component = null;
+ public Field _field = null;
+ /** Constructor */
+ public ComponentPair(Component inComponent, Field inField)
+ {
+ _component = inComponent;
+ _field = inField;
+ }
+ }
+
+ /** list itself */
+ private ArrayList _componentList = new ArrayList(20);
+
+ /**
+ * Add a new component to be controlled
+ * @param inComponent component to enable/disable
+ * @param inField associated field
+ */
+ public void addComponent(Component inComponent, Field inField)
+ {
+ if (inComponent != null && inField != null) {
+ _componentList.add(new ComponentPair(inComponent, inField));
+ }
+ }
+
+ /**
+ * Enable or disable the components for the given field
+ * @param inField field
+ * @param inEnabled true for enabled, false for disabled
+ */
+ public void enableComponents(Field inField, boolean inEnabled)
+ {
+ for (ComponentPair pair : _componentList)
+ {
+ if (pair != null && pair._field == inField) {
+ pair._component.setEnabled(inEnabled);
+ }
+ }
+ }
+}
diff --git a/tim/prune/load/FieldGuesser.java b/tim/prune/load/FieldGuesser.java
index 9b2bdc7..c76318d 100644
--- a/tim/prune/load/FieldGuesser.java
+++ b/tim/prune/load/FieldGuesser.java
@@ -19,20 +19,42 @@ public abstract class FieldGuesser
*/
private static boolean isHeaderRow(String[] inValues)
{
- // Loop over values looking for a Latitude value
+ // Loop over values seeing if any are mostly numeric
if (inValues != null)
{
- for (int v=0; v= '0' && currChar <= '9') {numNums++;}
+ }
+ // Return true if more than half the characters are numeric
+ return numNums > (numChars/2);
+ }
+
/**
* Try to guess the fields for the given values from the file
* @param inValues array of values from first non-blank line of file
@@ -108,12 +130,24 @@ public abstract class FieldGuesser
else if (!checkArrayHasField(fields, Field.LONGITUDE)) {
fields[f] = Field.LONGITUDE;
}
- else {
- customFieldNum++;
- fields[f] = new Field(customPrefix + (customFieldNum));
+ else
+ {
+ // Can we use the field name given?
+ Field customField = null;
+ if (isHeader && inValues[f] != null && inValues[f].length() > 0) {
+ customField = new Field(inValues[f]);
+ }
+ // Find an unused field number
+ while (customField == null || checkArrayHasField(fields, customField))
+ {
+ customFieldNum++;
+ customField = new Field(customPrefix + (customFieldNum));
+ }
+ fields[f] = customField;
}
}
}
+
// Do a final check to make sure lat and long are in there
if (!checkArrayHasField(fields, Field.LATITUDE)) {
fields[0] = Field.LATITUDE;
diff --git a/tim/prune/load/FileCacher.java b/tim/prune/load/FileCacher.java
index 1d12397..da354aa 100644
--- a/tim/prune/load/FileCacher.java
+++ b/tim/prune/load/FileCacher.java
@@ -42,8 +42,14 @@ public class FileCacher
{
reader = new BufferedReader(new FileReader(_file));
String currLine = reader.readLine();
+ if (currLine != null && currLine.startsWith("= 0) {
+ return; // it's a binary file, shouldn't use this cacher
+ }
if (currLine.trim().length() > 0)
contentList.add(currLine);
currLine = reader.readLine();
diff --git a/tim/prune/load/FileLoader.java b/tim/prune/load/FileLoader.java
index 5672c69..8669e18 100644
--- a/tim/prune/load/FileLoader.java
+++ b/tim/prune/load/FileLoader.java
@@ -2,11 +2,14 @@ package tim.prune.load;
import java.io.File;
import java.util.ArrayList;
+import java.util.TreeSet;
+
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import tim.prune.App;
import tim.prune.config.Config;
+import tim.prune.data.Photo;
import tim.prune.load.xml.GzipFileLoader;
import tim.prune.load.xml.XmlFileLoader;
import tim.prune.load.xml.ZipFileLoader;
@@ -130,6 +133,14 @@ public class FileLoader
{
_nmeaFileLoader.openFile(inFile);
}
+ else if (fileExtension.equals(".jpg") || fileExtension.equals("jpeg"))
+ {
+ Photo photo = JpegLoader.createPhoto(inFile);
+ TreeSet photoSet = new TreeSet();
+ photoSet.add(photo);
+ _app.informPhotosLoaded(photoSet);
+ _app.informNoDataLoaded(); // To trigger load of next file if any
+ }
else
{
// Use text loader for everything else
diff --git a/tim/prune/load/JpegLoader.java b/tim/prune/load/JpegLoader.java
index 8a376d8..a177eaa 100644
--- a/tim/prune/load/JpegLoader.java
+++ b/tim/prune/load/JpegLoader.java
@@ -20,6 +20,7 @@ import tim.prune.data.Latitude;
import tim.prune.data.Longitude;
import tim.prune.data.Photo;
import tim.prune.data.Timestamp;
+import tim.prune.data.UnitSetLibrary;
import tim.prune.function.Cancellable;
import tim.prune.jpeg.ExifGateway;
import tim.prune.jpeg.JpegData;
@@ -318,7 +319,7 @@ public class JpegLoader implements Runnable, Cancellable
Longitude longitude = new Longitude(lonval, Longitude.FORMAT_DEG_MIN_SEC);
Altitude altitude = null;
if (inData.hasAltitude()) {
- altitude = new Altitude(inData.getAltitude(), Altitude.Format.METRES);
+ altitude = new Altitude(inData.getAltitude(), UnitSetLibrary.UNITS_METRES);
}
return new DataPoint(latitude, longitude, altitude);
}
diff --git a/tim/prune/load/MediaLoadProgressDialog.java b/tim/prune/load/MediaLoadProgressDialog.java
index 4ed2ab0..a052b55 100644
--- a/tim/prune/load/MediaLoadProgressDialog.java
+++ b/tim/prune/load/MediaLoadProgressDialog.java
@@ -94,7 +94,6 @@ public class MediaLoadProgressDialog
_progressBar.setMaximum(inMax);
_progressBar.setValue(inCurrent);
_progressBar.setString("" + inCurrent + " / " + _progressBar.getMaximum());
- // TODO: Need to repaint?
}
/**
diff --git a/tim/prune/load/NmeaFileLoader.java b/tim/prune/load/NmeaFileLoader.java
index 2dd63de..d62ba5f 100644
--- a/tim/prune/load/NmeaFileLoader.java
+++ b/tim/prune/load/NmeaFileLoader.java
@@ -7,7 +7,6 @@ import java.io.IOException;
import java.util.ArrayList;
import tim.prune.App;
-import tim.prune.data.Altitude;
import tim.prune.data.Field;
import tim.prune.data.SourceInfo;
@@ -92,8 +91,7 @@ public class NmeaFileLoader
if (messages.size() > 0)
{
_app.informDataLoaded(getFieldArray(), makeDataArray(messages),
- Altitude.Format.METRES, new SourceInfo(inFile, SourceInfo.FILE_TYPE.NMEA),
- null);
+ null, new SourceInfo(inFile, SourceInfo.FILE_TYPE.NMEA), null);
}
}
diff --git a/tim/prune/load/TextFileLoader.java b/tim/prune/load/TextFileLoader.java
index 1f88012..53d6d2a 100644
--- a/tim/prune/load/TextFileLoader.java
+++ b/tim/prune/load/TextFileLoader.java
@@ -1,7 +1,6 @@
package tim.prune.load;
import java.awt.BorderLayout;
-import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
@@ -22,9 +21,13 @@ import java.io.File;
import tim.prune.App;
import tim.prune.I18nManager;
-import tim.prune.data.Altitude;
import tim.prune.data.Field;
+import tim.prune.data.PointCreateOptions;
import tim.prune.data.SourceInfo;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.GuiGridLayout;
+import tim.prune.gui.WizardLayout;
/**
@@ -37,8 +40,7 @@ public class TextFileLoader
private App _app = null;
private JFrame _parentFrame = null;
private JDialog _dialog = null;
- private JPanel _cardPanel = null;
- private CardLayout _layout = null;
+ private WizardLayout _wizard = null;
private JButton _backButton = null, _nextButton = null;
private JButton _finishButton = null;
private JButton _moveUpButton = null, _moveDownButton = null;
@@ -51,14 +53,18 @@ public class TextFileLoader
private FileExtractTableModel _fileExtractTableModel = null;
private JTable _fieldTable;
private FieldSelectionTableModel _fieldTableModel = null;
- private JComboBox _unitsDropDown = null;
+ private JComboBox _altitudeUnitsDropdown = null;
+ private JComboBox _hSpeedUnitsDropdown = null;
+ private JComboBox _vSpeedUnitsDropdown = null;
+ private JRadioButton _vSpeedUpwardsRadio = null;
+ private ComponentHider _componentHider = null;
private int _selectedField = -1;
private char _currentDelimiter = ',';
// previously selected values
private char _lastUsedDelimiter = ',';
private Field[] _lastSelectedFields = null;
- private Altitude.Format _lastAltitudeFormat = Altitude.Format.NO_FORMAT;
+ private Unit _lastAltitudeUnit = null;
// constants
private static final int SNIPPET_SIZE = 6;
@@ -135,9 +141,11 @@ public class TextFileLoader
_dialog.pack();
_dialog.setVisible(true);
}
- else {
+ else
+ {
// Didn't pass pre-check
- _app.showErrorMessage("error.load.dialogtitle", "error.load.noread");
+ _app.showErrorMessageNoLookup("error.load.dialogtitle",
+ I18nManager.getText("error.load.noread") + ": " + inFile.getName());
_app.informNoDataLoaded();
}
}
@@ -160,6 +168,9 @@ public class TextFileLoader
// Check each line of the file
String[] fileContents = _fileCacher.getContents();
+ if (fileContents == null) {
+ return false; // nothing cached, might be binary
+ }
boolean fileOK = true;
_delimiterInfos = new DelimiterInfo[5];
for (int i=0; i<4; i++) _delimiterInfos[i] = new DelimiterInfo(DELIMITERS[i]);
@@ -233,9 +244,9 @@ public class TextFileLoader
_backButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
- _layout.previous(_cardPanel);
- _backButton.setEnabled(false);
- _nextButton.setEnabled(true);
+ _wizard.showPreviousCard();
+ _nextButton.setEnabled(!_wizard.isLastCard());
+ _backButton.setEnabled(!_wizard.isFirstCard());
_finishButton.setEnabled(false);
}
});
@@ -245,11 +256,11 @@ public class TextFileLoader
_nextButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
- prepareSecondPanel();
- _layout.next(_cardPanel);
- _nextButton.setEnabled(false);
- _backButton.setEnabled(true);
- _finishButton.setEnabled(_fieldTableModel.getRowCount() > 1);
+ prepareNextPanel(); // Maybe it needs to be initialized based on previous panels
+ _wizard.showNextCard();
+ _nextButton.setEnabled(!_wizard.isLastCard() && isCurrentCardValid());
+ _backButton.setEnabled(!_wizard.isFirstCard());
+ _finishButton.setEnabled(_wizard.isLastCard() && isCurrentCardValid());
}
});
buttonPanel.add(_nextButton);
@@ -273,10 +284,9 @@ public class TextFileLoader
buttonPanel.add(cancelButton);
wholePanel.add(buttonPanel, BorderLayout.SOUTH);
- // Make the two cards, for delimiter and fields
- _cardPanel = new JPanel();
- _layout = new CardLayout();
- _cardPanel.setLayout(_layout);
+ // Make the card panel in the centre
+ JPanel cardPanel = new JPanel();
+ _wizard = new WizardLayout(cardPanel);
JPanel firstCard = new JPanel();
firstCard.setLayout(new BorderLayout());
firstCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
@@ -391,17 +401,71 @@ public class TextFileLoader
innerPanel2.add(innerPanel3, BorderLayout.EAST);
secondCard.add(innerPanel2, BorderLayout.CENTER);
+
+ // Third card, for units selection of altitude and speeds
+ JPanel thirdCard = new JPanel();
+ thirdCard.setLayout(new BorderLayout(10, 10));
+ JPanel holderPanel = new JPanel();
+ holderPanel.setLayout(new BoxLayout(holderPanel, BoxLayout.Y_AXIS));
+ // Altitude
JPanel altUnitsPanel = new JPanel();
- altUnitsPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
- altUnitsPanel.add(new JLabel(I18nManager.getText("dialog.openoptions.altitudeunits")));
- String[] units = {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")};
- _unitsDropDown = new JComboBox(units);
- altUnitsPanel.add(_unitsDropDown);
- secondCard.add(altUnitsPanel, BorderLayout.SOUTH);
- _cardPanel.add(firstCard, "card1");
- _cardPanel.add(secondCard, "card2");
-
- wholePanel.add(_cardPanel, BorderLayout.CENTER);
+ GuiGridLayout altGrid = new GuiGridLayout(altUnitsPanel);
+ altUnitsPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.altitude")));
+ JLabel altLabel = new JLabel(I18nManager.getText("dialog.openoptions.altitudeunits") + ": ");
+ altGrid.add(altLabel);
+ String[] altUnits = {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")};
+ _altitudeUnitsDropdown = new JComboBox(altUnits);
+ altGrid.add(_altitudeUnitsDropdown);
+ holderPanel.add(altUnitsPanel);
+ // Horizontal speed
+ JPanel speedPanel = new JPanel();
+ GuiGridLayout speedGrid = new GuiGridLayout(speedPanel);
+ speedPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.speed")));
+ JLabel speedLabel = new JLabel(I18nManager.getText("dialog.openoptions.speedunits") + ": ");
+ speedGrid.add(speedLabel);
+ _hSpeedUnitsDropdown = new JComboBox();
+ for (Unit spUnit : UnitSetLibrary.ALL_SPEED_UNITS) {
+ _hSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
+ }
+ speedGrid.add(_hSpeedUnitsDropdown);
+ holderPanel.add(speedPanel);
+ // Vertical speed
+ JPanel vSpeedPanel = new JPanel();
+ GuiGridLayout vSpeedGrid = new GuiGridLayout(vSpeedPanel);
+ vSpeedPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.verticalspeed")));
+ JLabel vSpeedLabel = new JLabel(I18nManager.getText("dialog.openoptions.vertspeedunits") + ": ");
+ vSpeedGrid.add(vSpeedLabel);
+ _vSpeedUnitsDropdown = new JComboBox();
+ for (Unit spUnit : UnitSetLibrary.ALL_SPEED_UNITS) {
+ _vSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
+ }
+ vSpeedGrid.add(_vSpeedUnitsDropdown);
+ _vSpeedUpwardsRadio = new JRadioButton(I18nManager.getText("dialog.openoptions.vspeed.positiveup"));
+ JRadioButton vSpeedDownwardsRadio = new JRadioButton(I18nManager.getText("dialog.openoptions.vspeed.positivedown"));
+ ButtonGroup vSpeedDirGroup = new ButtonGroup();
+ vSpeedDirGroup.add(_vSpeedUpwardsRadio); vSpeedDirGroup.add(vSpeedDownwardsRadio);
+ vSpeedGrid.add(_vSpeedUpwardsRadio); vSpeedGrid.add(vSpeedDownwardsRadio);
+ _vSpeedUpwardsRadio.setSelected(true);
+ holderPanel.add(vSpeedPanel);
+ thirdCard.add(holderPanel, BorderLayout.NORTH);
+
+ // Make a hider to show and hide the components according to the selected fields
+ _componentHider = new ComponentHider();
+ _componentHider.addComponent(altLabel, Field.ALTITUDE);
+ _componentHider.addComponent(_altitudeUnitsDropdown, Field.ALTITUDE);
+ _componentHider.addComponent(speedLabel, Field.SPEED);
+ _componentHider.addComponent(_hSpeedUnitsDropdown, Field.SPEED);
+ _componentHider.addComponent(vSpeedLabel, Field.VERTICAL_SPEED);
+ _componentHider.addComponent(_vSpeedUnitsDropdown, Field.VERTICAL_SPEED);
+ _componentHider.addComponent(_vSpeedUpwardsRadio, Field.VERTICAL_SPEED);
+ _componentHider.addComponent(vSpeedDownwardsRadio, Field.VERTICAL_SPEED);
+
+ // Add cards to the wizard
+ _wizard.addCard(firstCard);
+ _wizard.addCard(secondCard);
+ _wizard.addCard(thirdCard);
+
+ wholePanel.add(cardPanel, BorderLayout.CENTER);
return wholePanel;
}
@@ -471,6 +535,26 @@ public class TextFileLoader
}
+ /**
+ * Prepare the next panel to be shown, if necessary
+ */
+ private void prepareNextPanel()
+ {
+ int currPanel = _wizard.getCurrentCardIndex();
+ if (currPanel == 0) {
+ prepareSecondPanel();
+ }
+ else if (currPanel == 1)
+ {
+ Field[] selectedFields = _fieldTableModel.getFieldArray();
+ // Enable / disable controls based on whether altitude / speed / vspeed fields were chosen on second panel
+ _componentHider.enableComponents(Field.ALTITUDE, doesFieldArrayContain(selectedFields, Field.ALTITUDE));
+ _componentHider.enableComponents(Field.SPEED, doesFieldArrayContain(selectedFields, Field.SPEED));
+ _componentHider.enableComponents(Field.VERTICAL_SPEED, doesFieldArrayContain(selectedFields, Field.VERTICAL_SPEED));
+ // TODO: Also check ranges of altitudes, speeds, vert speeds to show them in the third panel
+ }
+ }
+
/**
* Use the delimiter selected to determine the fields in the file
* and prepare the second panel accordingly
@@ -512,14 +596,31 @@ public class TextFileLoader
_fieldTable.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(fieldTypesBox));
// Set altitude format to same as last time if available
- if (_lastAltitudeFormat == Altitude.Format.METRES)
- _unitsDropDown.setSelectedIndex(0);
- else if (_lastAltitudeFormat == Altitude.Format.FEET)
- _unitsDropDown.setSelectedIndex(1);
+ if (_lastAltitudeUnit == UnitSetLibrary.UNITS_METRES)
+ _altitudeUnitsDropdown.setSelectedIndex(0);
+ else if (_lastAltitudeUnit == UnitSetLibrary.UNITS_FEET)
+ _altitudeUnitsDropdown.setSelectedIndex(1);
// no selection on field list
selectField(-1);
}
+ /**
+ * See if the given array of selected fields contains the specified one
+ * @param inFields array of fields selected by user in the second panel
+ * @param inCheck field to check
+ * @return true if the field is present in the array
+ */
+ private boolean doesFieldArrayContain(Field[] inFields, Field inCheck)
+ {
+ if (inFields != null) {
+ for (int i=0; i 1;
+ }
+ // all other panels are always valid
+ return true;
+ }
/**
* Make a panel with a label and a component
diff --git a/tim/prune/load/babel/AddFilterDialog.java b/tim/prune/load/babel/AddFilterDialog.java
new file mode 100644
index 0000000..7de9a3a
--- /dev/null
+++ b/tim/prune/load/babel/AddFilterDialog.java
@@ -0,0 +1,185 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.WizardLayout;
+
+
+/**
+ * Class to manage a dialog for adding a single GPSBabel filter
+ */
+public class AddFilterDialog
+{
+ /** Parent panel to pass the filter back to */
+ private BabelFilterPanel _parentPanel = null;
+ /** Reference to parent frame */
+ private JFrame _parentFrame = null;
+ /** Main dialog */
+ private JDialog _dialog = null;
+ /** layout for dealing with cards */
+ private WizardLayout _wizard = null;
+ /** Array of filter definitions */
+ private FilterDefinition[] _filters = new FilterDefinition[4];
+ /** Finish button */
+ private JButton _finishButton = null;
+ /** back button */
+ private JButton _backButton = null;
+
+ // Selector class for one of the filter types
+ class FilterTypeListener implements ActionListener
+ {
+ private int _index = 0;
+ public FilterTypeListener(int inIndex) {_index = inIndex;}
+ public void actionPerformed(ActionEvent e) {
+ _wizard.showCard(_index);
+ _backButton.setEnabled(true);
+ filterParamsChanged(); // to check parameters and enable/disable Finish button
+ }
+ }
+
+ /**
+ * Constructor
+ * @param inParent parent panel to inform of selected filter
+ * @param inParentFrame parent frame to reference for dialogs
+ */
+ public AddFilterDialog(BabelFilterPanel inParent, JFrame inParentFrame)
+ {
+ _parentPanel = inParent;
+ _parentFrame = inParentFrame;
+ }
+
+ /**
+ * Show the dialog to add a new filter
+ */
+ public void showDialog()
+ {
+ if (_dialog == null)
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.addfilter.title"), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ }
+ // TODO: Initialise cards, clear entries?
+ _wizard.showFirstCard();
+ _backButton.setEnabled(false);
+ _finishButton.setEnabled(false);
+ _dialog.setVisible(true);
+ }
+
+ /**
+ * Create dialog components
+ * @return Panel containing all gui elements in dialog
+ */
+ private JPanel makeDialogComponents()
+ {
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BorderLayout());
+
+ // card panel in the middle
+ JPanel cardPanel = new JPanel();
+ _wizard = new WizardLayout(cardPanel);
+ JPanel typesCard = new JPanel();
+ JButton discardButton = new JButton(I18nManager.getText("dialog.gpsbabel.filter.discard"));
+ discardButton.addActionListener(new FilterTypeListener(1));
+ typesCard.add(discardButton);
+ JButton simplifyButton = new JButton(I18nManager.getText("dialog.gpsbabel.filter.simplify"));
+ simplifyButton.addActionListener(new FilterTypeListener(2));
+ typesCard.add(simplifyButton);
+ JButton distanceButton = new JButton(I18nManager.getText("dialog.gpsbabel.filter.distance"));
+ distanceButton.addActionListener(new FilterTypeListener(3));
+ typesCard.add(distanceButton);
+ JButton interpButton = new JButton(I18nManager.getText("dialog.gpsbabel.filter.interpolate"));
+ interpButton.addActionListener(new FilterTypeListener(4));
+ typesCard.add(interpButton);
+
+ // discard panel
+ _filters[0] = new DiscardFilter(this);
+ // simplify panel
+ _filters[1] = new SimplifyFilter(this);
+ // distance panel
+ _filters[2] = new DistanceFilter(this);
+ // interpolate panel
+ _filters[3] = new InterpolateFilter(this);
+
+ // Add cards to the wizard
+ _wizard.addCard(typesCard);
+ _wizard.addCard(_filters[0]);
+ _wizard.addCard(_filters[1]);
+ _wizard.addCard(_filters[2]);
+ _wizard.addCard(_filters[3]);
+ dialogPanel.add(cardPanel, BorderLayout.CENTER);
+
+ // button panel at bottom
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+ _backButton = new JButton(I18nManager.getText("button.back"));
+ _backButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ _wizard.showCard(0);
+ _backButton.setEnabled(!_wizard.isFirstCard());
+ _finishButton.setEnabled(false);
+ }
+ });
+ _backButton.setEnabled(false);
+ buttonPanel.add(_backButton);
+ _finishButton = new JButton(I18nManager.getText("button.finish"));
+ ActionListener okListener = new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ finish();
+ }
+ };
+ _finishButton.addActionListener(okListener);
+ _finishButton.setEnabled(false);
+ buttonPanel.add(_finishButton);
+ 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;
+ }
+
+ /**
+ * React to changes in the filter parameters (such as enabling/disabling the ok button)
+ */
+ public void filterParamsChanged()
+ {
+ final int currCard = _wizard.getCurrentCardIndex();
+ if (currCard > 0 && currCard < 5) {
+ _finishButton.setEnabled(_filters[currCard-1].isFilterValid());
+ }
+ }
+
+ /**
+ * Finish the dialog when OK pressed
+ */
+ private void finish()
+ {
+ // finish dialog and pass results back to the parent panel
+ final int currCard = _wizard.getCurrentCardIndex();
+ if (currCard > 0 && currCard < 5) {
+ _parentPanel.addFilter(_filters[currCard-1].getString());
+ }
+ _dialog.dispose();
+ }
+}
diff --git a/tim/prune/load/babel/BabelFilterPanel.java b/tim/prune/load/babel/BabelFilterPanel.java
new file mode 100644
index 0000000..b5b564f
--- /dev/null
+++ b/tim/prune/load/babel/BabelFilterPanel.java
@@ -0,0 +1,184 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.regex.Pattern;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.StatusIcon;
+
+/**
+ * Gui element to allow the specification of filters for GPSBabel.
+ * Used for loading from GPS and loading from file
+ */
+public class BabelFilterPanel extends JPanel
+{
+ /** Text field for entering filters manually */
+ private JTextField _filterField = null;
+ /** Icon for showing whether the value is valid for GPSBabel or not */
+ private StatusIcon _validIcon = null;
+ /** Dialog for adding a new filter */
+ private AddFilterDialog _addDialog = null;
+
+ /** Regular expression for detecting valid filter strings */
+ private static final Pattern FILTER_PATTERN
+ = Pattern.compile("(-x [a-z,\\.0-9=]+ *)+");
+
+ /**
+ * Constructor
+ * @param inParentFrame parent frame for launching popup dialog
+ */
+ public BabelFilterPanel(JFrame inParentFrame)
+ {
+ setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createTitledBorder(I18nManager.getText("dialog.gpsbabel.filters")),
+ BorderFactory.createEmptyBorder(2, 2, 2, 2)));
+ initPanel();
+ _addDialog = new AddFilterDialog(this, inParentFrame);
+ }
+
+ /**
+ * Set up the panel with all the components inside
+ */
+ private void initPanel()
+ {
+ setLayout(new BorderLayout(4, 4));
+ // text field for the filter text
+ _filterField = new JTextField(20);
+ _filterField.addKeyListener(new KeyAdapter() {
+ public void keyTyped(KeyEvent arg0) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ checkFilter();
+ }
+ });
+ }
+ });
+ JPanel filterFieldPanel = new JPanel();
+ filterFieldPanel.setLayout(new BorderLayout(3, 3));
+ JPanel filterIconPanel = new JPanel();
+ filterIconPanel.setLayout(new BorderLayout(3, 3));
+ filterIconPanel.add(_filterField, BorderLayout.CENTER);
+ _validIcon = new StatusIcon();
+ filterIconPanel.add(_validIcon, BorderLayout.EAST);
+ filterFieldPanel.add(filterIconPanel, BorderLayout.NORTH);
+ add(filterFieldPanel, BorderLayout.CENTER);
+ // Add and clear buttons
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS));
+ JButton addButton = new JButton(I18nManager.getText("button.addnew"));
+ addButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ // System.out.println("Filter exists: " + hasFilter() + ", valid: " + isFilterValid());
+ _addDialog.showDialog();
+ }
+ });
+ buttonPanel.add(addButton);
+ buttonPanel.add(Box.createVerticalStrut(2));
+ JButton clearButton = new JButton(I18nManager.getText("button.delete"));
+ clearButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ _filterField.setText("");
+ checkFilter();
+ }
+ });
+ buttonPanel.add(clearButton);
+ add(buttonPanel, BorderLayout.EAST);
+ }
+
+ /**
+ * @param inFilter filter string to set (normally from config)
+ */
+ public void setFilterString(String inFilter)
+ {
+ if (inFilter != null && _filterField != null) {
+ _filterField.setText(inFilter.trim());
+ }
+ checkFilter();
+ }
+
+ /**
+ * @return trimmed filter string, or null
+ */
+ public String getFilterString()
+ {
+ String filter = _filterField.getText();
+ if (filter != null) filter = filter.trim();
+ return filter;
+ }
+
+ /**
+ * @return true if a filter has been given (which may or may not be valid)
+ */
+ public boolean hasFilter()
+ {
+ String str = getFilterString();
+ return str != null && str.length() > 0;
+ }
+
+ /**
+ * @return true if the given filter string is valid
+ */
+ public boolean isFilterValid()
+ {
+ String str = getFilterString();
+ if (str == null) return false;
+ return FILTER_PATTERN.matcher(str).matches();
+ }
+
+ /**
+ * Called from the add filter dialog to indicate completion
+ * @param inFilter filter to add
+ */
+ public void addFilter(String inFilter)
+ {
+ if (inFilter != null)
+ {
+ String newFilter = inFilter.trim();
+ String currFilter = getFilterString();
+ if (!newFilter.equals(""))
+ {
+ if (currFilter == null || currFilter.equals("")) {
+ currFilter = newFilter;
+ }
+ else { // append
+ currFilter = currFilter + " " + newFilter;
+ }
+ }
+ _filterField.setText(currFilter);
+ }
+ checkFilter();
+ }
+
+ /**
+ * See if the current filter is valid or not, and update the icon accordingly
+ */
+ private void checkFilter()
+ {
+ if (hasFilter())
+ {
+ if (isFilterValid()) {
+ _validIcon.setStatusValid();
+ }
+ else {
+ _validIcon.setStatusInvalid();
+ }
+ }
+ else
+ {
+ _validIcon.setStatusBlank();
+ }
+ }
+}
diff --git a/tim/prune/load/babel/DiscardFilter.java b/tim/prune/load/babel/DiscardFilter.java
new file mode 100644
index 0000000..f310031
--- /dev/null
+++ b/tim/prune/load/babel/DiscardFilter.java
@@ -0,0 +1,144 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+import javax.swing.border.EtchedBorder;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * Discard filter for GPSBabel
+ */
+public class DiscardFilter extends FilterDefinition
+{
+ /** Constructor */
+ public DiscardFilter(AddFilterDialog inFilterDialog)
+ {
+ super(inFilterDialog);
+ makePanelContents();
+ }
+
+ private WholeNumberField _hdopField = null;
+ private WholeNumberField _vdopField = null;
+ private JComboBox _combineDopsCombo = null;
+ private WholeNumberField _numSatsField = null;
+ private JCheckBox _noFixCheckbox = null;
+ private JCheckBox _unknownFixCheckbox = null;
+
+
+ /** @return filter name */
+ protected String getFilterName() {
+ return "discard";
+ }
+
+ /** Make the panel contents */
+ protected void makePanelContents()
+ {
+ setLayout(new BorderLayout());
+ JPanel boxPanel = new JPanel();
+ boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
+ JLabel topLabel = new JLabel(I18nManager.getText("dialog.gpsbabel.filter.discard.intro"));
+ topLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ boxPanel.add(topLabel);
+ boxPanel.add(Box.createVerticalStrut(9)); // spacer
+
+ JPanel boxPanel2 = new JPanel();
+ boxPanel2.setLayout(new BoxLayout(boxPanel2, BoxLayout.Y_AXIS));
+ // Panel for dops
+ JPanel dopPanel = new JPanel();
+ dopPanel.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4))
+ );
+ dopPanel.setLayout(new GridLayout(0, 3, 4, 2));
+ dopPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.discard.hdop"), SwingConstants.RIGHT));
+ _hdopField = new WholeNumberField(2);
+ _hdopField.addKeyListener(_paramChangeListener);
+ dopPanel.add(_hdopField);
+ _combineDopsCombo = new JComboBox(new String[] {I18nManager.getText("logic.and"), I18nManager.getText("logic.or")});
+ dopPanel.add(_combineDopsCombo);
+ dopPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.discard.vdop"), SwingConstants.RIGHT));
+ _vdopField = new WholeNumberField(2);
+ _vdopField.addKeyListener(_paramChangeListener);
+ dopPanel.add(_vdopField);
+ boxPanel2.add(dopPanel);
+
+ // Number of satellites
+ JPanel satPanel = new JPanel();
+ satPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.discard.numsats")));
+ _numSatsField = new WholeNumberField(2);
+ _numSatsField.addKeyListener(_paramChangeListener);
+ satPanel.add(_numSatsField);
+ boxPanel2.add(satPanel);
+
+ // Checkboxes for no fix and unknown fix
+ _noFixCheckbox = new JCheckBox(I18nManager.getText("dialog.gpsbabel.filter.discard.nofix"));
+ boxPanel2.add(_noFixCheckbox);
+ _unknownFixCheckbox = new JCheckBox(I18nManager.getText("dialog.gpsbabel.filter.discard.unknownfix"));
+ boxPanel2.add(_unknownFixCheckbox);
+ boxPanel2.add(Box.createVerticalStrut(9)); // spacer
+
+ boxPanel2.setAlignmentX(Component.LEFT_ALIGNMENT);
+ boxPanel.add(boxPanel2);
+ add(boxPanel, BorderLayout.NORTH);
+}
+
+ /**
+ * @return true if the filters are valid
+ */
+ public boolean isFilterValid()
+ {
+ // If values are entered, insist that they're positive (0 not valid)
+ if (_hdopField.getText() != null && _hdopField.getText().length() > 0 && _hdopField.getValue() <= 0) {return false;}
+ if (_vdopField.getText() != null && _vdopField.getText().length() > 0 && _vdopField.getValue() <= 0) {return false;}
+ if (_numSatsField.getText() != null && _numSatsField.getText().length() > 0 && _numSatsField.getValue() <= 0) {return false;}
+ // Insist that at least one value has been entered
+ return _hdopField.getValue() > 0 || _vdopField.getValue() > 0 || _numSatsField.getValue() > 0;
+ }
+
+ /**
+ * @return filter parameters as a string, or null
+ */
+ protected String getParameters()
+ {
+ if (!isFilterValid()) return null;
+ StringBuilder builder = new StringBuilder();
+ // hdop and vdop
+ final int hdop = _hdopField.getValue();
+ if (hdop > 0) {
+ builder.append(",hdop=").append(hdop);
+ }
+ final int vdop = _vdopField.getValue();
+ if (vdop > 0)
+ {
+ builder.append(",vdop=").append(vdop);
+ if (hdop > 0 && _combineDopsCombo.getSelectedIndex() == 0) {
+ builder.append(",hdopandvdop");
+ }
+ }
+ // number of satellites
+ final int numSats = _numSatsField.getValue();
+ if (numSats > 0)
+ {
+ builder.append(",sat=").append(numSats);
+ }
+ // checkboxes
+ if (_noFixCheckbox.isSelected()) {
+ builder.append(",fixnone");
+ }
+ if (_unknownFixCheckbox.isSelected()) {
+ builder.append(",fixunknown");
+ }
+ return builder.toString();
+ }
+}
diff --git a/tim/prune/load/babel/DistanceFilter.java b/tim/prune/load/babel/DistanceFilter.java
new file mode 100644
index 0000000..7554160
--- /dev/null
+++ b/tim/prune/load/babel/DistanceFilter.java
@@ -0,0 +1,104 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * Distance filter for GPSBabel (compress by distance, or nearby points)
+ */
+public class DistanceFilter extends FilterDefinition
+{
+ /** Constructor */
+ public DistanceFilter(AddFilterDialog inFilterDialog)
+ {
+ super(inFilterDialog);
+ makePanelContents();
+ }
+
+ private DecimalNumberField _distField = null;
+ private JComboBox _distUnitsCombo = null;
+ private WholeNumberField _secondsField = null;
+
+
+ /** @return filter name */
+ protected String getFilterName() {
+ return "position";
+ }
+
+ /** Make the panel contents */
+ protected void makePanelContents()
+ {
+ setLayout(new BorderLayout());
+ JPanel boxPanel = new JPanel();
+ boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
+ add(boxPanel, BorderLayout.NORTH);
+ JLabel topLabel = new JLabel(I18nManager.getText("dialog.gpsbabel.filter.distance.intro"));
+ topLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ boxPanel.add(topLabel);
+ boxPanel.add(Box.createVerticalStrut(18)); // spacer
+ // Main three-column grid
+ JPanel gridPanel = new JPanel();
+ gridPanel.setLayout(new GridLayout(0, 3, 4, 4));
+ gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.distance.distance")));
+ _distField = new DecimalNumberField();
+ _distField.addKeyListener(_paramChangeListener);
+ gridPanel.add(_distField);
+ _distUnitsCombo = new JComboBox(new String[] {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")});
+ gridPanel.add(_distUnitsCombo);
+ gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.distance.time")));
+ _secondsField = new WholeNumberField(4);
+ _secondsField.addKeyListener(_paramChangeListener);
+ gridPanel.add(_secondsField);
+ gridPanel.add(new JLabel(I18nManager.getText("units.seconds")));
+ gridPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ boxPanel.add(gridPanel);
+ }
+
+ /**
+ * @return true if the filters are valid
+ */
+ public boolean isFilterValid()
+ {
+ if (_distField.getText() == null || _distField.getText().trim().equals("")) {
+ return false; // no distance given
+ }
+ final boolean timeGiven = _secondsField.getText() != null && _secondsField.getText().trim().length() > 0;
+ if (timeGiven && _secondsField.getValue() <= 1) {
+ return false; // must have a decent number of seconds
+ }
+ // check the distance
+ return (_distField.getValue() > 0.001); // no zero or negative distances allowed
+ }
+
+ /**
+ * @return filter parameters as a string, or null
+ */
+ protected String getParameters()
+ {
+ if (!isFilterValid()) return null;
+ StringBuilder builder = new StringBuilder();
+ // Get the distance
+ double dValue = _distField.getValue();
+ builder.append(",distance=").append(dValue);
+ // units of distance (miles by default)
+ builder.append(_distUnitsCombo.getSelectedIndex() == 0 ? "m" : "f"); // metres or feet
+
+ // is there a time as well?
+ final boolean timeGiven = _secondsField.getText() != null && _secondsField.getText().trim().length() > 0;
+ if (timeGiven) {
+ builder.append(",time=").append(_secondsField.getValue()); // no s at the end
+ }
+ return builder.toString();
+ }
+}
diff --git a/tim/prune/load/babel/FilterDefinition.java b/tim/prune/load/babel/FilterDefinition.java
new file mode 100644
index 0000000..8417908
--- /dev/null
+++ b/tim/prune/load/babel/FilterDefinition.java
@@ -0,0 +1,59 @@
+package tim.prune.load.babel;
+
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+/**
+ * Superclass of all the filter definition panels, to be added in the cardset
+ * of the AddFilterDialog
+ */
+public abstract class FilterDefinition extends JPanel
+{
+ /** Parent dialog to inform of parameter changes */
+ private AddFilterDialog _parentDialog = null;
+ /** Listener for key presses on the parameter entry fields */
+ protected KeyListener _paramChangeListener = null;
+
+ /**
+ * Constructor
+ */
+ public FilterDefinition(AddFilterDialog inFilterDialog)
+ {
+ _parentDialog = inFilterDialog;
+ _paramChangeListener = new KeyAdapter() {
+ public void keyTyped(KeyEvent arg0) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ _parentDialog.filterParamsChanged();
+ }
+ });
+ }
+ };
+ }
+
+ /**
+ * @return true if the filter definition is valid
+ */
+ public abstract boolean isFilterValid();
+
+ /**
+ * @return filter definition to pass to gpsbabel
+ */
+ public String getString()
+ {
+ return "-x " + getFilterName() + getParameters();
+ }
+
+ /** @return filter name */
+ protected abstract String getFilterName();
+
+ /** Construct the GUI elements and add them to the panel */
+ protected abstract void makePanelContents();
+
+ /** @return filter parameters */
+ protected abstract String getParameters();
+}
diff --git a/tim/prune/load/babel/InterpolateFilter.java b/tim/prune/load/babel/InterpolateFilter.java
new file mode 100644
index 0000000..a169846
--- /dev/null
+++ b/tim/prune/load/babel/InterpolateFilter.java
@@ -0,0 +1,112 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * Interpolate filter for GPSBabel (adding extra points, does that make it still a filter?)
+ * Very similar to the distance filter in terms of gui
+ */
+public class InterpolateFilter extends FilterDefinition
+{
+ /** Constructor */
+ public InterpolateFilter(AddFilterDialog inFilterDialog)
+ {
+ super(inFilterDialog);
+ makePanelContents();
+ }
+
+ private DecimalNumberField _distField = null;
+ private JComboBox _distUnitsCombo = null;
+ private WholeNumberField _secondsField = null;
+
+
+ /** @return filter name */
+ protected String getFilterName() {
+ return "interpolate";
+ }
+
+ /** Make the panel contents */
+ protected void makePanelContents()
+ {
+ setLayout(new BorderLayout());
+ JPanel boxPanel = new JPanel();
+ boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
+ add(boxPanel, BorderLayout.NORTH);
+ JLabel topLabel = new JLabel(I18nManager.getText("dialog.gpsbabel.filter.interpolate.intro"));
+ topLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ boxPanel.add(topLabel);
+ boxPanel.add(Box.createVerticalStrut(18)); // spacer
+ // Main three-column grid
+ JPanel gridPanel = new JPanel();
+ gridPanel.setLayout(new GridLayout(0, 3, 4, 4));
+ gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.interpolate.distance")));
+ _distField = new DecimalNumberField();
+ _distField.addKeyListener(_paramChangeListener);
+ gridPanel.add(_distField);
+ _distUnitsCombo = new JComboBox(new String[] {I18nManager.getText("units.kilometres"), I18nManager.getText("units.miles")});
+ gridPanel.add(_distUnitsCombo);
+ gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.interpolate.time")));
+ _secondsField = new WholeNumberField(4);
+ _secondsField.addKeyListener(_paramChangeListener);
+ gridPanel.add(_secondsField);
+ gridPanel.add(new JLabel(I18nManager.getText("units.seconds")));
+ gridPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ boxPanel.add(gridPanel);
+ }
+
+ /**
+ * @return true if the filters are valid
+ */
+ public boolean isFilterValid()
+ {
+ final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
+ final boolean timeGiven = _secondsField.getText() != null && _secondsField.getText().trim().length() > 0;
+ if ((!distGiven && !timeGiven) || (distGiven && timeGiven)) {
+ return false; // either one or the other, not both
+ }
+ if (distGiven && _distField.getValue() < 0.0001) {
+ return false; // must have a decent distance
+ }
+ if (timeGiven && _secondsField.getValue() <= 1) {
+ return false; // must have a decent number of seconds
+ }
+ // must be ok
+ return true;
+ }
+
+ /**
+ * @return filter parameters as a string, or null
+ */
+ protected String getParameters()
+ {
+ if (!isFilterValid()) return null;
+ StringBuilder builder = new StringBuilder();
+ final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
+ final boolean timeGiven = _secondsField.getText() != null && _secondsField.getText().trim().length() > 0;
+ if (distGiven)
+ {
+ // Get the distance
+ double dValue = _distField.getValue();
+ builder.append(",distance=").append(dValue);
+ // units of distance (km or miles)
+ builder.append(_distUnitsCombo.getSelectedIndex() == 0 ? "k" : "m");
+ }
+ else if (timeGiven) {
+ // time
+ builder.append(",time=").append(_secondsField.getValue()); // no s at the end
+ }
+ return builder.toString();
+ }
+}
diff --git a/tim/prune/load/babel/SimplifyFilter.java b/tim/prune/load/babel/SimplifyFilter.java
new file mode 100644
index 0000000..ebbc9f4
--- /dev/null
+++ b/tim/prune/load/babel/SimplifyFilter.java
@@ -0,0 +1,147 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import tim.prune.I18nManager;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * Simplify filter for GPSBabel
+ */
+public class SimplifyFilter extends FilterDefinition
+{
+ /** Constructor */
+ public SimplifyFilter(AddFilterDialog inFilterDialog)
+ {
+ super(inFilterDialog);
+ makePanelContents();
+ }
+
+ private WholeNumberField _maxPointsField = null;
+ private DecimalNumberField _distField = null;
+ private JComboBox _distUnitsCombo = null;
+ private JRadioButton _crossTrackRadio = null;
+ private JRadioButton _lengthRadio = null;
+ private JRadioButton _relativeRadio = null;
+
+
+ /** @return filter name */
+ protected String getFilterName() {
+ return "simplify";
+ }
+
+ /** Make the panel contents */
+ protected void makePanelContents()
+ {
+ setLayout(new BorderLayout());
+ JPanel boxPanel = new JPanel();
+ boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
+ add(boxPanel, BorderLayout.NORTH);
+ JLabel topLabel = new JLabel(I18nManager.getText("dialog.gpsbabel.filter.simplify.intro"));
+ topLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ boxPanel.add(topLabel);
+ boxPanel.add(Box.createVerticalStrut(18)); // spacer
+ // Main three-column grid
+ JPanel gridPanel = new JPanel();
+ gridPanel.setLayout(new GridLayout(0, 3, 4, 4));
+ gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.simplify.maxpoints")));
+ _maxPointsField = new WholeNumberField(6);
+ _maxPointsField.addKeyListener(_paramChangeListener);
+ gridPanel.add(_maxPointsField);
+ gridPanel.add(new JLabel(" "));
+ gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.simplify.maxerror")));
+ _distField = new DecimalNumberField();
+ _distField.addKeyListener(_paramChangeListener);
+ gridPanel.add(_distField);
+ _distUnitsCombo = new JComboBox(new String[] {
+ I18nManager.getText(UnitSetLibrary.UNITS_KILOMETRES.getNameKey()),
+ I18nManager.getText(UnitSetLibrary.UNITS_MILES.getNameKey())
+ });
+ gridPanel.add(_distUnitsCombo);
+ // radio buttons
+ _crossTrackRadio = new JRadioButton(I18nManager.getText("dialog.gpsbabel.filter.simplify.crosstrack"));
+ _crossTrackRadio.setSelected(true);
+ _lengthRadio = new JRadioButton(I18nManager.getText("dialog.gpsbabel.filter.simplify.length"));
+ _relativeRadio = new JRadioButton(I18nManager.getText("dialog.gpsbabel.filter.simplify.relative"));
+ ButtonGroup radioGroup = new ButtonGroup();
+ radioGroup.add(_crossTrackRadio);
+ radioGroup.add(_lengthRadio);
+ radioGroup.add(_relativeRadio);
+ gridPanel.add(_crossTrackRadio);
+ gridPanel.add(_lengthRadio);
+ gridPanel.add(_relativeRadio);
+ gridPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ boxPanel.add(gridPanel);
+ }
+
+ /**
+ * @return true if the filters are valid
+ */
+ public boolean isFilterValid()
+ {
+ final boolean countGiven = _maxPointsField.getText() != null && _maxPointsField.getText().trim().length() > 0;
+ final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
+ if ((!countGiven && !distGiven) || (countGiven && distGiven)) {
+ return false; // only one or the other allowed
+ }
+ if (countGiven && _maxPointsField.getValue() <= 1) {
+ return false; // must have a decent max points
+ }
+ if (distGiven && _distField.getValue() <= 0.001) {
+ return false; // no zero or negative distances allowed
+ }
+ // must be ok
+ return true;
+ }
+
+ /**
+ * @return filter parameters as a string, or null
+ */
+ protected String getParameters()
+ {
+ if (!isFilterValid()) return null;
+ StringBuilder builder = new StringBuilder();
+ // type
+ final boolean countGiven = _maxPointsField.getText() != null && _maxPointsField.getText().trim().length() > 0;
+ final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
+ if (countGiven) {
+ builder.append(",count=").append(_maxPointsField.getValue());
+ }
+ else if (distGiven)
+ {
+ double dValue = 1.0;
+ try {
+ dValue = Double.parseDouble(_distField.getText());
+ }
+ catch (Exception e) {} // shouldn't happen, otherwise validation would have failed
+ builder.append(",error=").append(dValue);
+ // units of distance (miles by default)
+ if (_distUnitsCombo.getSelectedIndex() == 0) {
+ builder.append("k"); // nothing for miles
+ }
+ }
+ // three options
+ if (_crossTrackRadio.isSelected()) {
+ builder.append(",crosstrack"); // default, could not pass it
+ }
+ else if (_lengthRadio.isSelected()) {
+ builder.append(",length");
+ }
+ else if (_relativeRadio.isSelected()) {
+ builder.append(",relative");
+ }
+ return builder.toString();
+ }
+}
diff --git a/tim/prune/load/xml/GzipFileLoader.java b/tim/prune/load/xml/GzipFileLoader.java
index 98ef946..3ebd4ca 100644
--- a/tim/prune/load/xml/GzipFileLoader.java
+++ b/tim/prune/load/xml/GzipFileLoader.java
@@ -7,7 +7,6 @@ import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import tim.prune.App;
import tim.prune.I18nManager;
-import tim.prune.data.Altitude;
import tim.prune.data.SourceInfo;
import tim.prune.load.MediaLinkInfo;
@@ -55,7 +54,7 @@ public class GzipFileLoader
SourceInfo sourceInfo = new SourceInfo(inFile,
(handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
_app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
- Altitude.Format.METRES, sourceInfo, handler.getTrackNameList(),
+ null, sourceInfo, handler.getTrackNameList(),
new MediaLinkInfo(inFile, handler.getLinkArray()));
}
}
diff --git a/tim/prune/load/xml/XmlFileLoader.java b/tim/prune/load/xml/XmlFileLoader.java
index 4c4239d..783ac79 100644
--- a/tim/prune/load/xml/XmlFileLoader.java
+++ b/tim/prune/load/xml/XmlFileLoader.java
@@ -10,9 +10,9 @@ import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
+
import tim.prune.App;
import tim.prune.I18nManager;
-import tim.prune.data.Altitude;
import tim.prune.data.SourceInfo;
import tim.prune.load.MediaLinkInfo;
@@ -86,7 +86,7 @@ public class XmlFileLoader extends DefaultHandler implements Runnable
SourceInfo sourceInfo = new SourceInfo(_file,
(_handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
_app.informDataLoaded(_handler.getFieldArray(), _handler.getDataArray(),
- Altitude.Format.METRES, sourceInfo, _handler.getTrackNameList(),
+ null, sourceInfo, _handler.getTrackNameList(),
new MediaLinkInfo(_handler.getLinkArray()));
}
}
diff --git a/tim/prune/load/xml/ZipFileLoader.java b/tim/prune/load/xml/ZipFileLoader.java
index fff0d92..74e48f9 100644
--- a/tim/prune/load/xml/ZipFileLoader.java
+++ b/tim/prune/load/xml/ZipFileLoader.java
@@ -11,7 +11,6 @@ import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import tim.prune.App;
-import tim.prune.data.Altitude;
import tim.prune.data.SourceInfo;
import tim.prune.load.MediaLinkInfo;
@@ -69,7 +68,7 @@ public class ZipFileLoader
SourceInfo sourceInfo = new SourceInfo(inFile,
(handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
_app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
- Altitude.Format.METRES, sourceInfo, handler.getTrackNameList(),
+ null, sourceInfo, handler.getTrackNameList(),
new MediaLinkInfo(inFile, handler.getLinkArray()));
xmlFound = true;
}
@@ -117,7 +116,7 @@ public class ZipFileLoader
{
// Send back to app
_app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
- Altitude.Format.METRES, new SourceInfo("gpsies", SourceInfo.FILE_TYPE.GPSIES),
+ new SourceInfo("gpsies", SourceInfo.FILE_TYPE.GPSIES),
handler.getTrackNameList());
xmlFound = true;
}
diff --git a/tim/prune/readme.txt b/tim/prune/readme.txt
index 6cddea5..f5379c3 100644
--- a/tim/prune/readme.txt
+++ b/tim/prune/readme.txt
@@ -1,5 +1,5 @@
-GpsPrune version 14.1
-=====================
+GpsPrune version 15
+===================
GpsPrune is an application for viewing, editing and managing coordinate data from GPS systems,
including format conversion, charting and photo correlation.
@@ -17,7 +17,7 @@ Running
=======
To run GpsPrune from the jar file, simply call it from a command prompt or shell:
- java -jar gpsprune_14.1.jar
+ java -jar gpsprune_15.jar
If the jar file is saved in a different directory, you will need to include the path.
Depending on your system settings, you may be able to click or double-click on the jar file
@@ -25,17 +25,25 @@ in a file manager window to execute it. A shortcut, menu item, alias, desktop i
or other link can of course be made should you wish.
To specify a language other than the default, use an additional parameter, eg:
- java -jar gpsprune_14.1.jar --lang=DE
+ java -jar gpsprune_15.jar --lang=DE
-New with version 14.1
+New with version 15
=====================
The following features were added since version 14:
- - Addition and correction of translations
- - Correction of version number in build scripts
+ - Extend povray output using map image on base plane
+ - Export an image of the map and track at a selected zoom level
+ - Estimation of hiking times and learining of parameter values
+ - Allow altitude / speed profile to show any arbitrary field
+ - Accept files dragged and dropped onto the GpsPrune window
+ - Take account of timezone if present in track timestamps
+ - Allow timestamp exports in KML using gx extensions
+ - GPSBabel filters
+ - Improved wikipedia name lookup
+ - Allow loading of speeds and vertical speeds from text files
New with version 14
-===================
+=====================
The following features were added since version 13:
- Dragging of existing points
- Creation of new points by dragging the halfway point between two points
diff --git a/tim/prune/save/BaseImageConfigDialog.java b/tim/prune/save/BaseImageConfigDialog.java
new file mode 100644
index 0000000..514ebd7
--- /dev/null
+++ b/tim/prune/save/BaseImageConfigDialog.java
@@ -0,0 +1,495 @@
+package tim.prune.save;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.io.File;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.DataSubscriber;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.Track;
+import tim.prune.gui.map.MapSource;
+import tim.prune.gui.map.MapSourceLibrary;
+
+/**
+ * Dialog to let you choose the parameters for a base image
+ * (source and zoom)
+ */
+public class BaseImageConfigDialog implements Runnable
+{
+ /** Parent to notify */
+ private DataSubscriber _parent = null;
+ /** Parent dialog for position */
+ private JDialog _parentDialog = null;
+ /** Track to use for preview image */
+ private Track _track = null;
+ /** Dialog to show */
+ private JDialog _dialog = null;
+ /** Checkbox for using an image or not */
+ private JCheckBox _useImageCheckbox = null;
+ /** Panel to hold the other controls */
+ private JPanel _mainPanel = null;
+ /** Dropdown for map source */
+ private JComboBox _mapSourceDropdown = null;
+ /** Dropdown for zoom levels */
+ private JComboBox _zoomDropdown = null;
+ /** Warning label that image is incomplete */
+ private JLabel _imageIncompleteLabel = null;
+ /** Label for number of tiles found */
+ private JLabel _tilesFoundLabel = null;
+ /** Label for image size in pixels */
+ private JLabel _imageSizeLabel = null;
+ /** Image preview panel */
+ private ImagePreviewPanel _previewPanel = null;
+ /** OK button, needs to be enabled/disabled */
+ private JButton _okButton = null;
+ /** Flag for rebuilding dialog, don't bother refreshing and recalculating */
+ private boolean _rebuilding = false;
+ /** Cached values to allow cancellation of dialog */
+ private boolean _useImage = false;
+ private int _sourceIndex = 0;
+ private int _zoomLevel = 0;
+
+
+ /**
+ * Constructor
+ * @param inParent parent object to notify on completion of dialog
+ * @param inParentDialog parent dialog
+ * @param inTrack track object
+ */
+ public BaseImageConfigDialog(DataSubscriber inParent, JDialog inParentDialog, Track inTrack)
+ {
+ _parent = inParent;
+ _parentDialog = inParentDialog;
+ _dialog = new JDialog(inParentDialog, I18nManager.getText("dialog.baseimage.title"), true);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ _track = inTrack;
+ }
+
+ /**
+ * Begin the function
+ */
+ public void begin()
+ {
+ initDialog();
+ _dialog.setLocationRelativeTo(_parentDialog);
+ _dialog.setVisible(true);
+ }
+
+ /**
+ * Begin the function with a default of using an image
+ */
+ public void beginWithImageYes()
+ {
+ initDialog();
+ _useImageCheckbox.setSelected(true);
+ refreshDialog();
+ _dialog.setVisible(true);
+ }
+
+ /**
+ * Initialise the dialog from the cached values
+ */
+ private void initDialog()
+ {
+ _rebuilding = true;
+ _useImageCheckbox.setSelected(_useImage);
+ // Populate the dropdown of map sources from the library in case it has changed
+ _mapSourceDropdown.removeAllItems();
+ for (int i=0; i= _mapSourceDropdown.getItemCount()) {
+ _sourceIndex = 0;
+ }
+ _mapSourceDropdown.setSelectedIndex(_sourceIndex);
+
+ // Zoom level
+ if (_useImage)
+ {
+ for (int i=0; i<_zoomDropdown.getItemCount(); i++)
+ {
+ String item = _zoomDropdown.getItemAt(i).toString();
+ try {
+ if (Integer.parseInt(item) == _zoomLevel) {
+ _zoomDropdown.setSelectedIndex(i);
+ break;
+ }
+ }
+ catch (NumberFormatException nfe) {}
+ }
+ }
+ _rebuilding = false;
+ refreshDialog();
+ }
+
+ /**
+ * Update the visibility of the controls, and update the zoom dropdown based on the selected map source
+ */
+ private void refreshDialog()
+ {
+ _mainPanel.setVisible(_useImageCheckbox.isSelected());
+ // Exit if we're in the middle of something
+ if (_rebuilding) {return;}
+ int currentZoom = 0;
+ try {
+ currentZoom = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
+ }
+ catch (Exception nfe) {}
+ // Get the extent of the track so we can work out how big the images are going to be for each zoom level
+ // System.out.println("Ranges are: x=" + _track.getXRange().getRange() + ", y=" + _track.getYRange().getRange());
+ final double xyExtent = Math.max(_track.getXRange().getRange(), _track.getYRange().getRange());
+ int zoomToSelect = -1;
+
+ _rebuilding = true;
+ _zoomDropdown.removeAllItems();
+ if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getItemCount() > 0)
+ {
+ int currentSource = _mapSourceDropdown.getSelectedIndex();
+ for (int i=5; i<18; i++)
+ {
+ // How many pixels does this give?
+ final int zoomFactor = 1 << i;
+ final int pixCount = (int) (xyExtent * zoomFactor * 256);
+ if (pixCount > 100 // less than this isn't worth it
+ && pixCount < 4000 // don't want to run out of memory
+ && isZoomAvailable(i, MapSourceLibrary.getSource(currentSource)))
+ {
+ _zoomDropdown.addItem("" + i);
+ if (i == currentZoom) {
+ zoomToSelect = _zoomDropdown.getItemCount() - 1;
+ }
+ }
+ // else System.out.println("Not using zoom " + i + " because pixCount=" + pixCount + " and xyExtent=" + xyExtent);
+ }
+ }
+ _zoomDropdown.setSelectedIndex(zoomToSelect);
+ _rebuilding = false;
+
+ _okButton.setEnabled(!_useImageCheckbox.isSelected() ||
+ (_zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0));
+ updateImagePreview();
+ }
+
+ /**
+ * @return true if it should be possible to use an image, false if no disk cache or cache empty
+ */
+ public static boolean isImagePossible()
+ {
+ String path = Config.getConfigString(Config.KEY_DISK_CACHE);
+ if (path != null && !path.equals(""))
+ {
+ File cacheDir = new File(path);
+ if (cacheDir.exists() && cacheDir.isDirectory())
+ {
+ // Check if there are any directories in the cache
+ for (File subdir : cacheDir.listFiles())
+ {
+ if (subdir.exists() && subdir.isDirectory()) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * See if the requested zoom level is available
+ * @param inZoom zoom level
+ * @param inSource selected map source
+ * @return true if there is a zoom directory for each of the source's layers
+ */
+ private static boolean isZoomAvailable(int inZoom, MapSource inSource)
+ {
+ if (inSource == null) {return false;}
+ String path = Config.getConfigString(Config.KEY_DISK_CACHE);
+ if (path == null || path.equals("")) {
+ return false;
+ }
+ File cacheDir = new File(path);
+ if (!cacheDir.exists() || !cacheDir.isDirectory()) {
+ return false;
+ }
+ // First layer
+ File layer0 = new File(cacheDir, inSource.getSiteName(0) + inZoom);
+ if (!layer0.exists() || !layer0.isDirectory() || !layer0.canRead()) {
+ return false;
+ }
+ // Second layer, if any
+ if (inSource.getNumLayers() > 1)
+ {
+ File layer1 = new File(cacheDir, inSource.getSiteName(1) + inZoom);
+ if (!layer1.exists() || !layer1.isDirectory() || !layer1.canRead()) {
+ return false;
+ }
+ }
+ // must be ok
+ return true;
+ }
+
+
+ /**
+ * @return true if image has been selected
+ */
+ public boolean useImage() {
+ return _useImage;
+ }
+
+ /**
+ * @return index of selected image source
+ */
+ public int getSourceIndex() {
+ return _sourceIndex;
+ }
+
+ /**
+ * @return selected zoom level
+ */
+ public int getZoomLevel() {
+ return _zoomLevel;
+ }
+
+ /**
+ * Make the dialog components to select the options
+ * @return Component holding gui elements
+ */
+ private Component makeDialogComponents()
+ {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BorderLayout());
+ _useImageCheckbox = new JCheckBox(I18nManager.getText("dialog.baseimage.useimage"));
+ _useImageCheckbox.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4));
+ _useImageCheckbox.setHorizontalAlignment(JLabel.CENTER);
+ _useImageCheckbox.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ refreshDialog();
+ }
+ });
+ panel.add(_useImageCheckbox, BorderLayout.NORTH);
+
+ // Outer panel with the grid and the map preview
+ _mainPanel = new JPanel();
+ _mainPanel.setLayout(new BorderLayout(1, 10));
+ // Central stuff with labels and dropdowns
+ JPanel controlsPanel = new JPanel();
+ controlsPanel.setLayout(new GridLayout(0, 2, 10, 4));
+ // map source
+ JLabel sourceLabel = new JLabel(I18nManager.getText("dialog.baseimage.mapsource") + ": ");
+ sourceLabel.setHorizontalAlignment(JLabel.RIGHT);
+ controlsPanel.add(sourceLabel);
+ _mapSourceDropdown = new JComboBox();
+ _mapSourceDropdown.addItem("name of map source");
+ // Add listener to dropdown to change zoom levels
+ _mapSourceDropdown.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ refreshDialog();
+ }
+ });
+ controlsPanel.add(_mapSourceDropdown);
+ // zoom level
+ JLabel zoomLabel = new JLabel(I18nManager.getText("dialog.baseimage.zoom") + ": ");
+ zoomLabel.setHorizontalAlignment(JLabel.RIGHT);
+ controlsPanel.add(zoomLabel);
+ _zoomDropdown = new JComboBox();
+ // Add action listener to enable ok button when zoom changed
+ _zoomDropdown.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ if (_zoomDropdown.getSelectedIndex() >= 0) {
+ _okButton.setEnabled(true);
+ updateImagePreview();
+ }
+ }
+ });
+ controlsPanel.add(_zoomDropdown);
+ _mainPanel.add(controlsPanel, BorderLayout.NORTH);
+
+ JPanel imagePanel = new JPanel();
+ imagePanel.setLayout(new BorderLayout(10, 1));
+ // image preview
+ _previewPanel = new ImagePreviewPanel();
+ imagePanel.add(_previewPanel, BorderLayout.CENTER);
+
+ // Label panel on right
+ JPanel labelPanel = new JPanel();
+ labelPanel.setLayout(new BorderLayout());
+ _imageIncompleteLabel = new JLabel(I18nManager.getText("dialog.baseimage.incomplete"));
+ _imageIncompleteLabel.setForeground(Color.RED);
+ _imageIncompleteLabel.setVisible(false);
+ labelPanel.add(_imageIncompleteLabel, BorderLayout.NORTH);
+ JPanel labelGridPanel = new JPanel();
+ labelGridPanel.setLayout(new GridLayout(0, 2, 10, 4));
+ labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.tiles") + ": "));
+ _tilesFoundLabel = new JLabel("11 / 11");
+ labelGridPanel.add(_tilesFoundLabel);
+ labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.size") + ": "));
+ _imageSizeLabel = new JLabel("1430");
+ labelGridPanel.add(_imageSizeLabel);
+ labelGridPanel.add(new JLabel(" ")); // just for spacing
+ labelPanel.add(labelGridPanel, BorderLayout.SOUTH);
+ imagePanel.add(labelPanel, BorderLayout.EAST);
+
+ _mainPanel.add(imagePanel, BorderLayout.CENTER);
+ panel.add(_mainPanel, BorderLayout.CENTER);
+
+ // OK, Cancel buttons
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ _okButton = new JButton(I18nManager.getText("button.ok"));
+ _okButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ // Check values, maybe don't want to exit
+ if (!_useImageCheckbox.isSelected()
+ || (_mapSourceDropdown.getSelectedIndex() >= 0 && _zoomDropdown.getSelectedIndex() >= 0))
+ {
+ storeValues();
+ _dialog.dispose();
+ }
+ }
+ });
+ 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);
+ panel.add(buttonPanel, BorderLayout.SOUTH);
+
+ // Listener to close dialog if escape pressed
+ KeyAdapter closer = new KeyAdapter() {
+ public void keyReleased(KeyEvent e)
+ {
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ _dialog.dispose();
+ }
+ }
+ };
+ _useImageCheckbox.addKeyListener(closer);
+ _mapSourceDropdown.addKeyListener(closer);
+ _zoomDropdown.addKeyListener(closer);
+ _okButton.addKeyListener(closer);
+ cancelButton.addKeyListener(closer);
+
+ return panel;
+ }
+
+ /**
+ * Use the selected settings to make a preview image and (asynchronously) update the preview panel
+ */
+ private void updateImagePreview()
+ {
+ // Clear labels
+ _imageIncompleteLabel.setVisible(false);
+ _tilesFoundLabel.setText("");
+ _imageSizeLabel.setText("");
+ if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getSelectedIndex() >= 0
+ && _zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0)
+ {
+ _previewPanel.startLoading();
+ // Launch a separate thread to create an image and pass it to the preview panel
+ new Thread(this).start();
+ }
+ else {
+ // clear preview
+ _previewPanel.setImage(null);
+ }
+ }
+
+ /**
+ * Store the selected details in the variables
+ */
+ private void storeValues()
+ {
+ // Store values of controls in variables
+ _useImage = _useImageCheckbox.isSelected();
+ _sourceIndex = _mapSourceDropdown.getSelectedIndex();
+ try {
+ String zoomStr = _zoomDropdown.getSelectedItem().toString();
+ _zoomLevel = Integer.parseInt(zoomStr);
+ }
+ catch (Exception nfe) {
+ _zoomLevel = 0;
+ }
+ // Call parent to retrieve values
+ _parent.dataUpdated(DataSubscriber.ALL);
+ }
+
+ /**
+ * Run method for separate thread. Uses the current dialog parameters
+ * to trigger a call to the Grouter, and pass the image to the preview panel
+ */
+ public void run()
+ {
+ // Remember the current dropdown indices, so we know whether they've changed or not
+ final int mapIndex = _mapSourceDropdown.getSelectedIndex();
+ final int zoomIndex = _zoomDropdown.getSelectedIndex();
+ if (!_useImageCheckbox.isSelected() || mapIndex < 0 || zoomIndex < 0) {return;}
+
+ // Get the map source and zoom level
+ MapSource mapSource = MapSourceLibrary.getSource(mapIndex);
+ int zoomLevel = 0;
+ try {
+ zoomLevel = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
+ }
+ catch (Exception e) {}
+
+ // Use the Grouter to create an image (slow, blocks thread)
+ GroutedImage groutedImage = MapGrouter.createMapImage(_track, mapSource, zoomLevel);
+
+ // If the dialog hasn't changed, pass the generated image to the preview panel
+ if (_useImageCheckbox.isSelected()
+ && _mapSourceDropdown.getSelectedIndex() == mapIndex
+ && _zoomDropdown.getSelectedIndex() == zoomIndex
+ && groutedImage != null)
+ {
+ _previewPanel.setImage(groutedImage);
+ // Set values of labels
+ _imageIncompleteLabel.setVisible(!groutedImage.isComplete());
+ _tilesFoundLabel.setText(groutedImage.getNumTilesUsed() + " / " + groutedImage.getNumTilesTotal());
+ if (groutedImage.getImageSize() > 0) {
+ _imageSizeLabel.setText("" + groutedImage.getImageSize());
+ }
+ else {
+ _imageSizeLabel.setText("");
+ }
+ }
+ else
+ {
+ _previewPanel.setImage(null);
+ // Clear labels
+ _imageIncompleteLabel.setVisible(false);
+ _tilesFoundLabel.setText("");
+ _imageSizeLabel.setText("");
+ }
+ }
+
+ /**
+ * @return true if any map data has been found for the image
+ */
+ public boolean getFoundData()
+ {
+ return _useImage && _zoomLevel > 0 && _previewPanel != null && _previewPanel.getTilesFound();
+ }
+}
diff --git a/tim/prune/save/ExifSaver.java b/tim/prune/save/ExifSaver.java
index 020ef53..3c1a7b8 100644
--- a/tim/prune/save/ExifSaver.java
+++ b/tim/prune/save/ExifSaver.java
@@ -24,7 +24,6 @@ import tim.prune.ExternalTools;
import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.config.Config;
-import tim.prune.data.Altitude;
import tim.prune.data.Coordinate;
import tim.prune.data.DataPoint;
import tim.prune.data.Photo;
@@ -244,7 +243,7 @@ public class ExifSaver implements Runnable
{
// Only look at photos which are selected and whose status has changed since load
photo = entry.getPhoto();
- if (photo != null && photo.getOriginalStatus() != photo.getCurrentStatus())
+ if (photo != null && photo.isModified())
{
// Increment counter if save successful
if (savePhoto(photo, overwriteFlag, false)) {
@@ -415,7 +414,7 @@ public class ExifSaver implements Runnable
result[paramOffset + 3] = "-GPSLongitudeRef=" + inPoint.getLongitude().output(Coordinate.FORMAT_CARDINAL);
// add altitude if it has it
result[paramOffset + 4] = "-GPSAltitude="
- + (inPoint.hasAltitude()?inPoint.getAltitude().getValue(Altitude.Format.METRES):0);
+ + (inPoint.hasAltitude()?inPoint.getAltitude().getMetricValue():0);
result[paramOffset + 5] = "-GPSAltitudeRef='Above Sea Level'";
// add the filename to modify
result[paramOffset + 6] = inFile.getAbsolutePath();
diff --git a/tim/prune/save/FileSaver.java b/tim/prune/save/FileSaver.java
index 6be44eb..b4a73d4 100644
--- a/tim/prune/save/FileSaver.java
+++ b/tim/prune/save/FileSaver.java
@@ -35,7 +35,6 @@ import tim.prune.App;
import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.config.Config;
-import tim.prune.data.Altitude;
import tim.prune.data.Coordinate;
import tim.prune.data.DataPoint;
import tim.prune.data.Field;
@@ -43,6 +42,8 @@ import tim.prune.data.FieldList;
import tim.prune.data.RecentFile;
import tim.prune.data.Timestamp;
import tim.prune.data.Track;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
import tim.prune.load.GenericFileFilter;
import tim.prune.load.OneCharDocument;
@@ -72,7 +73,7 @@ public class FileSaver
private static final int[] FORMAT_COORDS = {Coordinate.FORMAT_NONE, Coordinate.FORMAT_DEG_MIN_SEC,
Coordinate.FORMAT_DEG_MIN, Coordinate.FORMAT_DEG};
- private static final Altitude.Format[] FORMAT_ALTS = {Altitude.Format.NO_FORMAT, Altitude.Format.METRES, Altitude.Format.FEET};
+ private static final Unit[] UNIT_ALTS = {null, UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_FEET};
private static final int[] FORMAT_TIMES = {Timestamp.FORMAT_ORIGINAL, Timestamp.FORMAT_LOCALE, Timestamp.FORMAT_ISO_8601};
@@ -428,11 +429,11 @@ public class FileSaver
for (int i=0; i<_coordUnitsRadios.length; i++)
if (_coordUnitsRadios[i].isSelected())
coordFormat = FORMAT_COORDS[i];
- Altitude.Format altitudeFormat = Altitude.Format.NO_FORMAT;
+ Unit altitudeUnit = null;
for (int i=0; i<_altitudeUnitsRadios.length; i++)
{
if (_altitudeUnitsRadios[i].isSelected()) {
- altitudeFormat = FORMAT_ALTS[i];
+ altitudeUnit = UNIT_ALTS[i];
}
}
// Get timestamp format
@@ -518,7 +519,7 @@ public class FileSaver
if (!firstField) {
buffer.append(delimiter);
}
- saveField(buffer, point, info.getField(), coordFormat, altitudeFormat, timestampFormat);
+ saveField(buffer, point, info.getField(), coordFormat, altitudeUnit, timestampFormat);
firstField = false;
}
}
@@ -567,11 +568,11 @@ public class FileSaver
* @param inPoint point object
* @param inField field object
* @param inCoordFormat coordinate format
- * @param inAltitudeFormat altitude format
+ * @param inAltitudeUnit altitude unit
* @param inTimestampFormat timestamp format
*/
private void saveField(StringBuffer inBuffer, DataPoint inPoint, Field inField,
- int inCoordFormat, Altitude.Format inAltitudeFormat, int inTimestampFormat)
+ int inCoordFormat, Unit inAltitudeUnit, int inTimestampFormat)
{
// Output field according to type
if (inField == Field.LATITUDE)
@@ -586,7 +587,7 @@ public class FileSaver
{
try
{
- inBuffer.append(inPoint.getAltitude().getStringValue(inAltitudeFormat));
+ inBuffer.append(inPoint.getAltitude().getStringValue(inAltitudeUnit));
}
catch (NullPointerException npe) {}
}
diff --git a/tim/prune/save/GpxExporter.java b/tim/prune/save/GpxExporter.java
index 19007ac..8d9df8a 100644
--- a/tim/prune/save/GpxExporter.java
+++ b/tim/prune/save/GpxExporter.java
@@ -35,7 +35,6 @@ import tim.prune.GpsPrune;
import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.config.Config;
-import tim.prune.data.Altitude;
import tim.prune.data.AudioClip;
import tim.prune.data.Coordinate;
import tim.prune.data.DataPoint;
@@ -45,6 +44,7 @@ import tim.prune.data.Photo;
import tim.prune.data.RecentFile;
import tim.prune.data.Timestamp;
import tim.prune.data.TrackInfo;
+import tim.prune.data.UnitSetLibrary;
import tim.prune.gui.DialogCloser;
import tim.prune.load.GenericFileFilter;
import tim.prune.save.xml.GpxCacherList;
@@ -530,7 +530,7 @@ public class GpxExporter extends GenericFunction implements Runnable
// Point has been modified - maybe it's possible to modify the source
source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
- source = replaceGpxTags(source, "", "", inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
+ source = replaceGpxTags(source, "", "", inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
source = replaceGpxTags(source, "", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
if (inPoint.isWaypoint())
{
@@ -726,7 +726,7 @@ public class GpxExporter extends GenericFunction implements Runnable
if (inPoint.hasAltitude())
{
inWriter.write("\t\t");
- inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
+ inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
inWriter.write("\n");
}
// timestamp if available (point might have timestamp and then be turned into a waypoint)
@@ -798,7 +798,7 @@ public class GpxExporter extends GenericFunction implements Runnable
if (inPoint.hasAltitude())
{
inWriter.write("");
- inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
+ inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
inWriter.write("");
}
// timestamp if available (and selected)
diff --git a/tim/prune/save/GroutedImage.java b/tim/prune/save/GroutedImage.java
new file mode 100644
index 0000000..4400067
--- /dev/null
+++ b/tim/prune/save/GroutedImage.java
@@ -0,0 +1,111 @@
+package tim.prune.save;
+
+import java.awt.image.BufferedImage;
+
+import tim.prune.data.DoubleRange;
+
+
+/**
+ * Class to represent the result of the MapGrouter's assembly of map tiles
+ * into a single image. Includes information about how complete the result is.
+ */
+public class GroutedImage
+{
+ private BufferedImage _image = null;
+ private int _numTilesFound = 0;
+ private int _numTilesMissing = 0;
+ private DoubleRange _xRange = null;
+ private DoubleRange _yRange = null;
+
+ /**
+ * Constructor
+ * @param inImage image, or null if no image possible
+ * @param inTilesUsed number of tiles used
+ * @param inTilesMissing number of tiles which could not be found
+ */
+ public GroutedImage(BufferedImage inImage, int inTilesUsed, int inTilesMissing)
+ {
+ _image = inImage;
+ _numTilesFound = inTilesUsed;
+ _numTilesMissing = inTilesMissing;
+ }
+
+ /**
+ * @return true if any content at all was found
+ */
+ public boolean isValid() {
+ return _image != null && _numTilesFound > 0;
+ }
+
+ /**
+ * @return true if all the required tiles were found
+ */
+ public boolean isComplete() {
+ return _numTilesMissing == 0;
+ }
+
+ /**
+ * @return the pixel dimensions of the result image
+ */
+ public int getImageSize()
+ {
+ if (_image == null) {return -1;}
+ return _image.getWidth();
+ }
+
+ /**
+ * @return the image object
+ */
+ public BufferedImage getImage() {
+ return _image;
+ }
+
+ /**
+ * @return the number of tiles used in the image
+ */
+ public int getNumTilesUsed() {
+ return _numTilesFound;
+ }
+
+ /**
+ * @return the number of tiles which could not be found, leaving gaps in the image
+ */
+ public int getNumTilesMissing() {
+ return _numTilesMissing;
+ }
+
+ /**
+ * @return the total number of tiles
+ */
+ public int getNumTilesTotal() {
+ return _numTilesFound + _numTilesMissing;
+ }
+
+ /**
+ * @param inRange x range of data
+ */
+ public void setXRange(DoubleRange inRange) {
+ _xRange = inRange;
+ }
+
+ /**
+ * @return x range of data
+ */
+ public DoubleRange getXRange() {
+ return _xRange;
+ }
+
+ /**
+ * @param inRange y range of data
+ */
+ public void setYRange(DoubleRange inRange) {
+ _yRange = inRange;
+ }
+
+ /**
+ * @return y range of data
+ */
+ public DoubleRange getYRange() {
+ return _yRange;
+ }
+}
diff --git a/tim/prune/save/ImageExporter.java b/tim/prune/save/ImageExporter.java
new file mode 100644
index 0000000..fabed33
--- /dev/null
+++ b/tim/prune/save/ImageExporter.java
@@ -0,0 +1,454 @@
+package tim.prune.save;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.border.EtchedBorder;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.ColourScheme;
+import tim.prune.config.Config;
+import tim.prune.data.DataPoint;
+import tim.prune.data.DoubleRange;
+import tim.prune.data.Track;
+import tim.prune.gui.GuiGridLayout;
+import tim.prune.gui.WholeNumberField;
+import tim.prune.gui.map.MapSource;
+import tim.prune.gui.map.MapSourceLibrary;
+import tim.prune.gui.map.MapUtils;
+import tim.prune.load.GenericFileFilter;
+
+/**
+ * Class to handle the exporting of map images, optionally with track data drawn on top.
+ * This allows images larger than the screen to be generated.
+ */
+public class ImageExporter extends GenericFunction implements DataSubscriber
+{
+ private JDialog _dialog = null;
+ private JCheckBox _drawDataCheckbox = null;
+ private WholeNumberField _textScaleField = null;
+ private JLabel _baseImageLabel = null;
+ private BaseImageConfigDialog _baseImageConfig = null;
+ private JFileChooser _fileChooser = null;
+ private JButton _okButton = null;
+
+ /**
+ * Constructor
+ * @param inApp App object
+ */
+ public ImageExporter(App inApp)
+ {
+ super(inApp);
+ }
+
+ /** Get the name key */
+ public String getNameKey() {
+ return "function.exportimage";
+ }
+
+ /**
+ * Begin the function by showing the input dialog
+ */
+ public void begin()
+ {
+ // Make dialog window
+ if (_dialog == null)
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ _textScaleField.setValue(100);
+ }
+ // Make base image dialog too
+ if (_baseImageConfig == null) {
+ _baseImageConfig = new BaseImageConfigDialog(this, _dialog, _app.getTrackInfo().getTrack());
+ }
+
+ // Check if there is a cache to use
+ if (!BaseImageConfigDialog.isImagePossible())
+ {
+ _app.showErrorMessage(getNameKey(), "dialog.exportimage.noimagepossible");
+ return;
+ }
+
+ updateBaseImageDetails();
+ // Show dialog
+ _dialog.setVisible(true);
+ }
+
+ /**
+ * Make the dialog components to select the export options
+ * @return Component holding gui elements
+ */
+ private Component makeDialogComponents()
+ {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BorderLayout(4, 4));
+ // Checkbox for drawing track or not
+ _drawDataCheckbox = new JCheckBox(I18nManager.getText("dialog.exportimage.drawtrack"));
+ _drawDataCheckbox.setSelected(true); // draw by default
+
+ // TODO: Maybe have other controls such as line width, symbol scale factor
+ JPanel controlsPanel = new JPanel();
+ GuiGridLayout grid = new GuiGridLayout(controlsPanel);
+ grid.add(new JLabel(I18nManager.getText("dialog.exportimage.textscalepercent") + ": "));
+ _textScaleField = new WholeNumberField(3);
+ _textScaleField.setText("888");
+ grid.add(_textScaleField);
+
+ // OK, Cancel buttons
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ _okButton = new JButton(I18nManager.getText("button.ok"));
+ _okButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ doExport();
+ MapGrouter.clearMapImage();
+ _dialog.dispose();
+ }
+ });
+ buttonPanel.add(_okButton);
+ JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ MapGrouter.clearMapImage();
+ _dialog.dispose();
+ }
+ });
+ buttonPanel.add(cancelButton);
+ panel.add(buttonPanel, BorderLayout.SOUTH);
+
+ // Listener to close dialog if escape pressed
+ KeyAdapter closer = new KeyAdapter() {
+ public void keyReleased(KeyEvent e)
+ {
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ _dialog.dispose();
+ MapGrouter.clearMapImage();
+ }
+ }
+ };
+ _drawDataCheckbox.addKeyListener(closer);
+
+ // Panel for the base image
+ JPanel imagePanel = new JPanel();
+ imagePanel.setLayout(new BorderLayout(10, 4));
+ imagePanel.add(new JLabel(I18nManager.getText("dialog.exportpov.baseimage") + ": "), BorderLayout.WEST);
+ _baseImageLabel = new JLabel("Typical sourcename");
+ imagePanel.add(_baseImageLabel, BorderLayout.CENTER);
+ JButton baseImageButton = new JButton(I18nManager.getText("button.edit"));
+ baseImageButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ changeBaseImage();
+ }
+ });
+ baseImageButton.addKeyListener(closer);
+ imagePanel.add(baseImageButton, BorderLayout.EAST);
+ imagePanel.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4))
+ );
+
+ // add these panels to the holder panel
+ JPanel holderPanel = new JPanel();
+ holderPanel.setLayout(new BorderLayout(5, 5));
+ holderPanel.add(_drawDataCheckbox, BorderLayout.NORTH);
+ holderPanel.add(controlsPanel, BorderLayout.CENTER);
+ holderPanel.add(imagePanel, BorderLayout.SOUTH);
+ holderPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+ panel.add(holderPanel, BorderLayout.NORTH);
+ return panel;
+ }
+
+ /**
+ * Change the base image by calling the BaseImageConfigDialog
+ */
+ private void changeBaseImage()
+ {
+ // Check if there is a cache to use
+ if (BaseImageConfigDialog.isImagePossible())
+ {
+ // Show new dialog to choose image details
+ _baseImageConfig.beginWithImageYes();
+ }
+ }
+
+ /**
+ * Callback from base image config dialog
+ */
+ public void dataUpdated(byte inUpdateType)
+ {
+ updateBaseImageDetails();
+ }
+
+ /** Not required */
+ public void actionCompleted(String inMessage) {
+ }
+
+ /**
+ * Update the description label according to the selected base image details
+ */
+ private void updateBaseImageDetails()
+ {
+ String desc = null;
+ if (_baseImageConfig.useImage())
+ {
+ MapSource source = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex());
+ if (source != null) {
+ desc = source.getName() + " ("
+ + _baseImageConfig.getZoomLevel() + ")";
+ }
+ }
+ if (desc == null) {
+ desc = I18nManager.getText("dialog.about.no");
+ }
+ _baseImageLabel.setText(desc);
+ _okButton.setEnabled(_baseImageConfig.useImage() && _baseImageConfig.getFoundData()
+ && MapGrouter.isZoomLevelOk(_app.getTrackInfo().getTrack(), _baseImageConfig.getZoomLevel()));
+ }
+
+ /**
+ * Select the file and export data to it
+ */
+ private void doExport()
+ {
+ // OK pressed, so choose output file
+ _okButton.setEnabled(false);
+ if (_fileChooser == null)
+ {
+ _fileChooser = new JFileChooser();
+ _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+ _fileChooser.setFileFilter(new GenericFileFilter("filetype.png", new String[] {"png"}));
+ _fileChooser.setAcceptAllFileFilterUsed(false);
+ // start from directory in config which should be set
+ final String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
+ if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));}
+ }
+
+ // Allow choose again if an existing file is selected
+ boolean chooseAgain = false;
+ do
+ {
+ chooseAgain = false;
+ if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
+ {
+ // OK pressed and file chosen
+ File pngFile = _fileChooser.getSelectedFile();
+ if (!pngFile.getName().toLowerCase().endsWith(".png"))
+ {
+ pngFile = new File(pngFile.getAbsolutePath() + ".png");
+ }
+ // Check if file exists and if necessary prompt for overwrite
+ Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
+ if (!pngFile.exists() || JOptionPane.showOptionDialog(_parentFrame,
+ I18nManager.getText("dialog.save.overwrite.text"),
+ I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
+ JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
+ == JOptionPane.YES_OPTION)
+ {
+ // Export the file
+ if (!exportFile(pngFile))
+ {
+ // export failed so need to choose again
+ chooseAgain = true;
+ }
+ }
+ else
+ {
+ // overwrite cancelled so need to choose again
+ chooseAgain = true;
+ }
+ }
+ } while (chooseAgain);
+ }
+
+ /**
+ * Export the track data to the specified file
+ * @param inPngFile File object to save to
+ * @return true if successful
+ */
+ private boolean exportFile(File inPngFile)
+ {
+ // Get the image file from the grouter
+ MapSource source = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex());
+ GroutedImage baseImage = MapGrouter.getMapImage(_app.getTrackInfo().getTrack(), source,
+ _baseImageConfig.getZoomLevel());
+ if (baseImage == null || !baseImage.isValid())
+ {
+ _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage");
+ return true;
+ }
+ try
+ {
+ if (_drawDataCheckbox.isSelected())
+ {
+ // Draw the track on top of this image
+ drawData(baseImage);
+ }
+ // Write composite image to file
+ if (!ImageIO.write(baseImage.getImage(), "png", inPngFile)) {
+ _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage");
+ return false; // choose again - the image creation worked but the save failed
+ }
+ }
+ catch (IOException ioe) {
+ System.err.println("Can't write image: " + ioe.getClass().getName());
+ }
+ return true;
+ }
+
+ /**
+ * Draw the track and waypoint data from the current Track onto the given image
+ * @param inImage GroutedImage from map tiles
+ */
+ private void drawData(GroutedImage inImage)
+ {
+ // Work out x, y limits for drawing
+ DoubleRange xRange = inImage.getXRange();
+ DoubleRange yRange = inImage.getYRange();
+ int zoomFactor = 1 << _baseImageConfig.getZoomLevel();
+ Graphics g = inImage.getImage().getGraphics();
+ // TODO: Set colour, line width
+ g.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
+
+ // Loop over points
+ final Track track = _app.getTrackInfo().getTrack();
+ final int numPoints = track.getNumPoints();
+ int prevX = 0, prevY = 0;
+ for (int i=0; i 1)
+ {
+ if (_baseImage.isValid())
+ {
+ inG.drawImage(_baseImage.getImage(),
+ (width-previewSize)/2, (height-previewSize)/2, previewSize, previewSize, this);
+ }
+ else
+ {
+ // No content found at all
+ inG.setColor(EMPTY_IMAGE_COLOUR);
+ inG.fillRect((width-previewSize)/2, (height-previewSize)/2, previewSize, previewSize);
+ }
+ // draw frame around it to make it more obvious
+ inG.setColor(Color.BLACK);
+ inG.drawRect((width-previewSize)/2, (height-previewSize)/2, previewSize-1, previewSize-1);
+ }
+ }
+ }
+
+ /**
+ * @return true if there is an image to use and it contains map tiles
+ */
+ public boolean getTilesFound()
+ {
+ return _baseImage != null && _baseImage.isValid() && _baseImage.getImageSize() > 1
+ && _baseImage.getNumTilesUsed() > 0;
+ }
+}
diff --git a/tim/prune/save/KmlExporter.java b/tim/prune/save/KmlExporter.java
index e1cc151..6fbf749 100644
--- a/tim/prune/save/KmlExporter.java
+++ b/tim/prune/save/KmlExporter.java
@@ -23,6 +23,7 @@ import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.swing.Box;
import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
@@ -32,6 +33,7 @@ import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
+import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
@@ -41,17 +43,19 @@ import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.config.ColourUtils;
import tim.prune.config.Config;
-import tim.prune.data.Altitude;
import tim.prune.data.Coordinate;
import tim.prune.data.DataPoint;
import tim.prune.data.Field;
import tim.prune.data.RecentFile;
+import tim.prune.data.Timestamp;
import tim.prune.data.Track;
import tim.prune.data.TrackInfo;
+import tim.prune.data.UnitSetLibrary;
import tim.prune.gui.ColourChooser;
import tim.prune.gui.ColourPatch;
import tim.prune.gui.DialogCloser;
import tim.prune.gui.ImageUtils;
+import tim.prune.gui.WholeNumberField;
import tim.prune.load.GenericFileFilter;
import tim.prune.save.xml.XmlUtils;
@@ -66,9 +70,12 @@ public class KmlExporter extends GenericFunction implements Runnable
private JDialog _dialog = null;
private JTextField _descriptionField = null;
private PointTypeSelector _pointTypeSelector = null;
+ private JRadioButton _gxExtensionsRadio = null;
private JCheckBox _altitudesCheckbox = null;
private JCheckBox _kmzCheckbox = null;
private JCheckBox _exportImagesCheckbox = null;
+ private JLabel _imageSizeLabel = null;
+ private WholeNumberField _imageSizeField = null;
private ColourPatch _colourPatch = null;
private JLabel _progressLabel = null;
private JProgressBar _progressBar = null;
@@ -83,7 +90,6 @@ public class KmlExporter extends GenericFunction implements Runnable
private static final String KML_FILENAME_IN_KMZ = "doc.kml";
// Default width and height of thumbnail images in Kmz
private static final int DEFAULT_THUMBNAIL_WIDTH = 240;
- private static final int DEFAULT_THUMBNAIL_HEIGHT = 240;
// Default track colour
private static final Color DEFAULT_TRACK_COLOUR = new Color(204, 0, 0); // red
@@ -119,6 +125,8 @@ public class KmlExporter extends GenericFunction implements Runnable
_dialog.pack();
_colourChooser = new ColourChooser(_dialog);
}
+ // Fill in image size from config
+ _imageSizeField.setValue(Config.getConfigInt(Config.KEY_KMZ_IMAGE_SIZE));
enableCheckboxes();
_descriptionField.setEnabled(true);
_okButton.setEnabled(true);
@@ -169,19 +177,31 @@ public class KmlExporter extends GenericFunction implements Runnable
colourPanel.add(new JLabel(I18nManager.getText("dialog.exportkml.trackcolour")));
colourPanel.add(_colourPatch);
mainPanel.add(colourPanel);
+ // Pair of radio buttons for standard/extended KML
+ JRadioButton standardKmlRadio = new JRadioButton(I18nManager.getText("dialog.exportkml.standardkml"));
+ _gxExtensionsRadio = new JRadioButton(I18nManager.getText("dialog.exportkml.extendedkml"));
+ ButtonGroup bGroup = new ButtonGroup();
+ bGroup.add(standardKmlRadio); bGroup.add(_gxExtensionsRadio);
+ JPanel radioPanel = new JPanel();
+ radioPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 1));
+ radioPanel.add(standardKmlRadio);
+ radioPanel.add(_gxExtensionsRadio);
+ standardKmlRadio.setSelected(true);
+ mainPanel.add(radioPanel);
// Checkbox for altitude export
_altitudesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.altitude"));
_altitudesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
_altitudesCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
mainPanel.add(_altitudesCheckbox);
+
// Checkboxes for kmz export and image export
_kmzCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.kmz"));
_kmzCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
_kmzCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
+ // enable image checkbox if kmz activated
_kmzCheckbox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
- // enable image checkbox if kmz activated
enableCheckboxes();
}
});
@@ -189,7 +209,23 @@ public class KmlExporter extends GenericFunction implements Runnable
_exportImagesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.exportimages"));
_exportImagesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
_exportImagesCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
+ // enable image size fields if image checkbox changes
+ _exportImagesCheckbox.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ enableImageSizeFields();
+ }
+ });
mainPanel.add(_exportImagesCheckbox);
+ // Panel for the image size
+ JPanel imageSizePanel = new JPanel();
+ imageSizePanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+ _imageSizeLabel = new JLabel(I18nManager.getText("dialog.exportkml.imagesize"));
+ _imageSizeLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+ imageSizePanel.add(_imageSizeLabel);
+ _imageSizeField = new WholeNumberField(4);
+ imageSizePanel.add(_imageSizeField);
+ mainPanel.add(imageSizePanel);
+
mainPanel.add(Box.createVerticalStrut(10));
_progressLabel = new JLabel("...");
_progressLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
@@ -237,9 +273,26 @@ public class KmlExporter extends GenericFunction implements Runnable
boolean hasPhotos = _trackInfo.getPhotoList() != null && _trackInfo.getPhotoList().getNumPhotos() > 0;
_exportImagesCheckbox.setSelected(hasPhotos && _kmzCheckbox.isSelected());
_exportImagesCheckbox.setEnabled(hasPhotos && _kmzCheckbox.isSelected());
+ enableImageSizeFields();
+ }
+
+ /**
+ * Enable and disable the image size fields according to the checkboxes
+ */
+ private void enableImageSizeFields()
+ {
+ boolean exportImages = _exportImagesCheckbox.isEnabled() && _exportImagesCheckbox.isSelected();
+ _imageSizeField.setEnabled(exportImages);
+ _imageSizeLabel.setEnabled(exportImages);
}
+ /**
+ * @return true if using gx extensions for kml export
+ */
+ private boolean useGxExtensions() {
+ return _gxExtensionsRadio.isSelected();
+ }
/**
* Start the export process based on the input parameters
*/
@@ -325,11 +378,6 @@ public class KmlExporter extends GenericFunction implements Runnable
boolean exportImages = exportToKmz && _exportImagesCheckbox.isSelected();
_progressBar.setMaximum(exportImages?getNumPhotosToExport():1);
- // Determine photo thumbnail size from config
- int thumbWidth = Config.getConfigInt(Config.KEY_KMZ_IMAGE_WIDTH);
- if (thumbWidth < DEFAULT_THUMBNAIL_WIDTH) {thumbWidth = DEFAULT_THUMBNAIL_WIDTH;}
- int thumbHeight = Config.getConfigInt(Config.KEY_KMZ_IMAGE_HEIGHT);
- if (thumbHeight < DEFAULT_THUMBNAIL_HEIGHT) {thumbHeight = DEFAULT_THUMBNAIL_HEIGHT;}
// Create array for image dimensions in case it's required
_imageDimensions = new Dimension[_track.getNumPoints()];
@@ -350,9 +398,14 @@ public class KmlExporter extends GenericFunction implements Runnable
// Export images into zip file too if requested
if (exportImages)
{
+ // Get entered value for image size, store in config
+ int thumbSize = _imageSizeField.getValue();
+ if (thumbSize < DEFAULT_THUMBNAIL_WIDTH) {thumbSize = DEFAULT_THUMBNAIL_WIDTH;}
+ Config.setConfigInt(Config.KEY_KMZ_IMAGE_SIZE, thumbSize);
+
// Create thumbnails of each photo in turn and add to zip as images/image.jpg
// This is done first so that photo sizes are known for later
- exportThumbnails(zipOutputStream, thumbWidth, thumbHeight);
+ exportThumbnails(zipOutputStream, thumbSize);
}
writer = new OutputStreamWriter(zipOutputStream);
// Make an entry in the zip file for the kml file
@@ -420,8 +473,14 @@ public class KmlExporter extends GenericFunction implements Runnable
boolean writePhotos = _pointTypeSelector.getPhotopointsSelected();
boolean writeAudios = _pointTypeSelector.getAudiopointsSelected();
boolean justSelection = _pointTypeSelector.getJustSelection();
- inWriter.write("\n\n\n");
- inWriter.write("\t");
+ // Define xml header (depending on whether extensions are used or not)
+ if (useGxExtensions()) {
+ inWriter.write("\n\n");
+ }
+ else {
+ inWriter.write("\n\n");
+ }
+ inWriter.write("\n\t");
if (_descriptionField != null && _descriptionField.getText() != null && !_descriptionField.getText().equals(""))
{
inWriter.write(_descriptionField.getText());
@@ -492,51 +551,168 @@ public class KmlExporter extends GenericFunction implements Runnable
// Make a line for the track, if there is one
if (hasTrackpoints && writeTrack)
{
- // Set up strings for start and end of track segment
- String trackStart = "\t\n\t\ttrack\n\t\t\n\t\t\n";
- if (absoluteAltitudes) {
- trackStart += "\t\t\t1\n\t\t\tabsolute\n";
+ boolean useGxExtensions = _gxExtensionsRadio.isSelected();
+ if (useGxExtensions)
+ {
+ // Write track using the Google Extensions to KML including gx:Track
+ numSaved += writeGxTrack(inWriter, absoluteAltitudes, selStart, selEnd);
}
else {
- trackStart += "\t\t\tclampToGround\n";
+ // Write track using standard KML
+ numSaved += writeStandardTrack(inWriter, absoluteAltitudes, selStart, selEnd);
}
- trackStart += "\t\t\t";
- String trackEnd = "\t\t\t\n\t\t\n\t";
-
- // Start segment
- inWriter.write(trackStart);
- // Loop over track points
- boolean firstTrackpoint = true;
- for (i=0; i\n");
+ return numSaved;
+ }
+
+
+ /**
+ * Write out the track using standard KML LineString tag
+ * @param inWriter writer object to write to
+ * @param inAbsoluteAltitudes true to use absolute altitudes, false to clamp to ground
+ * @param inSelStart start index of selection, or -1 if whole track
+ * @param inSelEnd end index of selection, or -1 if whole track
+ * @return number of track points written
+ */
+ private int writeStandardTrack(OutputStreamWriter inWriter, boolean inAbsoluteAltitudes, int inSelStart,
+ int inSelEnd)
+ throws IOException
+ {
+ int numSaved = 0;
+ // Set up strings for start and end of track segment
+ String trackStart = "\t\n\t\ttrack\n\t\t\n\t\t\n";
+ if (inAbsoluteAltitudes) {
+ trackStart += "\t\t\t1\n\t\t\tabsolute\n";
+ }
+ else {
+ trackStart += "\t\t\tclampToGround\n";
+ }
+ trackStart += "\t\t\t";
+ String trackEnd = "\t\t\t\n\t\t\n\t";
+
+ boolean justSelection = _pointTypeSelector.getJustSelection();
+
+ // Start segment
+ inWriter.write(trackStart);
+ // Loop over track points
+ boolean firstTrackpoint = true;
+ final int numPoints = _track.getNumPoints();
+ for (int i=0; i=inSelStart && i<=inSelEnd);
+ if (!point.isWaypoint() && writeCurrentPoint)
{
- point = _track.getPoint(i);
- boolean writeCurrentPoint = !justSelection || (i>=selStart && i<=selEnd);
- if (!point.isWaypoint() && writeCurrentPoint)
+ // start new track segment if necessary
+ if (point.getSegmentStart() && !firstTrackpoint) {
+ inWriter.write(trackEnd);
+ inWriter.write(trackStart);
+ }
+ if (point.getPhoto() == null)
{
- // start new track segment if necessary
- if (point.getSegmentStart() && !firstTrackpoint) {
- inWriter.write(trackEnd);
- inWriter.write(trackStart);
+ exportTrackpoint(point, inWriter);
+ numSaved++;
+ firstTrackpoint = false;
+ }
+ }
+ }
+ // end segment
+ inWriter.write(trackEnd);
+ return numSaved;
+ }
+
+
+ /**
+ * Write out the track using Google's KML Extensions such as gx:Track
+ * @param inWriter writer object to write to
+ * @param inAbsoluteAltitudes true to use absolute altitudes, false to clamp to ground
+ * @param inSelStart start index of selection, or -1 if whole track
+ * @param inSelEnd end index of selection, or -1 if whole track
+ * @return number of track points written
+ */
+ private int writeGxTrack(OutputStreamWriter inWriter, boolean inAbsoluteAltitudes, int inSelStart,
+ int inSelEnd)
+ throws IOException
+ {
+ int numSaved = 0;
+ // Set up strings for start and end of track segment
+ String trackStart = "\t\n\t\ttrack\n\t\t\n\t\t\n";
+ if (inAbsoluteAltitudes) {
+ trackStart += "\t\t\t1\n\t\t\tabsolute\n";
+ }
+ else {
+ trackStart += "\t\t\tclampToGround\n";
+ }
+ String trackEnd = "\n\t\t\n\t\n";
+
+ boolean justSelection = _pointTypeSelector.getJustSelection();
+
+ // Start segment
+ inWriter.write(trackStart);
+ StringBuilder whenList = new StringBuilder();
+ StringBuilder coordList = new StringBuilder();
+
+ // Loop over track points
+ boolean firstTrackpoint = true;
+ final int numPoints = _track.getNumPoints();
+ for (int i=0; i=inSelStart && i<=inSelEnd);
+ if (!point.isWaypoint() && writeCurrentPoint)
+ {
+ // start new track segment if necessary
+ if (point.getSegmentStart() && !firstTrackpoint)
+ {
+ inWriter.write(whenList.toString());
+ inWriter.write('\n');
+ inWriter.write(coordList.toString());
+ inWriter.write('\n');
+ inWriter.write(trackEnd);
+ whenList.setLength(0); coordList.setLength(0);
+ inWriter.write(trackStart);
+ }
+ if (point.getPhoto() == null)
+ {
+ // Add timestamp (if any) to the list
+ whenList.append("");
+ if (point.hasTimestamp()) {
+ whenList.append(point.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
}
- if (point.getPhoto() == null)
- {
- exportTrackpoint(point, inWriter);
- numSaved++;
- firstTrackpoint = false;
+ whenList.append("\n");
+ // Add coordinates to the list
+ coordList.append("");
+ coordList.append(point.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT)).append(' ');
+ coordList.append(point.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT)).append(' ');
+ if (point.hasAltitude()) {
+ coordList.append("" + point.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
}
+ else {
+ coordList.append('0');
+ }
+ coordList.append("\n");
+ numSaved++;
+ firstTrackpoint = false;
}
}
- // end segment
- inWriter.write(trackEnd);
}
- inWriter.write("\n");
+ // end segment
+ inWriter.write(whenList.toString());
+ inWriter.write('\n');
+ inWriter.write(coordList.toString());
+ inWriter.write('\n');
+ inWriter.write(trackEnd);
return numSaved;
}
+
/**
* Reverse the hex code for the colours for KML's stupid backwards format
* @param inCode colour code rrggbb
@@ -652,7 +828,7 @@ public class KmlExporter extends GenericFunction implements Runnable
inWriter.write(',');
// Altitude if point has one
if (inPoint.hasAltitude()) {
- inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
+ inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
}
else {
inWriter.write('0');
@@ -674,7 +850,7 @@ public class KmlExporter extends GenericFunction implements Runnable
// Altitude if point has one
inWriter.write(',');
if (inPoint.hasAltitude()) {
- inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
+ inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
}
else {
inWriter.write('0');
@@ -686,10 +862,9 @@ public class KmlExporter extends GenericFunction implements Runnable
/**
* Loop through the photos and create thumbnails
* @param inZipStream zip stream to save image files to
- * @param inThumbWidth thumbnail width
- * @param inThumbHeight thumbnail height
+ * @param inThumbSize thumbnail size
*/
- private void exportThumbnails(ZipOutputStream inZipStream, int inThumbWidth, int inThumbHeight)
+ private void exportThumbnails(ZipOutputStream inZipStream, int inThumbSize)
throws IOException
{
// set up image writer
@@ -724,9 +899,9 @@ public class KmlExporter extends GenericFunction implements Runnable
// Load image and write to outstream
ImageIcon icon = point.getPhoto().createImageIcon();
- // Scale image to required size TODO: should it also be smoothed, or only if it's smaller than a certain size?
+ // Scale image to required size (not smoothed)
BufferedImage bufferedImage = ImageUtils.rotateImage(icon.getImage(),
- inThumbWidth, inThumbHeight, point.getPhoto().getRotationDegrees());
+ inThumbSize, inThumbSize, point.getPhoto().getRotationDegrees());
// Store image dimensions so that it doesn't have to be calculated again for the points
_imageDimensions[i] = new Dimension(bufferedImage.getWidth(), bufferedImage.getHeight());
diff --git a/tim/prune/save/MapGrouter.java b/tim/prune/save/MapGrouter.java
new file mode 100644
index 0000000..65e23c0
--- /dev/null
+++ b/tim/prune/save/MapGrouter.java
@@ -0,0 +1,139 @@
+package tim.prune.save;
+
+import tim.prune.config.Config;
+import tim.prune.data.DoubleRange;
+import tim.prune.data.Track;
+import tim.prune.data.TrackExtents;
+import tim.prune.gui.map.DiskTileCacher;
+import tim.prune.gui.map.MapSource;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+
+
+/**
+ * Class to handle the sticking together (grouting) of map tiles
+ * to create a single map image for the current track
+ */
+public abstract class MapGrouter
+{
+ /** The most recently produced image */
+ private static GroutedImage _lastGroutedImage = null;
+
+ /**
+ * Clear the last image, it's not needed any more
+ */
+ public static void clearMapImage() {
+ _lastGroutedImage = null;
+ }
+
+ /**
+ * Grout the required map tiles together according to the track's extent
+ * @param inTrack track object
+ * @param inMapSource map source to use (may have one or two layers)
+ * @param inZoom selected zoom level
+ * @return grouted image, or null if no image could be created
+ */
+ public static GroutedImage createMapImage(Track inTrack, MapSource inMapSource, int inZoom)
+ {
+ // Get the extents of the track including a standard (10%) border around the data
+ TrackExtents extents = new TrackExtents(inTrack);
+ extents.applySquareBorder();
+ DoubleRange xRange = extents.getXRange();
+ DoubleRange yRange = extents.getYRange();
+
+ // Get path to disk cache
+ final String cachePath = Config.getConfigString(Config.KEY_DISK_CACHE);
+ // Work out which tiles are required
+ final int zoomFactor = 1 << inZoom;
+ final int minTileX = (int) (xRange.getMinimum() * zoomFactor);
+ final int maxTileX = (int) (xRange.getMaximum() * zoomFactor);
+ final int minTileY = (int) (yRange.getMinimum() * zoomFactor);
+ final int maxTileY = (int) (yRange.getMaximum() * zoomFactor);
+
+ // Work out how big the final image will be, create a BufferedImage
+ final int pixCount = (int) (extents.getXRange().getRange() * zoomFactor * 256);
+ if (pixCount < 2) {return null;}
+ BufferedImage resultImage = new BufferedImage(pixCount, pixCount, BufferedImage.TYPE_INT_RGB);
+ Graphics g = resultImage.getGraphics();
+ g.setColor(Color.WHITE);
+ g.fillRect(0, 0, pixCount, pixCount);
+ // Work out where to start drawing the tiles on the image
+ int xOffset = (int) ((minTileX - xRange.getMinimum() * zoomFactor) * 256);
+
+ int numTilesUsed = 0;
+ int numTilesMissing = 0;
+ // Loop over the tiles
+ for (int x = minTileX; x <= maxTileX; x++)
+ {
+ int yOffset = (int) ((minTileY - yRange.getMinimum() * zoomFactor) * 256);
+ for (int y = minTileY; y <= maxTileY; y++)
+ {
+ for (int layer=0; layer < inMapSource.getNumLayers(); layer++)
+ {
+ Image tile = DiskTileCacher.getTile(cachePath, inMapSource.makeFilePath(layer, inZoom, x, y), false);
+ if (tile != null)
+ {
+ // Wait until tile is available (loaded asynchronously)
+ while (tile.getWidth(null) < 0) {
+ try {
+ Thread.sleep(100);
+ }
+ catch (InterruptedException ie) {}
+ }
+ // work out where to copy it to, paint it
+ // System.out.println("Painting tile " + x + "," + y + " at " + xOffset + "," + yOffset);
+ numTilesUsed++;
+ g.drawImage(tile, xOffset, yOffset, null);
+ }
+ else numTilesMissing++;
+ }
+ yOffset += 256;
+ }
+ xOffset += 256;
+ }
+ // Get rid of the image if it's empty
+ if (numTilesUsed == 0) {
+ resultImage = null;
+ }
+ // Store the xy limits in the GroutedImage to make it easier to draw on top
+ GroutedImage result = new GroutedImage(resultImage, numTilesUsed, numTilesMissing);
+ result.setXRange(xRange);
+ result.setYRange(yRange);
+ return result;
+ }
+
+ /**
+ * Get the grouted map image, using the previously-created one if available
+ * @param inTrack track object
+ * @param inMapSource map source to use (may have one or two layers)
+ * @param inZoom selected zoom level
+ * @return grouted image, or null if no image could be created
+ */
+ public static GroutedImage getMapImage(Track inTrack, MapSource inMapSource, int inZoom)
+ {
+ if (_lastGroutedImage == null) {
+ _lastGroutedImage = createMapImage(inTrack, inMapSource, inZoom);
+ }
+ return _lastGroutedImage;
+ }
+
+ /**
+ * @param inTrack track object
+ * @param inZoom selected zoom level
+ * @return true if the image size is acceptable
+ */
+ public static boolean isZoomLevelOk(Track inTrack, int inZoom)
+ {
+ // Get the extents of the track including a standard (10%) border around the data
+ TrackExtents extents = new TrackExtents(inTrack);
+ extents.applySquareBorder();
+
+ // Work out how big the final image will be
+ final int zoomFactor = 1 << inZoom;
+ final int pixCount = (int) (extents.getXRange().getRange() * zoomFactor * 256);
+ return pixCount > 2 && pixCount < 4000;
+ }
+}
diff --git a/tim/prune/save/PovExporter.java b/tim/prune/save/PovExporter.java
index ee30841..ba66058 100644
--- a/tim/prune/save/PovExporter.java
+++ b/tim/prune/save/PovExporter.java
@@ -12,7 +12,9 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
+import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
+import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
@@ -24,8 +26,10 @@ import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
+import javax.swing.border.EtchedBorder;
import tim.prune.App;
+import tim.prune.DataSubscriber;
import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.config.Config;
@@ -33,14 +37,16 @@ import tim.prune.data.NumberUtils;
import tim.prune.data.Track;
import tim.prune.function.Export3dFunction;
import tim.prune.gui.DialogCloser;
+import tim.prune.gui.map.MapSource;
+import tim.prune.gui.map.MapSourceLibrary;
import tim.prune.load.GenericFileFilter;
-import tim.prune.threedee.LineDialog;
import tim.prune.threedee.ThreeDModel;
/**
* Class to export a 3d scene of the track to a specified Pov file
+ * Note: Subscriber interface only used for callback from image config dialog
*/
-public class PovExporter extends Export3dFunction
+public class PovExporter extends Export3dFunction implements DataSubscriber
{
private Track _track = null;
private JDialog _dialog = null;
@@ -49,9 +55,13 @@ public class PovExporter extends Export3dFunction
private JTextField _cameraXField = null, _cameraYField = null, _cameraZField = null;
private JTextField _fontName = null, _altitudeFactorField = null;
private JRadioButton _ballsAndSticksButton = null;
+ private JLabel _baseImageLabel = null;
+ private JButton _baseImageButton = null;
+ private BaseImageConfigDialog _baseImageConfig = null;
// defaults
private static final double DEFAULT_CAMERA_DISTANCE = 30.0;
+ private static final double MODEL_SCALE_FACTOR = 20.0;
private static final String DEFAULT_FONT_FILE = "crystal.ttf";
@@ -84,10 +94,10 @@ public class PovExporter extends Export3dFunction
double cameraDist = Math.sqrt(inX*inX + inY*inY + inZ*inZ);
if (cameraDist > 0.0)
{
- _cameraX = NumberUtils.formatNumber(inX / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
- _cameraY = NumberUtils.formatNumber(inY / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
+ _cameraX = NumberUtils.formatNumberUk(inX / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
+ _cameraY = NumberUtils.formatNumberUk(inY / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
// Careful! Need to convert from java3d (right-handed) to povray (left-handed) coordinate system!
- _cameraZ = NumberUtils.formatNumber(-inZ / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
+ _cameraZ = NumberUtils.formatNumberUk(-inZ / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
}
}
@@ -104,12 +114,18 @@ public class PovExporter extends Export3dFunction
_dialog.setLocationRelativeTo(_parentFrame);
_dialog.getContentPane().add(makeDialogComponents());
}
+ // Make base image dialog
+ if (_baseImageConfig == null)
+ {
+ _baseImageConfig = new BaseImageConfigDialog(this, _dialog, _track);
+ }
// Set angles
_cameraXField.setText(_cameraX);
_cameraYField.setText(_cameraY);
_cameraZField.setText(_cameraZ);
_altitudeFactorField.setText("" + _altFactor);
+ updateBaseImageDetails();
// Show dialog
_dialog.pack();
_dialog.setVisible(true);
@@ -123,7 +139,7 @@ public class PovExporter extends Export3dFunction
private Component makeDialogComponents()
{
JPanel panel = new JPanel();
- panel.setLayout(new BorderLayout());
+ panel.setLayout(new BorderLayout(4, 4));
JLabel introLabel = new JLabel(I18nManager.getText("dialog.exportpov.text"));
introLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4));
panel.add(introLabel, BorderLayout.NORTH);
@@ -135,6 +151,7 @@ public class PovExporter extends Export3dFunction
public void actionPerformed(ActionEvent e)
{
doExport();
+ MapGrouter.clearMapImage();
_dialog.dispose();
}
});
@@ -143,6 +160,7 @@ public class PovExporter extends Export3dFunction
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
+ MapGrouter.clearMapImage();
_dialog.dispose();
}
});
@@ -205,37 +223,78 @@ public class PovExporter extends Export3dFunction
group.add(_ballsAndSticksButton); group.add(tubesButton);
stylePanel.add(radioPanel);
- // add this grid to the holder panel
+ // Panel for the base image
+ JPanel imagePanel = new JPanel();
+ imagePanel.setLayout(new BorderLayout(10, 4));
+ imagePanel.add(new JLabel(I18nManager.getText("dialog.exportpov.baseimage") + ": "), BorderLayout.WEST);
+ _baseImageLabel = new JLabel("Typical sourcename");
+ imagePanel.add(_baseImageLabel, BorderLayout.CENTER);
+ _baseImageButton = new JButton(I18nManager.getText("button.edit"));
+ _baseImageButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ changeBaseImage();
+ }
+ });
+ imagePanel.add(_baseImageButton, BorderLayout.EAST);
+ // Put these image controls inside a holder panel with an outline
+ JPanel imageHolderPanel = new JPanel();
+ imageHolderPanel.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4))
+ );
+ imageHolderPanel.setLayout(new BorderLayout());
+ imageHolderPanel.add(imagePanel, BorderLayout.NORTH);
+
+ // add these panels to the holder panel
JPanel holderPanel = new JPanel();
holderPanel.setLayout(new BorderLayout(5, 5));
JPanel boxPanel = new JPanel();
boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
boxPanel.add(centralPanel);
+ boxPanel.add(Box.createVerticalStrut(4));
boxPanel.add(stylePanel);
+ boxPanel.add(Box.createVerticalStrut(4));
+ boxPanel.add(imageHolderPanel);
holderPanel.add(boxPanel, BorderLayout.CENTER);
- // show lines button
- JButton showLinesButton = new JButton(I18nManager.getText("button.showlines"));
- showLinesButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- // Need to scale model to find lines
- ThreeDModel model = new ThreeDModel(_track);
- model.scale();
- double[] latLines = model.getLatitudeLines();
- double[] lonLines = model.getLongitudeLines();
- LineDialog dialog = new LineDialog(_parentFrame, latLines, lonLines);
- dialog.showDialog();
- }
- });
- JPanel flowPanel = new JPanel();
- flowPanel.setLayout(new FlowLayout());
- flowPanel.add(showLinesButton);
- holderPanel.add(flowPanel, BorderLayout.EAST);
panel.add(holderPanel, BorderLayout.CENTER);
return panel;
}
+ /**
+ * Change the base image by calling the BaseImageConfigDialog
+ */
+ private void changeBaseImage()
+ {
+ // Check if there is a cache to use
+ if (BaseImageConfigDialog.isImagePossible())
+ {
+ // Show new dialog to choose image details
+ _baseImageConfig.begin();
+ }
+ else {
+ _app.showErrorMessage(getNameKey(), "dialog.exportimage.noimagepossible");
+ }
+ }
+
+ /**
+ * Update the description label according to the selected base image details
+ */
+ private void updateBaseImageDetails()
+ {
+ String desc = null;
+ if (_baseImageConfig.useImage())
+ {
+ MapSource source = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex());
+ if (source != null) {
+ desc = source.getName() + " ("
+ + _baseImageConfig.getZoomLevel() + ")";
+ }
+ }
+ if (desc == null) {
+ desc = I18nManager.getText("dialog.about.no");
+ }
+ _baseImageLabel.setText(desc);
+ }
/**
* Select the file and export data to it
@@ -267,25 +326,27 @@ public class PovExporter extends Export3dFunction
if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
{
// OK pressed and file chosen
- File file = _fileChooser.getSelectedFile();
- if (!file.getName().toLowerCase().endsWith(".pov"))
+ File povFile = _fileChooser.getSelectedFile();
+ if (!povFile.getName().toLowerCase().endsWith(".pov"))
{
- file = new File(file.getAbsolutePath() + ".pov");
+ povFile = new File(povFile.getAbsolutePath() + ".pov");
}
- // Check if file exists and if necessary prompt for overwrite
+ final int nameLen = povFile.getName().length() - 4;
+ final File imageFile = new File(povFile.getParentFile(), povFile.getName().substring(0, nameLen) + "_base.png");
+ final boolean imageExists = _baseImageConfig.useImage() && imageFile.exists();
+ // Check if files exist and if necessary prompt for overwrite
Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
- if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame,
+ if ((!povFile.exists() && !imageExists) || JOptionPane.showOptionDialog(_parentFrame,
I18nManager.getText("dialog.save.overwrite.text"),
I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
== JOptionPane.YES_OPTION)
{
// Export the file
- if (exportFile(file))
+ if (exportFile(povFile, imageFile))
{
- // file saved
- // Store directory in config for later
- Config.setConfigString(Config.KEY_TRACK_DIR, file.getParentFile().getAbsolutePath());
+ // file saved - store directory in config for later
+ Config.setConfigString(Config.KEY_TRACK_DIR, povFile.getParentFile().getAbsolutePath());
}
else
{
@@ -305,10 +366,11 @@ public class PovExporter extends Export3dFunction
/**
* Export the track data to the specified file
- * @param inFile File object to save to
+ * @param inPovFile File object to save pov file to
+ * @param inImageFile file object to save image to
* @return true if successful
*/
- private boolean exportFile(File inFile)
+ private boolean exportFile(File inPovFile, File inImageFile)
{
FileWriter writer = null;
// find out the line separator for this system
@@ -317,6 +379,7 @@ public class PovExporter extends Export3dFunction
{
// create and scale model
ThreeDModel model = new ThreeDModel(_track);
+ model.setModelSize(MODEL_SCALE_FACTOR);
try
{
// try to use given altitude cap
@@ -328,12 +391,28 @@ public class PovExporter extends Export3dFunction
}
model.scale();
- // Create file and write basics
- writer = new FileWriter(inFile);
- writeStartOfFile(writer, model.getModelSize(), lineSeparator);
+ boolean useImage = _baseImageConfig.useImage();
+ if (useImage)
+ {
+ // Get base image from grouter
+ MapSource mapSource = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex());
+ GroutedImage baseImage = MapGrouter.getMapImage(_track, mapSource, _baseImageConfig.getZoomLevel());
+ try
+ {
+ useImage = ImageIO.write(baseImage.getImage(), "png", inImageFile);
+ }
+ catch (IOException ioe) {
+ System.err.println("Can't write image: " + ioe.getClass().getName());
+ useImage = false;
+ }
+ if (!useImage) {
+ _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage");
+ }
+ }
- // write out lat/long lines using model
- writeLatLongLines(writer, model, lineSeparator);
+ // Create file and write basics
+ writer = new FileWriter(inPovFile);
+ writeStartOfFile(writer, lineSeparator, useImage ? inImageFile : null);
// write out points
if (_ballsAndSticksButton.isSelected()) {
@@ -346,7 +425,7 @@ public class PovExporter extends Export3dFunction
// everything worked
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
+ " " + _track.getNumPoints() + " " + I18nManager.getText("confirm.save.ok2")
- + " " + inFile.getAbsolutePath());
+ + " " + inPovFile.getAbsolutePath());
return true;
}
catch (IOException ioe)
@@ -370,11 +449,11 @@ public class PovExporter extends Export3dFunction
/**
* Write the start of the Pov file, including base plane and lights
* @param inWriter Writer to use for writing file
- * @param inModelSize model size
* @param inLineSeparator line separator to use
+ * @param inImageFile image file to reference (or null if none)
* @throws IOException on file writing error
*/
- private void writeStartOfFile(FileWriter inWriter, double inModelSize, String inLineSeparator)
+ private void writeStartOfFile(FileWriter inWriter, String inLineSeparator, File inImageFile)
throws IOException
{
inWriter.write("// Pov file produced by GpsPrune - see http://activityworkshop.net/");
@@ -389,6 +468,20 @@ public class PovExporter extends Export3dFunction
else {
Config.setConfigString(Config.KEY_POVRAY_FONT, fontPath);
}
+
+ // Make the definition of the base plane depending on whether there's an image or not
+ final boolean useImage = (inImageFile != null);
+ final String boxDefinition = (inImageFile == null ?
+ " <-10.0, -0.15, -10.0>," + inLineSeparator
+ + " <10.0, 0.15, 10.0>" + inLineSeparator
+ + " pigment { color rgb <0.5 0.75 0.8> }"
+ :
+ " <0, 0, 0>, <1, 1, 0.001>" + inLineSeparator
+ + " pigment {image_map { png \"" + inImageFile.getName() + "\" map_type 0 interpolate 2 once } }" + inLineSeparator
+ + " scale 20.0 rotate <90, 0, 0>" + inLineSeparator
+ + " translate <-10.0, 0, -10.0>");
+ // TODO: Maybe could use the same geometry for the imageless case, would simplify code a bit
+
// Set up output
String[] outputLines = {
"global_settings { ambient_light rgb <4, 4, 4> }", "",
@@ -401,27 +494,15 @@ public class PovExporter extends Export3dFunction
"}", "",
// global declares
"// Global declares",
- "#declare lat_line =",
- " cylinder {",
- " <-" + inModelSize + ", 0.1, 0>,",
- " <" + inModelSize + ", 0.1, 0>,",
- " 0.1 // Radius",
- " pigment { color rgb <0.5 0.5 0.5> }",
- " }",
- "#declare lon_line =",
- " cylinder {",
- " <0, 0.1, -" + inModelSize + ">,",
- " <0, 0.1, " + inModelSize + ">,",
- " 0.1 // Radius",
- " pigment { color rgb <0.5 0.5 0.5> }",
- " }",
"#declare point_rod =",
" cylinder {",
" <0, 0, 0>,",
" <0, 1, 0>,",
" 0.15",
" open",
- " pigment { color rgb <0.5 0.5 0.5> }",
+ " texture {",
+ " pigment { color rgb <0.5 0.5 0.5> }",
+ useImage ? " } no_shadow" : " }",
" }", "",
// MAYBE: Export rods to POV? How to store in data?
"#declare waypoint_sphere =",
@@ -430,7 +511,7 @@ public class PovExporter extends Export3dFunction
" texture {",
" pigment {color rgb <0.1 0.1 1.0>}",
" finish { phong 1 }",
- " }",
+ useImage ? " } no_shadow" : " }",
" }",
"#declare track_sphere0 =",
" sphere {",
@@ -491,31 +572,29 @@ public class PovExporter extends Export3dFunction
"#declare wall_colour = rgbt <0.5, 0.5, 0.5, 0.3>;", "",
"// Base plane",
"box {",
- " <-" + inModelSize + ", -0.15, -" + inModelSize + ">, // Near lower left corner",
- " <" + inModelSize + ", 0.15, " + inModelSize + "> // Far upper right corner",
- " pigment { color rgb <0.5 0.75 0.8> }",
+ boxDefinition,
"}", "",
// write cardinals
"// Cardinal letters N,S,E,W",
"text {",
" ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.n") + "\" 0.3, 0",
" pigment { color rgb <1 1 1> }",
- " translate <0, 0.2, " + inModelSize + ">",
+ " translate <0, 0.2, 10.0>",
"}",
"text {",
" ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.s") + "\" 0.3, 0",
" pigment { color rgb <1 1 1> }",
- " translate <0, 0.2, -" + inModelSize + ">",
+ " translate <0, 0.2, -10.0>",
"}",
"text {",
" ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.e") + "\" 0.3, 0",
" pigment { color rgb <1 1 1> }",
- " translate <" + (inModelSize * 0.97) + ", 0.2, 0>",
+ " translate <9.7, 0.2, 0>",
"}",
"text {",
" ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.w") + "\" 0.3, 0",
" pigment { color rgb <1 1 1> }",
- " translate <-" + (inModelSize * 1.03) + ", 0.2, 0>",
+ " translate <-10.3, 0.2, 0>",
"}", "",
// MAYBE: Light positions should relate to model size
"// lights",
@@ -534,36 +613,6 @@ public class PovExporter extends Export3dFunction
}
- /**
- * Write out all the lat and long lines to the file
- * @param inWriter Writer to use for writing file
- * @param inModel model object for getting lat/long lines
- * @param inLineSeparator line separator to use
- * @throws IOException on file writing error
- */
- private static void writeLatLongLines(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator)
- throws IOException
- {
- inWriter.write("// Latitude and longitude lines:");
- inWriter.write(inLineSeparator);
- int numlines = inModel.getLatitudeLines().length;
- for (int i=0; i }");
- inWriter.write(inLineSeparator);
- }
- numlines = inModel.getLongitudeLines().length;
- for (int i=0; i }");
- inWriter.write(inLineSeparator);
- }
- inWriter.write(inLineSeparator);
- }
-
-
/**
* Write out all the data points to the file in the balls-and-sticks style
* @param inWriter Writer to use for writing file
@@ -829,4 +878,16 @@ public class PovExporter extends Export3dFunction
}
return segmentList;
}
+
+ /**
+ * Callback from base image config dialog
+ */
+ public void dataUpdated(byte inUpdateType)
+ {
+ updateBaseImageDetails();
+ }
+
+ /** Not required */
+ public void actionCompleted(String inMessage) {
+ }
}
diff --git a/tim/prune/save/SvgExporter.java b/tim/prune/save/SvgExporter.java
index 4a86c5b..33f42f9 100644
--- a/tim/prune/save/SvgExporter.java
+++ b/tim/prune/save/SvgExporter.java
@@ -47,7 +47,7 @@ public class SvgExporter extends Export3dFunction
private JTextField _phiField = null, _thetaField = null;
private JTextField _altitudeFactorField = null;
private JCheckBox _gradientsCheckbox = null;
- private static double _scaleFactor = 1.0;
+ private static final double _scaleFactor = 200.0;
/**
@@ -265,16 +265,15 @@ public class SvgExporter extends Export3dFunction
}
catch (NumberFormatException nfe) {}
model.scale();
- _scaleFactor = 200 / model.getModelSize();
boolean useGradients = _gradientsCheckbox.isSelected();
// Create file and write basics
writer = new FileWriter(inFile);
writeStartOfFile(writer, useGradients, lineSeparator);
- writeBasePlane(writer, model.getModelSize(), lineSeparator);
+ writeBasePlane(writer, lineSeparator);
// write out cardinal letters NESW
- writeCardinals(writer, model.getModelSize(), lineSeparator);
+ writeCardinals(writer, lineSeparator);
// write out points
writeDataPoints(writer, model, useGradients, lineSeparator);
@@ -342,18 +341,17 @@ public class SvgExporter extends Export3dFunction
/**
* Write the base plane
* @param inWriter Writer to use for writing file
- * @param inModelSize model size
* @param inLineSeparator line separator to use
* @throws IOException on file writing error
*/
- private void writeBasePlane(FileWriter inWriter, double inModelSize, String inLineSeparator)
+ private void writeBasePlane(FileWriter inWriter, String inLineSeparator)
throws IOException
{
// Use model size and camera angles to draw path for base rectangle (using 3d transform)
- int[] coords1 = convertCoordinates(-inModelSize, -inModelSize, 0);
- int[] coords2 = convertCoordinates(inModelSize, -inModelSize, 0);
- int[] coords3 = convertCoordinates(inModelSize, inModelSize, 0);
- int[] coords4 = convertCoordinates(-inModelSize, inModelSize, 0);
+ int[] coords1 = convertCoordinates(-1.0, -1.0, 0);
+ int[] coords2 = convertCoordinates(1.0, -1.0, 0);
+ int[] coords3 = convertCoordinates(1.0, 1.0, 0);
+ int[] coords4 = convertCoordinates(-1.0, 1.0, 0);
final String corners = "M " + coords1[0] + "," + coords1[1]
+ " L " + coords2[0] + "," + coords2[1]
+ " L " + coords3[0] + "," + coords3[1]
@@ -365,21 +363,20 @@ public class SvgExporter extends Export3dFunction
/**
* Write the cardinal letters NESW
* @param inWriter Writer to use for writing file
- * @param inModelSize model size
* @param inLineSeparator line separator to use
* @throws IOException on file writing error
*/
- private void writeCardinals(FileWriter inWriter, double inModelSize, String inLineSeparator)
+ private void writeCardinals(FileWriter inWriter, String inLineSeparator)
throws IOException
{
// Use model size and camera angles to calculate positions
- int[] coordsN = convertCoordinates(0, inModelSize, 0);
+ int[] coordsN = convertCoordinates(0, 1.0, 0);
writeCardinal(inWriter, coordsN[0], coordsN[1], "cardinal.n", inLineSeparator);
- int[] coordsE = convertCoordinates(inModelSize, 0, 0);
+ int[] coordsE = convertCoordinates(1.0, 0, 0);
writeCardinal(inWriter, coordsE[0], coordsE[1], "cardinal.e", inLineSeparator);
- int[] coordsS = convertCoordinates(0, -inModelSize, 0);
+ int[] coordsS = convertCoordinates(0, -1.0, 0);
writeCardinal(inWriter, coordsS[0], coordsS[1], "cardinal.s", inLineSeparator);
- int[] coordsW = convertCoordinates(-inModelSize, 0, 0);
+ int[] coordsW = convertCoordinates(-1.0, 0, 0);
writeCardinal(inWriter, coordsW[0], coordsW[1], "cardinal.w", inLineSeparator);
}
diff --git a/tim/prune/threedee/Java3DWindow.java b/tim/prune/threedee/Java3DWindow.java
index 1671607..cfac9af 100644
--- a/tim/prune/threedee/Java3DWindow.java
+++ b/tim/prune/threedee/Java3DWindow.java
@@ -58,7 +58,7 @@ public class Java3DWindow implements ThreeDWindow
private JFrame _frame = null;
private ThreeDModel _model = null;
private OrbitBehavior _orbit = null;
- private double _altFactor = 50.0;
+ private double _altFactor = 5.0;
/** only prompt about big track size once */
private static boolean TRACK_SIZE_WARNING_GIVEN = false;
@@ -68,6 +68,7 @@ public class Java3DWindow implements ThreeDWindow
private static final double INITIAL_X_ROTATION = 15.0;
private static final String CARDINALS_FONT = "Arial";
private static final int MAX_TRACK_SIZE = 2500; // threshold for warning
+ private static final double MODEL_SCALE_FACTOR = 20.0;
/**
@@ -127,9 +128,8 @@ public class Java3DWindow implements ThreeDWindow
Object[] buttonTexts = {I18nManager.getText("button.continue"), I18nManager.getText("button.cancel")};
if (_track.getNumPoints() > MAX_TRACK_SIZE && !TRACK_SIZE_WARNING_GIVEN)
{
- // FIXME: Change text reference from exportpov to java3d
if (JOptionPane.showOptionDialog(_parentFrame,
- I18nManager.getText("dialog.exportpov.warningtracksize"),
+ I18nManager.getText("dialog.3d.warningtracksize"),
I18nManager.getText("function.show3d"), JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
== JOptionPane.OK_OPTION)
@@ -191,18 +191,7 @@ public class Java3DWindow implements ThreeDWindow
}
}});
panel.add(svgButton);
- // Display coordinates of lat/long lines of 3d graph in separate dialog
- JButton showLinesButton = new JButton(I18nManager.getText("button.showlines"));
- showLinesButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e)
- {
- double[] latLines = _model.getLatitudeLines();
- double[] lonLines = _model.getLongitudeLines();
- LineDialog dialog = new LineDialog(_frame, latLines, lonLines);
- dialog.showDialog();
- }
- });
- panel.add(showLinesButton);
+
// Close button
JButton closeButton = new JButton(I18nManager.getText("button.close"));
closeButton.addActionListener(new ActionListener()
@@ -305,9 +294,6 @@ public class Java3DWindow implements ThreeDWindow
_model.setAltitudeFactor(_altFactor);
_model.scale();
- // Lat/Long lines
- objTrans.addChild(createLatLongs(_model));
-
// Add points to model
objTrans.addChild(createDataPoints(_model));
@@ -360,70 +346,6 @@ public class Java3DWindow implements ThreeDWindow
}
- /**
- * Create all the latitude and longitude lines on the base plane
- * @param inModel model containing data
- * @return Group object containing cylinders for lat and long lines
- */
- private static Group createLatLongs(ThreeDModel inModel)
- {
- Group group = new Group();
- int numlines = inModel.getLatitudeLines().length;
- for (int i=0; i").append(I18nManager.getText("dialog.3dlines.empty")).append("");
- }
- else
- {
- descBuffer.append("