[gpsprune] 01/07: Imported Upstream version 16.2

David Paleino dapal at debian.org
Thu Jul 3 08:57:09 UTC 2014


This is an automated email from the git hooks/post-receive script.

dapal pushed a commit to annotated tag debian/16.2-1
in repository gpsprune.

commit 279ef3c52e285bd729d3a9827e1890e67713cdd6
Author: David Paleino <dapal at debian.org>
Date:   Wed Jul 2 21:44:13 2014 +0200

    Imported Upstream version 16.2
---
 build.sh                                           |   2 +-
 tim/prune/App.java                                 |  31 +-
 tim/prune/DataStatus.java                          |  34 ++
 tim/prune/ExternalTools.java                       |  11 +
 tim/prune/FunctionLibrary.java                     |  15 +
 tim/prune/GpsPrune.java                            |   6 +-
 tim/prune/I18nManager.java                         |  18 +
 tim/prune/UpdateMessageBroker.java                 |  14 +
 tim/prune/config/Config.java                       |  29 ++
 tim/prune/copyright.txt                            |   2 +-
 tim/prune/correlate/Correlator.java                |  17 +-
 tim/prune/data/DataPoint.java                      |  42 +-
 tim/prune/data/DoubleRange.java                    |  12 +
 tim/prune/data/PointScaler.java                    |  83 +++-
 tim/prune/data/RangeStats.java                     |  23 +-
 tim/prune/data/Selection.java                      |  46 +-
 tim/prune/data/Track.java                          |  16 +-
 tim/prune/function/AboutScreen.java                |  19 +-
 tim/prune/function/AddMapSourceDialog.java         | 159 +------
 tim/prune/function/DeleteFieldValues.java          |   4 +-
 tim/prune/function/Export3dFunction.java           |  22 +
 tim/prune/function/FieldListModel.java             |   4 +-
 tim/prune/function/FindWaypoint.java               |   4 +-
 tim/prune/function/FullRangeDetails.java           |   3 +-
 tim/prune/function/GetWikipediaXmlHandler.java     |   4 +-
 tim/prune/function/MapSourceListModel.java         |   4 +-
 tim/prune/function/PasteCoordinates.java           |   4 +-
 tim/prune/function/RearrangeWaypointsFunction.java |   1 +
 tim/prune/function/SelectTracksFunction.java       |   4 +-
 tim/prune/function/SetLanguage.java                |   8 +-
 tim/prune/function/SetMapBgFunction.java           |   4 +-
 tim/prune/function/ShowThreeDFunction.java         | 127 +++++-
 tim/prune/function/browser/BrowserLauncher.java    |  11 +-
 tim/prune/function/cache/ManageCacheFunction.java  |   3 +-
 .../function/compress/CompressTrackFunction.java   |  28 +-
 .../function/compress/DouglasPeuckerAlgorithm.java |  44 +-
 .../compress/MarkPointsInRectangleFunction.java    |  35 +-
 tim/prune/function/estimate/EstimateTime.java      |  16 +-
 .../function/estimate/EstimationParameters.java    |  13 +
 tim/prune/function/estimate/LearnParameters.java   |   2 +-
 tim/prune/function/sew/CandidateSorter.java        |  29 ++
 tim/prune/function/sew/SegmentEnd.java             | 197 +++++++++
 .../function/sew/SewTrackSegmentsFunction.java     | 331 ++++++++++++++
 tim/prune/function/sew/SplitPoint.java             | 101 +++++
 tim/prune/function/sew/SplitSegmentsFunction.java  | 285 ++++++++++++
 tim/prune/function/srtm/DownloadSrtmFunction.java  | 222 ++++++++++
 tim/prune/function/srtm/LookupSrtmFunction.java    | 207 ++++++---
 tim/prune/function/srtm/SrtmTile.java              |  13 +-
 .../weather/GetWeatherForecastFunction.java        | 480 +++++++++++++++++++++
 tim/prune/function/weather/IconRenderer.java       |  37 ++
 tim/prune/function/weather/OWMCurrentHandler.java  |  92 ++++
 tim/prune/function/weather/OWMForecastHandler.java | 102 +++++
 tim/prune/function/weather/ResultSet.java          |  83 ++++
 tim/prune/function/weather/SingleForecast.java     | 185 ++++++++
 tim/prune/function/weather/WeatherResults.java     | 166 +++++++
 tim/prune/function/weather/WeatherTableModel.java  | 124 ++++++
 tim/prune/gui/BaseImageDefinitionPanel.java        | 157 +++++++
 tim/prune/gui/DecimalNumberField.java              |   3 +-
 tim/prune/gui/DetailsDisplay.java                  |  17 +-
 tim/prune/gui/DisplayUtils.java                    |  20 +
 .../GenericProgressDialog.java}                    |  32 +-
 tim/prune/gui/MediaListModel.java                  |   4 +-
 tim/prune/gui/MenuManager.java                     |  57 ++-
 tim/prune/gui/ProgressDialog.java                  |   8 +-
 tim/prune/gui/SelectorDisplay.java                 |  14 +-
 tim/prune/gui/TerrainDefinitionPanel.java          |  85 ++++
 tim/prune/gui/UndoManager.java                     |   4 +-
 tim/prune/gui/WaypointListModel.java               |   4 +-
 tim/prune/gui/WaypointNameMatcher.java             |   4 +-
 tim/prune/gui/images/weather-clear-day.png         | Bin 0 -> 3088 bytes
 tim/prune/gui/images/weather-clear-night.png       | Bin 0 -> 3063 bytes
 tim/prune/gui/images/weather-clouds-day.png        | Bin 0 -> 3766 bytes
 tim/prune/gui/images/weather-clouds-night.png      | Bin 0 -> 3824 bytes
 tim/prune/gui/images/weather-clouds.png            | Bin 0 -> 2618 bytes
 tim/prune/gui/images/weather-extreme.png           | Bin 0 -> 1533 bytes
 tim/prune/gui/images/weather-fog.png               | Bin 0 -> 1521 bytes
 tim/prune/gui/images/weather-hail.png              | Bin 0 -> 1708 bytes
 tim/prune/gui/images/weather-lightrain.png         | Bin 0 -> 1731 bytes
 tim/prune/gui/images/weather-rain.png              | Bin 0 -> 2184 bytes
 tim/prune/gui/images/weather-snow.png              | Bin 0 -> 2106 bytes
 tim/prune/gui/images/weather-storm.png             | Bin 0 -> 2984 bytes
 tim/prune/gui/map/CloudmadeMapSource.java          |  61 ---
 tim/prune/gui/map/DiskTileCacher.java              |  13 +-
 tim/prune/gui/map/MapCanvas.java                   |  55 ++-
 tim/prune/gui/map/MapSourceLibrary.java            |   2 -
 tim/prune/gui/map/MapTileManager.java              | 110 +++--
 tim/prune/gui/map/TileConsumer.java                |  10 +
 tim/prune/gui/map/TileDownloader.java              |   3 +
 tim/prune/jpeg/ExifGateway.java                    |  21 +-
 tim/prune/jpeg/ExternalExifLibrary.java            |  64 +--
 tim/prune/lang/prune-texts_af.properties           |  15 +-
 tim/prune/lang/prune-texts_cz.properties           |  78 +++-
 tim/prune/lang/prune-texts_da.properties           |  84 ++++
 tim/prune/lang/prune-texts_de.properties           |  77 +++-
 tim/prune/lang/prune-texts_de_CH.properties        |  97 +++--
 tim/prune/lang/prune-texts_en.properties           |  77 +++-
 tim/prune/lang/prune-texts_en_US.properties        |   1 +
 tim/prune/lang/prune-texts_es.properties           |  85 +++-
 tim/prune/lang/prune-texts_fa.properties           |  73 ++++
 tim/prune/lang/prune-texts_fr.properties           |  90 +++-
 tim/prune/lang/prune-texts_hu.properties           | 210 +++++++--
 tim/prune/lang/prune-texts_in.properties           | 111 +++++
 tim/prune/lang/prune-texts_it.properties           |  77 +++-
 tim/prune/lang/prune-texts_ja.properties           |  26 +-
 tim/prune/lang/prune-texts_ko.properties           |  25 +-
 tim/prune/lang/prune-texts_nl.properties           |  77 +++-
 tim/prune/lang/prune-texts_pl.properties           |  65 ++-
 tim/prune/lang/prune-texts_pt.properties           | 217 ++++++++--
 tim/prune/lang/prune-texts_ro.properties           |  97 ++++-
 tim/prune/lang/prune-texts_ru.properties           |  55 ++-
 tim/prune/lang/prune-texts_sv.properties           |  58 +++
 tim/prune/lang/prune-texts_tr.properties           |  16 +-
 tim/prune/lang/prune-texts_uk.properties           | 234 ++++++++++
 tim/prune/lang/prune-texts_zh.properties           |  90 +++-
 tim/prune/load/BabelFileFormats.java               |   4 +-
 tim/prune/load/BabelLoadFromFile.java              |  39 +-
 tim/prune/load/FileCacher.java                     |   4 +-
 tim/prune/load/MediaLoadProgressDialog.java        |  93 +---
 tim/prune/load/TextFileLoader.java                 |  18 +-
 tim/prune/load/babel/DiscardFilter.java            |   4 +-
 tim/prune/load/babel/DistanceFilter.java           |   4 +-
 tim/prune/load/babel/InterpolateFilter.java        |   4 +-
 tim/prune/load/babel/SimplifyFilter.java           |   4 +-
 tim/prune/load/xml/GzipFileLoader.java             |   6 +-
 tim/prune/load/xml/XmlFileLoader.java              |  60 ++-
 tim/prune/load/xml/ZipFileLoader.java              |   4 +-
 tim/prune/readme.txt                               |  41 +-
 tim/prune/save/BaseImageConfigDialog.java          | 197 ++++++---
 tim/prune/save/BaseImageConsumer.java              |  10 +
 tim/prune/save/ExifSaver.java                      |   9 +-
 tim/prune/save/GpxExporter.java                    |  73 +---
 tim/prune/save/GroutedImage.java                   |   7 -
 tim/prune/save/ImageExporter.java                  | 139 +++---
 tim/prune/save/KmlExporter.java                    |   8 +-
 tim/prune/save/MapGrouter.java                     |  69 ++-
 tim/prune/save/PovExporter.java                    | 255 ++++++-----
 tim/prune/save/SvgExporter.java                    |   9 +-
 tim/prune/save/xml/ByteBuffer.java                 |  88 ++++
 tim/prune/save/xml/GpxSlicer.java                  |  68 ++-
 tim/prune/save/xml/XmlUtils.java                   |  72 ++++
 tim/prune/threedee/ImageDefinition.java            |  66 +++
 tim/prune/threedee/Java3DWindow.java               | 208 +++++++--
 tim/prune/threedee/TerrainCache.java               |  57 +++
 tim/prune/threedee/TerrainDefinition.java          |  68 +++
 tim/prune/threedee/TerrainHelper.java              | 477 ++++++++++++++++++++
 tim/prune/threedee/ThreeDModel.java                |  53 ++-
 tim/prune/threedee/ThreeDWindow.java               |  20 +
 tim/prune/threedee/WindowFactory.java              |   6 +-
 tim/prune/tips/TipDefinition.java                  |  66 +++
 tim/prune/tips/TipManager.java                     |  45 ++
 tim/prune/undo/UndoAddAltitudeOffset.java          |   3 +-
 tim/prune/undo/UndoCutAndMove.java                 |   1 +
 tim/prune/undo/UndoDeleteAudio.java                |   4 +-
 tim/prune/undo/UndoDeleteOperation.java            |  38 ++
 tim/prune/undo/UndoDeletePhoto.java                |   4 +-
 tim/prune/undo/UndoDeletePoint.java                |   4 +-
 tim/prune/undo/UndoDeleteRange.java                |  17 +-
 tim/prune/undo/UndoReorder.java                    |   3 +-
 tim/prune/undo/UndoSewSegments.java                |  44 ++
 tim/prune/undo/UndoSplitSegments.java              |  23 +
 tim/prune/undo/UndoStack.java                      |  24 ++
 161 files changed, 7447 insertions(+), 1434 deletions(-)

diff --git a/build.sh b/build.sh
index 329c51f..d6f0c68 100644
--- a/build.sh
+++ b/build.sh
@@ -1,6 +1,6 @@
 # Build script using external exif library
 # Version number
-PRUNENAME=gpsprune_15.1
+PRUNENAME=gpsprune_16.2
 # remove compile directory
 rm -rf compile
 # remove dist directory
diff --git a/tim/prune/App.java b/tim/prune/App.java
index 686b3de..e57dcf6 100644
--- a/tim/prune/App.java
+++ b/tim/prune/App.java
@@ -41,6 +41,7 @@ import tim.prune.load.MediaLinkInfo;
 import tim.prune.load.TrackNameList;
 import tim.prune.save.ExifSaver;
 import tim.prune.save.FileSaver;
+import tim.prune.tips.TipManager;
 import tim.prune.undo.*;
 
 
@@ -59,7 +60,7 @@ public class App
 	private FileLoader _fileLoader = null;
 	private JpegLoader _jpegLoader = null;
 	private FileSaver _fileSaver = null;
-	private Stack<UndoOperation> _undoStack = null;
+	private UndoStack _undoStack = null;
 	private boolean _mangleTimestampsConfirmed = false;
 	private Viewport _viewport = null;
 	private ArrayList<File> _dataFiles = null;
@@ -78,7 +79,7 @@ public class App
 	public App(JFrame inFrame)
 	{
 		_frame = inFrame;
-		_undoStack = new Stack<UndoOperation>();
+		_undoStack = new UndoStack();
 		_track = new Track();
 		_trackInfo = new TrackInfo(_track);
 		FunctionLibrary.initialise(this);
@@ -119,6 +120,22 @@ public class App
 		return _undoStack;
 	}
 
+
+	/**
+	 * Show the specified tip if appropriate
+	 * @param inTipNumber tip number from TipManager
+	 */
+	public void showTip(int inTipNumber)
+	{
+		String key = TipManager.fireTipTrigger(inTipNumber);
+		if (key != null && !key.equals(""))
+		{
+			JOptionPane.showMessageDialog(_frame, I18nManager.getText(key),
+				I18nManager.getText("tip.title"), JOptionPane.INFORMATION_MESSAGE);
+		}
+	}
+
+
 	/**
 	 * Load the specified data files one by one
 	 * @param inDataFiles arraylist containing File objects to load
@@ -511,8 +528,8 @@ public class App
 		// ensure track's field list contains point's fields
 		_track.extendFieldList(inPoint.getFieldList());
 		_trackInfo.selectPoint(inIndex);
-		final int selStart = _trackInfo.getSelection().getStart(); 
-		final int selEnd   = _trackInfo.getSelection().getEnd(); 
+		final int selStart = _trackInfo.getSelection().getStart();
+		final int selEnd   = _trackInfo.getSelection().getEnd();
 		if (selStart < inIndex && selEnd > inIndex)
 		{
 			// Extend end of selection by 1
@@ -894,6 +911,12 @@ public class App
 		UpdateMessageBroker.informSubscribers();
 	}
 
+	/**
+	 * @return the current data status, used for later comparison
+	 */
+	public DataStatus getCurrentDataStatus() {
+		return new DataStatus(_undoStack.size(), _undoStack.getNumTimesDeleted());
+	}
 
 	/**
 	 * Show a map url in an external browser
diff --git a/tim/prune/DataStatus.java b/tim/prune/DataStatus.java
new file mode 100644
index 0000000..cacb288
--- /dev/null
+++ b/tim/prune/DataStatus.java
@@ -0,0 +1,34 @@
+package tim.prune;
+
+/**
+ * Class to remember the current status of the data,
+ * and make it possible to see whether the data has
+ * changed in any way since the DataStatus was requested
+ */
+public class DataStatus
+{
+	private int _undoSize = 0;
+	private int _deleteCount = 0;
+
+	/**
+	 * Constructor
+	 * @param inUndoSize current size of undo stack
+	 * @param inDeleteCount number of times undo stack has been deleted
+	 */
+	public DataStatus(int inUndoSize, int inDeleteCount)
+	{
+		_undoSize = inUndoSize;
+		_deleteCount = inDeleteCount;
+	}
+
+	/**
+	 * Has the data changed compared to the previous status?
+	 * @param inPreviousStatus previous status obtained from App
+	 * @return true if the status is now different
+	 */
+	public boolean hasDataChanged(DataStatus inPreviousStatus)
+	{
+		return _undoSize != inPreviousStatus._undoSize
+			|| _deleteCount != inPreviousStatus._deleteCount;
+	}
+}
diff --git a/tim/prune/ExternalTools.java b/tim/prune/ExternalTools.java
index 31c0302..8cd1aef 100644
--- a/tim/prune/ExternalTools.java
+++ b/tim/prune/ExternalTools.java
@@ -16,6 +16,8 @@ public abstract class ExternalTools
 	public static final int TOOL_GPSBABEL = 1;
 	/** Constant for Gnuplot */
 	public static final int TOOL_GNUPLOT  = 2;
+	/** Constant for Xerces xml library */
+	public static final int TOOL_XERCES   = 3;
 	/** Config keys in order that the tools are defined above */
 	private static final String[] CONFIG_KEYS = {Config.KEY_EXIFTOOL_PATH, Config.KEY_GPSBABEL_PATH, Config.KEY_GNUPLOT_PATH};
 	/** Verification flags for the tools in the order defined above */
@@ -37,6 +39,15 @@ public abstract class ExternalTools
 				if (toolPath != null && toolPath.length() > 0) {
 					return check(toolPath + " " + VERIFY_FLAGS[inToolNum]);
 				}
+				break;
+			case TOOL_XERCES:
+				try {
+					return Class.forName("org.apache.xerces.parsers.SAXParser").getClassLoader() != null;
+				}
+				catch (ClassNotFoundException e) {
+					// System.err.println(e.getClass().getName() + " : " + e.getMessage());
+				}
+				break;
 		}
 		// Not found
 		return false;
diff --git a/tim/prune/FunctionLibrary.java b/tim/prune/FunctionLibrary.java
index 53d8563..9c51c8d 100644
--- a/tim/prune/FunctionLibrary.java
+++ b/tim/prune/FunctionLibrary.java
@@ -5,13 +5,18 @@ import tim.prune.correlate.PhotoCorrelator;
 import tim.prune.function.*;
 import tim.prune.function.charts.Charter;
 import tim.prune.function.compress.CompressTrackFunction;
+import tim.prune.function.compress.MarkPointsInRectangleFunction;
 import tim.prune.function.distance.DistanceFunction;
 import tim.prune.function.edit.PointNameEditor;
 import tim.prune.function.estimate.EstimateTime;
 import tim.prune.function.estimate.LearnParameters;
 import tim.prune.function.gpsies.GetGpsiesFunction;
 import tim.prune.function.gpsies.UploadGpsiesFunction;
+import tim.prune.function.sew.SewTrackSegmentsFunction;
+import tim.prune.function.sew.SplitSegmentsFunction;
+import tim.prune.function.srtm.DownloadSrtmFunction;
 import tim.prune.function.srtm.LookupSrtmFunction;
+import tim.prune.function.weather.GetWeatherForecastFunction;
 import tim.prune.load.AudioLoader;
 import tim.prune.load.BabelLoadFromFile;
 import tim.prune.load.BabelLoadFromGps;
@@ -38,12 +43,16 @@ public abstract class FunctionLibrary
 	public static GenericFunction FUNCTION_SAVECONFIG  = null;
 	public static GenericFunction FUNCTION_EDIT_WAYPOINT_NAME = null;
 	public static RearrangeWaypointsFunction FUNCTION_REARRANGE_WAYPOINTS = null;
+	public static GenericFunction FUNCTION_SPLIT_SEGMENTS = null;
+	public static GenericFunction FUNCTION_SEW_SEGMENTS = null;
 	public static GenericFunction FUNCTION_REARRANGE_PHOTOS = null;
 	public static GenericFunction FUNCTION_COMPRESS = null;
 	public static GenericFunction FUNCTION_DELETE_RANGE = null;
 	public static GenericFunction FUNCTION_CROP_TRACK = null;
+	public static GenericFunction FUNCTION_MARK_IN_RECTANGLE = null;
 	public static GenericFunction FUNCTION_INTERPOLATE = null;
 	public static GenericFunction FUNCTION_LOOKUP_SRTM = null;
+	public static GenericFunction FUNCTION_DOWNLOAD_SRTM = null;
 	public static GenericFunction FUNCTION_LOOKUP_WIKIPEDIA = null;
 	public static GenericFunction FUNCTION_SEARCH_WIKIPEDIA = null;
 	public static GenericFunction FUNCTION_DOWNLOAD_OSM = null;
@@ -71,6 +80,7 @@ public abstract class FunctionLibrary
 	public static GenericFunction FUNCTION_LEARN_ESTIMATION_PARAMS = null;
 	public static GenericFunction FUNCTION_GET_GPSIES = null;
 	public static GenericFunction FUNCTION_UPLOAD_GPSIES = null;
+	public static GenericFunction FUNCTION_GET_WEATHER_FORECAST = null;
 	public static GenericFunction FUNCTION_LOAD_AUDIO = null;
 	public static GenericFunction FUNCTION_REMOVE_AUDIO = null;
 	public static GenericFunction FUNCTION_CORRELATE_AUDIOS = null;
@@ -105,12 +115,16 @@ public abstract class FunctionLibrary
 		FUNCTION_SAVECONFIG = new SaveConfig(inApp);
 		FUNCTION_EDIT_WAYPOINT_NAME = new PointNameEditor(inApp);
 		FUNCTION_REARRANGE_WAYPOINTS = new RearrangeWaypointsFunction(inApp);
+		FUNCTION_SPLIT_SEGMENTS = new SplitSegmentsFunction(inApp);
+		FUNCTION_SEW_SEGMENTS = new SewTrackSegmentsFunction(inApp);
 		FUNCTION_REARRANGE_PHOTOS = new RearrangePhotosFunction(inApp);
 		FUNCTION_COMPRESS = new CompressTrackFunction(inApp);
 		FUNCTION_DELETE_RANGE = new DeleteSelectedRangeFunction(inApp);
 		FUNCTION_CROP_TRACK = new CropToSelection(inApp);
+		FUNCTION_MARK_IN_RECTANGLE = new MarkPointsInRectangleFunction(inApp);
 		FUNCTION_INTERPOLATE = new InterpolateFunction(inApp);
 		FUNCTION_LOOKUP_SRTM = new LookupSrtmFunction(inApp);
+		FUNCTION_DOWNLOAD_SRTM = new DownloadSrtmFunction(inApp);
 		FUNCTION_LOOKUP_WIKIPEDIA = new GetWikipediaFunction(inApp);
 		FUNCTION_SEARCH_WIKIPEDIA = new SearchWikipediaNames(inApp);
 		FUNCTION_DOWNLOAD_OSM = new DownloadOsmFunction(inApp);
@@ -137,6 +151,7 @@ public abstract class FunctionLibrary
 		FUNCTION_LEARN_ESTIMATION_PARAMS = new LearnParameters(inApp);
 		FUNCTION_GET_GPSIES = new GetGpsiesFunction(inApp);
 		FUNCTION_UPLOAD_GPSIES = new UploadGpsiesFunction(inApp);
+		FUNCTION_GET_WEATHER_FORECAST = new GetWeatherForecastFunction(inApp);
 		FUNCTION_LOAD_AUDIO = new AudioLoader(inApp);
 		FUNCTION_REMOVE_AUDIO = new RemoveAudioFunction(inApp);
 		FUNCTION_CORRELATE_AUDIOS = new AudioCorrelator(inApp);
diff --git a/tim/prune/GpsPrune.java b/tim/prune/GpsPrune.java
index 1f4d6c8..d8e9549 100644
--- a/tim/prune/GpsPrune.java
+++ b/tim/prune/GpsPrune.java
@@ -28,16 +28,16 @@ import tim.prune.gui.profile.ProfileChart;
 /**
  * GpsPrune is a tool to visualize, edit, convert and prune GPS data
  * Please see the included readme.txt or http://activityworkshop.net
- * This software is copyright activityworkshop.net 2006-2013 and made available through the Gnu GPL version 2.
+ * This software is copyright activityworkshop.net 2006-2014 and made available through the Gnu GPL version 2.
  * For license details please see the included license.txt.
  * GpsPrune is the main entry point to the application, including initialisation and launch
  */
 public class GpsPrune
 {
 	/** Version number of application, used in about screen and for version check */
-	public static final String VERSION_NUMBER = "15.1";
+	public static final String VERSION_NUMBER = "16.2";
 	/** Build number, just used for about screen */
-	public static final String BUILD_NUMBER = "283a";
+	public static final String BUILD_NUMBER = "303b";
 	/** Static reference to App object */
 	private static App APP = null;
 
diff --git a/tim/prune/I18nManager.java b/tim/prune/I18nManager.java
index 349db3f..24a4fde 100644
--- a/tim/prune/I18nManager.java
+++ b/tim/prune/I18nManager.java
@@ -115,4 +115,22 @@ public abstract class I18nManager
 		// return the key itself
 		return inKey;
 	}
+
+	/**
+	 * Lookup the given key and return the associated text, formatting with the number
+	 * @param inKey key to lookup (text should contain a %d)
+	 * @param inNumber number to substitute into the %d
+	 * @return associated text, or the key if not found
+	 */
+	public static String getTextWithNumber(String inKey, int inNumber)
+	{
+		String localText = getText(inKey);
+		try
+		{
+			localText = String.format(localText, inNumber);
+		}
+		catch (Exception e)
+		{} // printf formatting didn't work, maybe the placeholders are wrong?
+		return localText;
+	}
 }
diff --git a/tim/prune/UpdateMessageBroker.java b/tim/prune/UpdateMessageBroker.java
index 337fc27..c032359 100644
--- a/tim/prune/UpdateMessageBroker.java
+++ b/tim/prune/UpdateMessageBroker.java
@@ -7,8 +7,12 @@ package tim.prune;
 public abstract class UpdateMessageBroker
 {
 	private static final int MAXIMUM_NUMBER_SUBSCRIBERS = 6;
+	/** Array of all subscribers */
 	private static DataSubscriber[] _subscribers = new DataSubscriber[MAXIMUM_NUMBER_SUBSCRIBERS];
+	/** Counter of the number of subscribers added so far */
 	private static int _subscriberNum = 0;
+	/** Enable/disabled flag */
+	private static boolean _enabled = true;
 
 
 	/**
@@ -21,6 +25,14 @@ public abstract class UpdateMessageBroker
 		_subscriberNum++;
 	}
 
+	/**
+	 * Enable or disable the messaging (to allow temporary disabling for multiple operations)
+	 * @param inEnabled false to disable, true to enable again
+	 */
+	public static void enableMessaging(boolean inEnabled)
+	{
+		_enabled = inEnabled;
+	}
 
 	/**
 	 * Send a message to all subscribers that
@@ -39,6 +51,7 @@ public abstract class UpdateMessageBroker
 	public static void informSubscribers(byte inChange)
 	{
 		// TODO: Launch separate thread so that whatever caused the inform can finish
+		if (!_enabled) return;
 		for (int i=0; i<_subscribers.length; i++)
 		{
 			if (_subscribers[i] != null)
@@ -54,6 +67,7 @@ public abstract class UpdateMessageBroker
 	 */
 	public static void informSubscribers(String inMessage)
 	{
+		if (!_enabled) return;
 		for (int i=0; i<_subscribers.length; i++)
 		{
 			if (_subscribers[i] != null)
diff --git a/tim/prune/config/Config.java b/tim/prune/config/Config.java
index 385a5d9..35f5a31 100644
--- a/tim/prune/config/Config.java
+++ b/tim/prune/config/Config.java
@@ -7,6 +7,7 @@ import java.util.Properties;
 import tim.prune.data.RecentFileList;
 import tim.prune.data.UnitSet;
 import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.map.MapSourceLibrary;
 
 
 /**
@@ -44,6 +45,8 @@ public abstract class Config
 	public static final String KEY_GPS_FORMAT = "prune.gpsformat";
 	/** Key for GPSBabel filter string */
 	public static final String KEY_GPSBABEL_FILTER = "prune.gpsbabelfilter";
+	/** Key for GPSBabel import file format */
+	public static final String KEY_IMPORT_FILE_FORMAT = "prune.lastimportfileformat";
 	/** Key for Povray font */
 	public static final String KEY_POVRAY_FONT = "prune.povrayfont";
 	/** Key for the selected unit set */
@@ -80,6 +83,8 @@ public abstract class Config
 	public static final String KEY_RECENT_FILES = "prune.recentfiles";
 	/** Key for estimation parameters */
 	public static final String KEY_ESTIMATION_PARAMS = "prune.estimationparams";
+	/** Key for 3D exaggeration factor */
+	public static final String KEY_HEIGHT_EXAGGERATION = "prune.heightexaggeration";
 
 
 	/** Initialise the default properties */
@@ -142,6 +147,8 @@ public abstract class Config
 		_colourScheme.loadFromHex(_configValues.getProperty(KEY_COLOUR_SCHEME));
 		_recentFiles = new RecentFileList(_configValues.getProperty(KEY_RECENT_FILES));
 		_unitSet = UnitSetLibrary.getUnitSet(_configValues.getProperty(KEY_UNITSET_KEY));
+		// Adjust map source index if necessary
+		adjustSelectedMap();
 
 		if (loadFailed) {
 			throw new ConfigException();
@@ -164,13 +171,35 @@ public abstract class Config
 		props.put(KEY_EXIFTOOL_PATH, "exiftool");
 		props.put(KEY_GNUPLOT_PATH, "gnuplot");
 		props.put(KEY_GPSBABEL_PATH, "gpsbabel");
+		props.put(KEY_IMPORT_FILE_FORMAT, "-1"); // no file format selected
 		props.put(KEY_KMZ_IMAGE_SIZE, "240");
 		props.put(KEY_AUTOSAVE_SETTINGS, "0"); // autosave false by default
 		props.put(KEY_UNITSET_KEY, "unitset.kilometres"); // metric by default
+		props.put(KEY_HEIGHT_EXAGGERATION, "100"); // 100%, no exaggeration
 		return props;
 	}
 
 	/**
+	 * Adjust the index of the selected map
+	 * (only required if config was loaded from a previous version of GpsPrune)
+	 */
+	private static void adjustSelectedMap()
+	{
+		int sourceNum = getConfigInt(Config.KEY_MAPSOURCE_INDEX);
+		int prevNumFixed = getConfigInt(Config.KEY_NUM_FIXED_MAPS);
+		// Number of fixed maps not specified in version <=13, default to 6
+		if (prevNumFixed == 0) prevNumFixed = 6;
+		int currNumFixed = MapSourceLibrary.getNumFixedSources();
+		// Only need to do something if the number has changed
+		if (currNumFixed != prevNumFixed && (sourceNum >= prevNumFixed || sourceNum >= currNumFixed))
+		{
+			sourceNum += (currNumFixed - prevNumFixed);
+			setConfigInt(Config.KEY_MAPSOURCE_INDEX, sourceNum);
+		}
+		setConfigInt(Config.KEY_NUM_FIXED_MAPS, currNumFixed);
+	}
+
+	/**
 	 * @param inString String to parse
 	 * @return int value of String, or 0 if unparseable
 	 */
diff --git a/tim/prune/copyright.txt b/tim/prune/copyright.txt
index 1add3e7..7990765 100644
--- a/tim/prune/copyright.txt
+++ b/tim/prune/copyright.txt
@@ -1,4 +1,4 @@
-The source code of GpsPrune is copyright 2006-2013 activityworkshop.net
+The source code of GpsPrune is copyright 2006-2014 activityworkshop.net
 and is distributed under the terms of the Gnu GPL version 2.
 
 Portions of the package jpeg.drew (if included in this package) were taken
diff --git a/tim/prune/correlate/Correlator.java b/tim/prune/correlate/Correlator.java
index 85b96e9..7e887ff 100644
--- a/tim/prune/correlate/Correlator.java
+++ b/tim/prune/correlate/Correlator.java
@@ -37,6 +37,7 @@ import tim.prune.data.Timestamp;
 import tim.prune.data.Track;
 import tim.prune.data.Unit;
 import tim.prune.data.UnitSetLibrary;
+import tim.prune.tips.TipManager;
 
 /**
  * Abstract superclass of the two correlator functions
@@ -45,7 +46,6 @@ public abstract class Correlator extends GenericFunction
 {
 	protected JDialog _dialog;
 	private CardStack _cards = null;
-	private JLabel _tipLabel = null;
 	private JTable _selectionTable = null;
 	protected JTable _previewTable = null;
 	private boolean _previewEnabled = false; // flag required to enable preview function on final panel
@@ -55,7 +55,7 @@ public abstract class Correlator extends GenericFunction
 	private JRadioButton _timeLimitRadio = null, _distLimitRadio = null;
 	private JTextField _limitMinBox = null, _limitSecBox = null;
 	private JTextField _limitDistBox = null;
-	private JComboBox _distUnitsDropdown = null;
+	private JComboBox<String> _distUnitsDropdown = null;
 	private JButton _nextButton = null, _backButton = null;
 	protected JButton _okButton = null;
 
@@ -117,7 +117,9 @@ public abstract class Correlator extends GenericFunction
 		_cards.showCard(card);
 		showCard(0); // does set up and next/prev enabling
 		_okButton.setEnabled(false);
-		_tipLabel.setVisible(!isCardEnabled(1));
+		if (!isCardEnabled(1)) {
+			_app.showTip(TipManager.Tip_ManuallyCorrelateOne);
+		}
 		_dialog.setVisible(true);
 	}
 
@@ -339,9 +341,6 @@ public abstract class Correlator extends GenericFunction
 		card2.setLayout(new BorderLayout());
 		JPanel card2Top = new JPanel();
 		card2Top.setLayout(new BoxLayout(card2Top, BoxLayout.Y_AXIS));
-		_tipLabel = new JLabel(I18nManager.getText("dialog.correlate.options.tip"));
-		_tipLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 5, 6));
-		card2Top.add(_tipLabel);
 		JLabel introLabel = new JLabel(I18nManager.getText("dialog.correlate.options.intro"));
 		introLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 5, 6));
 		card2Top.add(introLabel);
@@ -400,7 +399,7 @@ public abstract class Correlator extends GenericFunction
 		noTimeLimitRadio.addItemListener(optionsChangedListener);
 		noTimeLimitRadio.addActionListener(radioListener);
 		timeLimitPanel.add(noTimeLimitRadio);
-		_timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + " : ");
+		_timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + ": ");
 		_timeLimitRadio.addItemListener(optionsChangedListener);
 		_timeLimitRadio.addActionListener(radioListener);
 		timeLimitPanel.add(_timeLimitRadio);
@@ -421,7 +420,7 @@ public abstract class Correlator extends GenericFunction
 		noDistLimitRadio.addItemListener(optionsChangedListener);
 		noDistLimitRadio.addActionListener(radioListener);
 		distLimitPanel.add(noDistLimitRadio);
-		_distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit"));
+		_distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit") + ": ");
 		_distLimitRadio.addItemListener(optionsChangedListener);
 		_distLimitRadio.addActionListener(radioListener);
 		distLimitPanel.add(_distLimitRadio);
@@ -431,7 +430,7 @@ public abstract class Correlator extends GenericFunction
 		distLimitPanel.add(_limitDistBox);
 		String[] distUnitsOptions = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.metres"),
 			I18nManager.getText("units.miles")};
-		_distUnitsDropdown = new JComboBox(distUnitsOptions);
+		_distUnitsDropdown = new JComboBox<String>(distUnitsOptions);
 		_distUnitsDropdown.addItemListener(optionsChangedListener);
 		distLimitPanel.add(_distUnitsDropdown);
 		limitsPanel.add(distLimitPanel);
diff --git a/tim/prune/data/DataPoint.java b/tim/prune/data/DataPoint.java
index 4de97ef..c244622 100644
--- a/tim/prune/data/DataPoint.java
+++ b/tim/prune/data/DataPoint.java
@@ -138,7 +138,7 @@ public class DataPoint
 	 * @param inIndex index number starting at zero
 	 * @return field value, or null if not found
 	 */
-	public String getFieldValue(int inIndex)
+	private String getFieldValue(int inIndex)
 	{
 		if (_fieldValues == null || inIndex < 0 || inIndex >= _fieldValues.length)
 			return null;
@@ -338,6 +338,46 @@ public class DataPoint
 		return (inOther._waypointName != null && inOther._waypointName.equals(_waypointName));
 	}
 
+	/**
+	 * Add an altitude offset to this point, and keep the point's string value in sync
+	 * @param inOffset offset as double
+	 * @param inUnit unit of offset, feet or metres
+	 * @param inDecimals number of decimal places
+	 */
+	public void addAltitudeOffset(double inOffset, Unit inUnit, int inDecimals)
+	{
+		if (hasAltitude())
+		{
+			_altitude.addOffset(inOffset, inUnit, inDecimals);
+			_fieldValues[_fieldList.getFieldIndex(Field.ALTITUDE)] = _altitude.getStringValue(null);
+			setModified(false);
+		}
+	}
+
+	/**
+	 * Reset the altitude to the previous value (by an undo)
+	 * @param inClone altitude object cloned from earlier
+	 */
+	public void resetAltitude(Altitude inClone)
+	{
+		_altitude.reset(inClone);
+		_fieldValues[_fieldList.getFieldIndex(Field.ALTITUDE)] = _altitude.getStringValue(null);
+		setModified(true);
+	}
+
+	/**
+	 * Add a time offset to this point
+	 * @param inOffset offset to add (-ve to subtract)
+	 */
+	public void addTimeOffset(long inOffset)
+	{
+		if (hasTimestamp())
+		{
+			_timestamp.addOffset(inOffset);
+			_fieldValues[_fieldList.getFieldIndex(Field.TIMESTAMP)] = _timestamp.getText();
+			setModified(false);
+		}
+	}
 
 	/**
 	 * Set the photo for this data point
diff --git a/tim/prune/data/DoubleRange.java b/tim/prune/data/DoubleRange.java
index 4e36122..3f252ee 100644
--- a/tim/prune/data/DoubleRange.java
+++ b/tim/prune/data/DoubleRange.java
@@ -45,6 +45,18 @@ public class DoubleRange
 		_empty = false;
 	}
 
+	/**
+	 * Combine this range with another one
+	 * @param inOtherRange other range to add to this one
+	 */
+	public void combine(DoubleRange inOtherRange)
+	{
+		if (inOtherRange != null && inOtherRange.getRange() > 1.0)
+		{
+			addValue(inOtherRange.getMinimum());
+			addValue(inOtherRange.getMaximum());
+		}
+	}
 
 	/**
 	 * @return true if data values were found
diff --git a/tim/prune/data/PointScaler.java b/tim/prune/data/PointScaler.java
index a133885..446c0a2 100644
--- a/tim/prune/data/PointScaler.java
+++ b/tim/prune/data/PointScaler.java
@@ -5,14 +5,23 @@ package tim.prune.data;
  */
 public class PointScaler
 {
-	// Original data
+	/** Original data */
 	private Track _track = null;
-	// Scaled values
+	/** Secondary data for terrain grid */
+	private Track _terrainTrack = null;
+	// Scaled values for data track
 	private double[] _xValues = null;
 	private double[] _yValues = null;
 	private double[] _altValues = null;
+	// Scaled values for terrain track, if any
+	private double[] _terrainxValues = null;
+	private double[] _terrainyValues = null;
+	private double[] _terrainAltValues = null;
 	// Altitude range
 	private double _altitudeRange = 0.0;
+	private double _minAltitudeMetres = 0.0;
+	// Horizontal distance
+	private double _horizDistanceMetres = 0.0;
 
 
 	/**
@@ -24,6 +33,13 @@ public class PointScaler
 		_track = inTrack;
 	}
 
+	/**
+	 * @param inTrack terrain track to add
+	 */
+	public void addTerrain(Track inTrack)
+	{
+		_terrainTrack = inTrack;
+	}
 
 	/**
 	 * Scale the points
@@ -33,12 +49,16 @@ public class PointScaler
 		// Work out extents
 		TrackExtents extents = new TrackExtents(_track);
 		extents.applySquareBorder();
-		final double horizDistance = Math.max(extents.getHorizontalDistanceMetres(), 1.0);
+		_horizDistanceMetres = Math.max(extents.getHorizontalDistanceMetres(), 1.0);
 		final int numPoints = _track.getNumPoints();
 
-		// Find altitude range
-		_altitudeRange = extents.getAltitudeRange().getRange() / horizDistance;
-		final double minAltitude = extents.getAltitudeRange().getMinimum();
+		// Find altitude range (including terrain)
+		DoubleRange altRangeMetres = extents.getAltitudeRange();
+		if (_terrainTrack != null) {
+			altRangeMetres.combine(new TrackExtents(_terrainTrack).getAltitudeRange());
+		}
+		_altitudeRange = altRangeMetres.getRange() / _horizDistanceMetres;
+		_minAltitudeMetres = altRangeMetres.getMinimum();
 
 		// create new arrays for scaled values
 		if (_xValues == null || _xValues.length != numPoints)
@@ -46,6 +66,12 @@ public class PointScaler
 			_xValues = new double[numPoints];
 			_yValues = new double[numPoints];
 			_altValues = new double[numPoints];
+			if (_terrainTrack != null)
+			{
+				_terrainxValues = new double[_terrainTrack.getNumPoints()];
+				_terrainyValues = new double[_terrainTrack.getNumPoints()];
+				_terrainAltValues = new double[_terrainTrack.getNumPoints()];
+			}
 		}
 
 		final double midXvalue = extents.getXRange().getMidValue();
@@ -60,7 +86,16 @@ public class PointScaler
 			{
 				_xValues[p] = (_track.getX(p) - midXvalue) / xyRange;
 				_yValues[p] = (midYvalue - _track.getY(p)) / xyRange; // y values have to be inverted
-				_altValues[p] = (point.getAltitude().getMetricValue() - minAltitude) / horizDistance;
+				_altValues[p] = (point.getAltitude().getMetricValue() - _minAltitudeMetres) / _horizDistanceMetres;
+			}
+		}
+		if (_terrainTrack != null)
+		{
+			for (int p=0; p<_terrainTrack.getNumPoints(); p++)
+			{
+				_terrainxValues[p] = (_terrainTrack.getX(p) - midXvalue) / xyRange;
+				_terrainyValues[p] = (midYvalue - _terrainTrack.getY(p)) / xyRange; // y values have to be inverted
+				_terrainAltValues[p] = (_terrainTrack.getPoint(p).getAltitude().getMetricValue() - _minAltitudeMetres) / _horizDistanceMetres;
 			}
 		}
 	}
@@ -98,10 +133,42 @@ public class PointScaler
 	}
 
 	/**
-	 * @return altitude range, in metres
+	 * @return altitude range as fraction of horizontal range
 	 */
 	public double getAltitudeRange()
 	{
 		return _altitudeRange;
 	}
+
+	/**
+	 * Get the horizontal value for the specified point
+	 * @param inIndex index of point, starting at 0
+	 * @return scaled horizontal value
+	 */
+	public double getTerrainHorizValue(int inIndex)
+	{
+		return _terrainxValues[inIndex];
+	}
+
+	/**
+	 * Get the vertical value for the specified point
+	 * @param inIndex index of point, starting at 0
+	 * @return scaled vertical value
+	 */
+	public double getTerrainVertValue(int inIndex)
+	{
+		return _terrainyValues[inIndex];
+	}
+
+	/**
+	 * @param inIndex index of point in terrain track
+	 * @return scaled altitude value for the specified terrain point
+	 */
+	public double getTerrainAltValue(int inIndex)
+	{
+		if (_terrainAltValues != null) {
+			return _terrainAltValues[inIndex];
+		}
+		return 0.0;
+	}
 }
diff --git a/tim/prune/data/RangeStats.java b/tim/prune/data/RangeStats.java
index 0ed8fc7..37d061b 100644
--- a/tim/prune/data/RangeStats.java
+++ b/tim/prune/data/RangeStats.java
@@ -9,6 +9,7 @@ import tim.prune.config.Config;
  */
 public class RangeStats
 {
+	// MAYBE: Split into basic stats (quick to calculate, for detailsdisplay) and full stats (for other two)
 	private boolean _valid = false;
 	private int     _numPoints   = 0;
 	private int     _startIndex = 0, _endIndex = 0;
@@ -17,7 +18,8 @@ public class RangeStats
 	private AltitudeRange _gentleAltitudeRange = null, _steepAltitudeRange = null;
 	private Timestamp _earliestTimestamp = null, _latestTimestamp = null;
 	private long _movingMilliseconds = 0L;
-	private boolean _timestampsIncomplete = false;
+	private boolean _timesIncomplete = false;
+	private boolean _timesOutOfSequence = false;
 	private double _totalDistanceRads = 0.0, _movingDistanceRads = 0.0;
 	// Note, maximum speed is not calculated here, use the SpeedData method instead
 
@@ -123,12 +125,14 @@ public class RangeStats
 				if (!p.getSegmentStart() && prevPoint != null && prevPoint.hasTimestamp())
 				{
 					long millisLater = p.getTimestamp().getMillisecondsSince(prevPoint.getTimestamp());
-					if (millisLater < 0) {_timestampsIncomplete = true;}
-					_movingMilliseconds += millisLater;
+					if (millisLater < 0) {_timesOutOfSequence = true;}
+					else {
+						_movingMilliseconds += millisLater;
+					}
 				}
 			}
-			else if (!p.getSegmentStart()) {
-				_timestampsIncomplete = true;
+			else {
+				_timesIncomplete = true;
 			}
 
 			prevPoint = p;
@@ -207,9 +211,14 @@ public class RangeStats
 		return _movingMilliseconds / 1000;
 	}
 
-	/** @return true if any timestamps are missing or out of sequence */
+	/** @return true if any timestamps are missing */
 	public boolean getTimestampsIncomplete() {
-		return _timestampsIncomplete;
+		return _timesIncomplete;
+	}
+
+	/** @return true if any timestamps are out of sequence */
+	public boolean getTimestampsOutOfSequence() {
+		return _timesOutOfSequence;
 	}
 
 	/** @return total distance in the current distance units (km or mi) */
diff --git a/tim/prune/data/Selection.java b/tim/prune/data/Selection.java
index 6125eac..68654e8 100644
--- a/tim/prune/data/Selection.java
+++ b/tim/prune/data/Selection.java
@@ -17,8 +17,8 @@ public class Selection
 	private int _currentPhotoIndex = -1;
 	private int _currentAudioIndex = -1;
 	private AltitudeRange _altitudeRange = null;
-	private long _totalSeconds = 0L, _movingSeconds = 0L;
-	private double _angDistance = -1.0, _angMovingDistance = -1.0;
+	private long _movingSeconds = 0L;
+	private double _angMovingDistance = -1.0;
 
 
 	/**
@@ -65,7 +65,8 @@ public class Selection
 	{
 		final int numPoints = _track.getNumPoints();
 		// Recheck if the number of points has changed
-		if (numPoints != _prevNumPoints) {
+		if (numPoints != _prevNumPoints)
+		{
 			_prevNumPoints = numPoints;
 			check();
 		}
@@ -73,11 +74,10 @@ public class Selection
 		{
 			_altitudeRange = new AltitudeRange();
 			Altitude altitude = null;
-			Timestamp time = null, startTime = null, endTime = null;
-			Timestamp previousTime = null;
+			Timestamp time = null, previousTime = null;
 			DataPoint lastPoint = null, currPoint = null;
-			_angDistance = 0.0; _angMovingDistance = 0.0;
-			_totalSeconds = 0L; _movingSeconds = 0L;
+			_angMovingDistance = 0.0;
+			_movingSeconds = 0L;
 			// Loop over points in selection
 			for (int i=_startIndex; i<=_endIndex; i++)
 			{
@@ -86,14 +86,17 @@ public class Selection
 				// Ignore waypoints in altitude calculations
 				if (!currPoint.isWaypoint() && altitude.isValid())
 				{
-					_altitudeRange.addValue(altitude);
+					if (currPoint.getSegmentStart()) {
+						_altitudeRange.ignoreValue(altitude);
+					}
+					else {
+						_altitudeRange.addValue(altitude);
+					}
 				}
-				// Store the first and last timestamp in the range
+				// Compare timestamps within the segments
 				time = currPoint.getTimestamp();
 				if (time.isValid())
 				{
-					if (startTime == null || startTime.isAfter(time)) startTime = time;
-					if (endTime == null || time.isAfter(endTime)) endTime = time;
 					// add moving time
 					if (!currPoint.getSegmentStart() && previousTime != null && time.isAfter(previousTime)) {
 						_movingSeconds += time.getSecondsSince(previousTime);
@@ -106,7 +109,6 @@ public class Selection
 					if (lastPoint != null)
 					{
 						double radians = DataPoint.calculateRadiansBetween(lastPoint, currPoint);
-						_angDistance += radians;
 						if (!currPoint.getSegmentStart()) {
 							_angMovingDistance += radians;
 						}
@@ -114,9 +116,6 @@ public class Selection
 					lastPoint = currPoint;
 				}
 			}
-			if (endTime != null) {
-				_totalSeconds = endTime.getSecondsSince(startTime);
-			}
 		}
 		_valid = true;
 	}
@@ -152,15 +151,6 @@ public class Selection
 
 
 	/**
-	 * @return number of seconds spanned by selection
-	 */
-	public long getNumSeconds()
-	{
-		if (!_valid) recalculate();
-		return _totalSeconds;
-	}
-
-	/**
 	 * @return number of seconds spanned by segments within selection
 	 */
 	public long getMovingSeconds()
@@ -170,14 +160,6 @@ public class Selection
 	}
 
 	/**
-	 * @return distance of Selection in specified units
-	 */
-	public double getDistance()
-	{
-		return Distance.convertRadiansToDistance(_angDistance);
-	}
-
-	/**
 	 * @return moving distance of Selection in current units
 	 */
 	public double getMovingDistance()
diff --git a/tim/prune/data/Track.java b/tim/prune/data/Track.java
index 8623c58..45810e2 100644
--- a/tim/prune/data/Track.java
+++ b/tim/prune/data/Track.java
@@ -316,13 +316,13 @@ public class Track
 		// Loop over all points within range
 		for (int i=inStart; i<=inEnd; i++)
 		{
-			Timestamp timestamp = _dataPoints[i].getTimestamp();
-			if (timestamp != null)
+			DataPoint p = _dataPoints[i];
+			if (p != null && p.hasTimestamp())
 			{
 				// This point has a timestamp so add the offset to it
 				foundTimestamp = true;
-				timestamp.addOffset(inOffset);
-				_dataPoints[i].setModified(inUndo);
+				p.addTimeOffset(inOffset);
+				p.setModified(inUndo);
 			}
 		}
 		return foundTimestamp;
@@ -348,13 +348,13 @@ public class Track
 		// Loop over all points within range
 		for (int i=inStart; i<=inEnd; i++)
 		{
-			Altitude alt = _dataPoints[i].getAltitude();
-			if (alt != null && alt.isValid())
+			DataPoint p = _dataPoints[i];
+			if (p != null && p.hasAltitude())
 			{
 				// This point has an altitude so add the offset to it
 				foundAlt = true;
-				alt.addOffset(inOffset, inUnit, inDecimals);
-				_dataPoints[i].setModified(false);
+				p.addAltitudeOffset(inOffset, inUnit, inDecimals);
+				p.setModified(false);
 			}
 		}
 		// needs to be scaled again
diff --git a/tim/prune/function/AboutScreen.java b/tim/prune/function/AboutScreen.java
index 7a95693..5a463a3 100644
--- a/tim/prune/function/AboutScreen.java
+++ b/tim/prune/function/AboutScreen.java
@@ -99,7 +99,7 @@ public class AboutScreen extends GenericFunction
 		descBuffer.append("<p>").append(I18nManager.getText("dialog.about.languages")).append(" : ")
 			.append("\u010de\u0161tina, deutsch, english, espa\u00F1ol, fran\u00E7ais, italiano, magyar,<br>" +
 				" nederlands, polski, portugu\u00EAs, \u0440\u0443\u0441\u0441\u043a\u0438\u0439 (russian), \u4e2d\u6587 (chinese), \u65E5\u672C\u8A9E (japanese),<br>" +
-				" \uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean), schwiizerd\u00FC\u00FCtsch, t\u00FCrk\u00E7e, afrikaans, rom\u00E2n\u0103</p>");
+				" \uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean), schwiizerd\u00FC\u00FCtsch, t\u00FCrk\u00E7e, afrikaans, rom\u00E2n\u0103, ukrainian</p>");
 		descBuffer.append("<p>").append(I18nManager.getText("dialog.about.translatedby")).append("</p>");
 		JEditorPane descPane = new JEditorPane("text/html", descBuffer.toString());
 		descPane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
@@ -130,7 +130,7 @@ public class AboutScreen extends GenericFunction
 			new JLabel(System.getProperty("java.runtime.version")),
 			1, 1);
 		// Create install labels to be populated later
-		final int NUM_INSTALL_CHECKS = 4;
+		final int NUM_INSTALL_CHECKS = 5;
 		_installedLabels = new JLabel[NUM_INSTALL_CHECKS];
 		for (int i=0; i<NUM_INSTALL_CHECKS; i++) {
 			_installedLabels[i] = new JLabel("...");
@@ -151,13 +151,15 @@ public class AboutScreen extends GenericFunction
 			new JLabel(I18nManager.getText("dialog.about.systeminfo.gnuplot") + " : "),
 			0, 5);
 		addToGridBagPanel(sysInfoPanel, gridBag, constraints, _installedLabels[3], 1, 5);
+		addToGridBagPanel(sysInfoPanel, gridBag, constraints, new JLabel("Xerces : "), 0, 6);
+		addToGridBagPanel(sysInfoPanel, gridBag, constraints, _installedLabels[4], 1, 6);
 		// Exif library
 		addToGridBagPanel(sysInfoPanel, gridBag, constraints,
 			new JLabel(I18nManager.getText("dialog.about.systeminfo.exiflib") + " : "),
-			0, 6);
+			0, 7);
 		final String exiflibkey = "dialog.about.systeminfo.exiflib." + ExifGateway.getDescriptionKey();
 		addToGridBagPanel(sysInfoPanel, gridBag, constraints,
-			new JLabel(I18nManager.getText(exiflibkey)), 1, 6);
+			new JLabel(I18nManager.getText(exiflibkey)), 1, 7);
 		_tabs.add(I18nManager.getText("dialog.about.systeminfo"), sysInfoPanel);
 
 		// Third pane for credits
@@ -198,7 +200,7 @@ public class AboutScreen extends GenericFunction
 			new JLabel(" katpatuka, R\u00E9mi, Marcus, Ali, Javier, Jeroen, prot_d, Gy\u00F6rgy,"),
 			1, 5);
 		addToGridBagPanel(creditsPanel, gridBag, constraints,
-			new JLabel(" HooAU, Sergey"),
+			new JLabel(" HooAU, Sergey, P\u00E9ter, serhijdubyk"),
 			1, 6);
 		addToGridBagPanel(creditsPanel, gridBag, constraints,
 			new JLabel(I18nManager.getText("dialog.about.credits.translations") + " : "),
@@ -210,13 +212,13 @@ public class AboutScreen extends GenericFunction
 			new JLabel(I18nManager.getText("dialog.about.credits.devtools") + " : "),
 			0, 8);
 		addToGridBagPanel(creditsPanel, gridBag, constraints,
-			new JLabel("Debian Linux, Sun Java, Eclipse, Svn, Gimp, Inkscape"),
+			new JLabel("Debian Linux, Sun Java, OpenJDK, Eclipse, Svn, Gimp, Inkscape"),
 			1, 8);
 		addToGridBagPanel(creditsPanel, gridBag, constraints,
 			new JLabel(I18nManager.getText("dialog.about.credits.othertools") + " : "),
 			0, 9);
 		addToGridBagPanel(creditsPanel, gridBag, constraints,
-			new JLabel("Openstreetmap, Povray, Exiftool, Google Earth, Gpsbabel, Gnuplot"),
+			new JLabel("Openstreetmap, Povray, Exiftool, Gpsbabel, Gnuplot"),
 			1, 9);
 		addToGridBagPanel(creditsPanel, gridBag, constraints,
 			new JLabel(I18nManager.getText("dialog.about.credits.thanks") + " : "),
@@ -380,7 +382,8 @@ public class AboutScreen extends GenericFunction
 		String yesText = I18nManager.getText("dialog.about.yes");
 		String noText = I18nManager.getText("dialog.about.no");
 		_installedLabels[0].setText(WindowFactory.isJava3dEnabled()?yesText:noText);
-		final int[] tools = {ExternalTools.TOOL_EXIFTOOL, ExternalTools.TOOL_GPSBABEL, ExternalTools.TOOL_GNUPLOT};
+		final int[] tools = {ExternalTools.TOOL_EXIFTOOL, ExternalTools.TOOL_GPSBABEL,
+			ExternalTools.TOOL_GNUPLOT, ExternalTools.TOOL_XERCES};
 		for (int i=0; i<tools.length; i++) {
 			_installedLabels[i+1].setText(ExternalTools.isToolInstalled(tools[i])?yesText:noText);
 		}
diff --git a/tim/prune/function/AddMapSourceDialog.java b/tim/prune/function/AddMapSourceDialog.java
index 52a8874..9207ac5 100644
--- a/tim/prune/function/AddMapSourceDialog.java
+++ b/tim/prune/function/AddMapSourceDialog.java
@@ -1,12 +1,10 @@
 package tim.prune.function;
 
 import java.awt.BorderLayout;
-import java.awt.CardLayout;
 import java.awt.Component;
 import java.awt.FlowLayout;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
-import java.awt.GridLayout;
 import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -24,7 +22,6 @@ import javax.swing.JRadioButton;
 import javax.swing.JTextField;
 
 import tim.prune.I18nManager;
-import tim.prune.gui.map.CloudmadeMapSource;
 import tim.prune.gui.map.MapSource;
 import tim.prune.gui.map.MapSourceLibrary;
 import tim.prune.gui.map.OsmMapSource;
@@ -36,18 +33,12 @@ public class AddMapSourceDialog
 {
 	private SetMapBgFunction _parent = null;
 	private JDialog _addDialog = null;
-	private JRadioButton[] _sourceTypeRadios = null;
-	private JPanel _cards = null;
 	private MapSource _originalSource = null;
 	// controls for osm panel
 	private JTextField _oNameField = null;
 	private JTextField _baseUrlField = null, _topUrlField = null;
 	private JRadioButton[] _baseTypeRadios = null, _topTypeRadios = null;
-	private JComboBox _oZoomCombo = null;
-	// controls for cloudmade panel
-	private JTextField _cNameField = null;
-	private JTextField _cStyleField = null;
-	private JComboBox _cZoomCombo = null;
+	private JComboBox<Integer> _oZoomCombo = null;
 	private JButton _okButton = null;
 
 	/** array of file types */
@@ -76,31 +67,9 @@ public class AddMapSourceDialog
 	{
 		JPanel dialogPanel = new JPanel();
 		dialogPanel.setLayout(new BorderLayout());
-		// Top panel with two radio buttons to select source type
-		JPanel radioPanel = new JPanel();
-		ButtonGroup radioGroup = new ButtonGroup();
-		radioPanel.setLayout(new GridLayout(1, 0));
-		_sourceTypeRadios = new JRadioButton[2];
-		_sourceTypeRadios[0] = new JRadioButton("Openstreetmap");
-		radioGroup.add(_sourceTypeRadios[0]);
-		radioPanel.add(_sourceTypeRadios[0]);
-		_sourceTypeRadios[1] = new JRadioButton("Cloudmade");
-		radioGroup.add(_sourceTypeRadios[1]);
-		radioPanel.add(_sourceTypeRadios[1]);
-		_sourceTypeRadios[0].setSelected(true);
-		// listener for clicks on type radios
-		ActionListener typeListener = new ActionListener() {
-			public void actionPerformed(ActionEvent arg0) {
-				onRadioClicked();
-			}
-		};
-		_sourceTypeRadios[0].addActionListener(typeListener);
-		_sourceTypeRadios[1].addActionListener(typeListener);
-		radioPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
-		dialogPanel.add(radioPanel, BorderLayout.NORTH);
+		// Top panel with spacer
+		dialogPanel.add(new JLabel(" "), BorderLayout.NORTH);
 
-		_cards = new JPanel();
-		_cards.setLayout(new CardLayout());
 		// listener
 		KeyAdapter keyListener = new KeyAdapter() {
 			public void keyReleased(KeyEvent e) {
@@ -149,7 +118,7 @@ public class AddMapSourceDialog
 		c.gridx = 1; c.weightx = 1.0;
 		gbPanel.add(_baseUrlField, c);
 		_baseTypeRadios = new JRadioButton[3];
-		radioGroup = new ButtonGroup();
+		ButtonGroup radioGroup = new ButtonGroup();
 		for (int i=0; i<3; i++)
 		{
 			_baseTypeRadios[i] = new JRadioButton(FILE_TYPES[i]);
@@ -181,44 +150,20 @@ public class AddMapSourceDialog
 		// Max zoom
 		c.gridx = 0; c.gridy = 3;
 		gbPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.maxzoom")), c);
-		_oZoomCombo = new JComboBox();
+		_oZoomCombo = new JComboBox<Integer>();
 		for (int i=10; i<=20; i++) {
-			_oZoomCombo.addItem("" + 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);
-		_cards.add(osmPanel, "card1");
 
-		// Panel for cloudmade source
-		JPanel cloudPanel = new JPanel();
-		cloudPanel.setBorder(BorderFactory.createEmptyBorder(6, 3, 4, 3));
-		// Use a gridlayout inside a borderlayout to avoid stretching
-		cloudPanel.setLayout(new BorderLayout());
-		JPanel cloudGridPanel = new JPanel();
-		cloudGridPanel.setLayout(new GridLayout(0, 2, 5, 5));
-		cloudGridPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.sourcename")));
-		_cNameField = new JTextField(18);
-		_cNameField.addKeyListener(keyListener);
-		cloudGridPanel.add(_cNameField);
-		cloudGridPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.cloudstyle")));
-		_cStyleField = new JTextField(18);
-		_cStyleField.addKeyListener(keyListener);
-		cloudGridPanel.add(_cStyleField);
-		cloudGridPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.maxzoom")));
-		_cZoomCombo = new JComboBox();
-		for (int i=10; i<=20; i++) {
-			_cZoomCombo.addItem("" + i);
-		}
-		cloudGridPanel.add(_cZoomCombo);
-		cloudPanel.add(cloudGridPanel, BorderLayout.NORTH);
-		_cards.add(cloudPanel, "card2");
 		// cards
 		JPanel holderPanel = new JPanel();
 		holderPanel.setLayout(new BorderLayout());
-		holderPanel.add(_cards, BorderLayout.NORTH);
+		holderPanel.add(osmPanel, BorderLayout.NORTH);
 		dialogPanel.add(holderPanel, BorderLayout.CENTER);
 
 		// button panel at bottom
@@ -267,13 +212,7 @@ public class AddMapSourceDialog
 		_topUrlField.setText("");
 		_topTypeRadios[0].setSelected(true);
 		_oZoomCombo.setSelectedIndex(8);
-		_cNameField.setText("");
-		_cStyleField.setText("");
-		_cZoomCombo.setSelectedIndex(8);
 		_okButton.setEnabled(false);
-		for (int i=0; i<2; i++) {
-			_sourceTypeRadios[i].setEnabled(true);
-		}
 		_addDialog.setVisible(true);
 	}
 
@@ -287,67 +226,32 @@ public class AddMapSourceDialog
 			clearAllFields();
 			return;
 		}
-		boolean sourceFound = false;
-		// See if it's a cloudmade source
+
+		// See if it's an osm source
 		try
 		{
-			CloudmadeMapSource cloudSource = (CloudmadeMapSource) _originalSource;
-			sourceFound = true;
-			_cNameField.setText(cloudSource.getName());
-			_cStyleField.setText(cloudSource.getStyle());
-			_cZoomCombo.setSelectedIndex(getZoomIndex(cloudSource.getMaxZoomLevel()));
-			_sourceTypeRadios[1].setSelected(true);
+			OsmMapSource osmSource = (OsmMapSource) _originalSource;
+			_oNameField.setText(osmSource.getName());
+			_baseUrlField.setText(osmSource.getBaseUrl(0));
+			int baseType = getBaseType(osmSource.getFileExtension(0));
+			_baseTypeRadios[baseType].setSelected(true);
+			_topUrlField.setText(osmSource.getNumLayers()==0?"":osmSource.getBaseUrl(1));
+			int topType = getBaseType(osmSource.getFileExtension(1));
+			_topTypeRadios[topType].setSelected(true);
+			_oZoomCombo.setSelectedIndex(getZoomIndex(osmSource.getMaxZoomLevel()));
 		}
 		catch (ClassCastException cce) {} // ignore, sourceFound flag stays false
 
-		// See if it's an osm source
-		if (!sourceFound)
-		{
-			try
-			{
-				OsmMapSource osmSource = (OsmMapSource) _originalSource;
-				sourceFound = true;
-				_oNameField.setText(osmSource.getName());
-				_baseUrlField.setText(osmSource.getBaseUrl(0));
-				int baseType = getBaseType(osmSource.getFileExtension(0));
-				_baseTypeRadios[baseType].setSelected(true);
-				_topUrlField.setText(osmSource.getNumLayers()==0?"":osmSource.getBaseUrl(1));
-				int topType = getBaseType(osmSource.getFileExtension(1));
-				_topTypeRadios[topType].setSelected(true);
-				_oZoomCombo.setSelectedIndex(getZoomIndex(osmSource.getMaxZoomLevel()));
-				_sourceTypeRadios[0].setSelected(true);
-			}
-			catch (ClassCastException cce) {} // ignore, sourceFound flag stays false
-		}
-		for (int i=0; i<2; i++) {
-			_sourceTypeRadios[i].setEnabled(false);
-		}
-		onRadioClicked();
 		_okButton.setEnabled(false);
 		_addDialog.setVisible(true);
 	}
 
-
-	/**
-	 * React to one of the type radio buttons being clicked
-	 */
-	private void onRadioClicked()
-	{
-		CardLayout cl = (CardLayout) _cards.getLayout();
-		if (_sourceTypeRadios[0].isSelected()) {cl.first(_cards);}
-		else {cl.last(_cards);}
-		enableOK();
-	}
-
 	/**
 	 * Check the currently entered details and enable the OK button if it looks OK
 	 */
 	private void enableOK()
 	{
-		boolean ok = false;
-		if (_sourceTypeRadios[0].isSelected()) {ok = isOsmPanelOk();}
-		if (_sourceTypeRadios[1].isSelected()) {ok = isCloudPanelOk();}
-		_okButton.setEnabled(ok);
+		_okButton.setEnabled(isOsmPanelOk());
 	}
 
 	/**
@@ -371,28 +275,14 @@ public class AddMapSourceDialog
 	}
 
 	/**
-	 * Check the cloudmade panel if all details are complete
-	 * @return true if details look ok
-	 */
-	private boolean isCloudPanelOk()
-	{
-		boolean ok = _cNameField.getText().trim().length() > 1;
-		int styleNum = 0;
-		try {
-			styleNum = Integer.parseInt(_cStyleField.getText());
-		}
-		catch (NumberFormatException nfe) {}
-		return (ok && styleNum > 0);
-	}
-
-	/**
 	 * Finish by adding the requested source and refreshing the parent
 	 */
 	private void finish()
 	{
 		MapSource newSource = null;
 		String origName = (_originalSource == null ? null : _originalSource.getName());
-		if (_sourceTypeRadios[0].isSelected())
+
+		if (isOsmPanelOk())
 		{
 			// Openstreetmap source
 			String sourceName = getValidSourcename(_oNameField.getText(), origName);
@@ -402,12 +292,7 @@ public class AddMapSourceDialog
 			String ext2 = getFileExtension(_topTypeRadios);
 			newSource = new OsmMapSource(sourceName, url1, ext1, url2, ext2, _oZoomCombo.getSelectedIndex()+10);
 		}
-		else if (_sourceTypeRadios[1].isSelected())
-		{
-			String sourceName = getValidSourcename(_cNameField.getText(), origName);
-			newSource = new CloudmadeMapSource(sourceName, _cStyleField.getText(),
-				_cZoomCombo.getSelectedIndex()+10);
-		}
+
 		// Add new source if ok
 		if (newSource != null)
 		{
diff --git a/tim/prune/function/DeleteFieldValues.java b/tim/prune/function/DeleteFieldValues.java
index b6c97f5..0c0e44f 100644
--- a/tim/prune/function/DeleteFieldValues.java
+++ b/tim/prune/function/DeleteFieldValues.java
@@ -33,7 +33,7 @@ import tim.prune.undo.UndoDeleteFieldValues;
 public class DeleteFieldValues extends GenericFunction
 {
 	private JDialog _dialog = null;
-	private JList _fieldList = null;
+	private JList<String> _fieldList = null;
 	private FieldListModel _listModel = null;
 	private JButton _okButton = null;
 
@@ -88,7 +88,7 @@ public class DeleteFieldValues extends GenericFunction
 		dialogPanel.setLayout(new BorderLayout());
 		dialogPanel.add(new JLabel(I18nManager.getText("dialog.deletefieldvalues.intro")), BorderLayout.NORTH);
 		// List in centre
-		_fieldList = new JList(new String[] {"First field", "Second field"});
+		_fieldList = new JList<String>(new String[] {"First field", "Second field"});
 		// These entries will be replaced by the initDialog method
 		_fieldList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 		_fieldList.addListSelectionListener(new ListSelectionListener() {
diff --git a/tim/prune/function/Export3dFunction.java b/tim/prune/function/Export3dFunction.java
index 9e92e62..d32828a 100644
--- a/tim/prune/function/Export3dFunction.java
+++ b/tim/prune/function/Export3dFunction.java
@@ -2,6 +2,8 @@ package tim.prune.function;
 
 import tim.prune.App;
 import tim.prune.GenericFunction;
+import tim.prune.threedee.ImageDefinition;
+import tim.prune.threedee.TerrainDefinition;
 
 /**
  * Abstract superclass for pov and svg export functions
@@ -10,6 +12,10 @@ public abstract class Export3dFunction extends GenericFunction
 {
 	/** altitude exaggeration factor */
 	protected double _altFactor = 5.0;
+	/** definition of terrain */
+	protected TerrainDefinition _terrainDef = null;
+	/** definition of base image */
+	protected ImageDefinition _imageDef = null;
 
 	/**
 	 * Required constructor
@@ -36,4 +42,20 @@ public abstract class Export3dFunction extends GenericFunction
 			_altFactor = inFactor;
 		}
 	}
+
+	/**
+	 * @param inDefinition terrain definition, or null
+	 */
+	public void setTerrainDefinition(TerrainDefinition inDefinition)
+	{
+		_terrainDef = inDefinition;
+	}
+
+	/**
+	 * @param inDefinition image definition, or null
+	 */
+	public void setImageDefinition(ImageDefinition inDefinition)
+	{
+		_imageDef = inDefinition;
+	}
 }
diff --git a/tim/prune/function/FieldListModel.java b/tim/prune/function/FieldListModel.java
index 89a721c..b4589d8 100644
--- a/tim/prune/function/FieldListModel.java
+++ b/tim/prune/function/FieldListModel.java
@@ -7,7 +7,7 @@ import tim.prune.data.Field;
 /**
  * Class to act as a list model for the delete field values function
  */
-public class FieldListModel extends AbstractListModel
+public class FieldListModel extends AbstractListModel<String>
 {
 	/** ArrayList containing fields */
 	private ArrayList<Field> _fields = new ArrayList<Field>();
@@ -34,7 +34,7 @@ public class FieldListModel extends AbstractListModel
 	 * @param inRow row number
 	 * @return String for specified row
 	 */
-	public Object getElementAt(int inRow)
+	public String getElementAt(int inRow)
 	{
 		if (inRow < 0 || inRow >= getSize()) {return null;}
 		return _fields.get(inRow).getName();
diff --git a/tim/prune/function/FindWaypoint.java b/tim/prune/function/FindWaypoint.java
index 8b6beea..ac0bd37 100644
--- a/tim/prune/function/FindWaypoint.java
+++ b/tim/prune/function/FindWaypoint.java
@@ -34,7 +34,7 @@ public class FindWaypoint extends GenericFunction
 	private WaypointNameMatcher _nameMatcher = null;
 	private JDialog _dialog = null;
 	private JTextField _searchField = null;
-	private JList _pointList = null;
+	private JList<String> _pointList = null;
 	private JButton _okButton = null;
 
 
@@ -108,7 +108,7 @@ public class FindWaypoint extends GenericFunction
 
 		// middle panel with list
 		_nameMatcher = new WaypointNameMatcher();
-		_pointList = new JList(_nameMatcher);
+		_pointList = new JList<String>(_nameMatcher);
 		_pointList.addListSelectionListener(new ListSelectionListener() {
 			public void valueChanged(ListSelectionEvent e)
 			{
diff --git a/tim/prune/function/FullRangeDetails.java b/tim/prune/function/FullRangeDetails.java
index d9dbb70..07ef39e 100644
--- a/tim/prune/function/FullRangeDetails.java
+++ b/tim/prune/function/FullRangeDetails.java
@@ -251,7 +251,8 @@ public class FullRangeDetails extends GenericFunction
 		final boolean isMultiSegments = (stats.getNumSegments() > 1);
 		// Set visibility of third column accordingly
 		_movingDistanceLabel.setVisible(isMultiSegments);
-		_movingDurationLabel.setVisible(isMultiSegments);
+		_movingDurationLabel.setVisible(isMultiSegments || stats.getTimestampsOutOfSequence());
+		// FIXME: What to show if timestamps are out of sequence? Warning message?
 		_movingClimbLabel.setVisible(isMultiSegments);
 		_movingDescentLabel.setVisible(isMultiSegments);
 		_movingSpeedLabel.setVisible(isMultiSegments);
diff --git a/tim/prune/function/GetWikipediaXmlHandler.java b/tim/prune/function/GetWikipediaXmlHandler.java
index d9a3b74..f70e5f7 100644
--- a/tim/prune/function/GetWikipediaXmlHandler.java
+++ b/tim/prune/function/GetWikipediaXmlHandler.java
@@ -9,7 +9,7 @@ import org.xml.sax.helpers.DefaultHandler;
 import tim.prune.function.gpsies.GpsiesTrack;
 
 /**
- * XML handler for dealing with XML returned from gpsies.com
+ * XML handler for dealing with XML returned from the geonames api
  */
 public class GetWikipediaXmlHandler extends DefaultHandler
 {
@@ -71,7 +71,7 @@ public class GetWikipediaXmlHandler extends DefaultHandler
 			catch (NumberFormatException nfe) {}
 		}
 		else if (inTagName.equals("wikipediaUrl")) {
-			_track.setWebUrl(_value);
+			_track.setWebUrl(_value.replaceFirst("http://", "https://"));
 		}
 		super.endElement(inUri, inLocalName, inTagName);
 	}
diff --git a/tim/prune/function/MapSourceListModel.java b/tim/prune/function/MapSourceListModel.java
index 4548cc4..489f95d 100644
--- a/tim/prune/function/MapSourceListModel.java
+++ b/tim/prune/function/MapSourceListModel.java
@@ -8,7 +8,7 @@ import tim.prune.gui.map.MapSourceLibrary;
 /**
  * Class to act as list model for the map source list
  */
-public class MapSourceListModel extends AbstractListModel
+public class MapSourceListModel extends AbstractListModel<String>
 {
 	/**
 	 * @see javax.swing.ListModel#getSize()
@@ -21,7 +21,7 @@ public class MapSourceListModel extends AbstractListModel
 	/**
 	 * @see javax.swing.ListModel#getElementAt(int)
 	 */
-	public Object getElementAt(int inIndex)
+	public String getElementAt(int inIndex)
 	{
 		if (inIndex < 0 || inIndex >= getSize()) return "";
 		return MapSourceLibrary.getSource(inIndex).getName();
diff --git a/tim/prune/function/PasteCoordinates.java b/tim/prune/function/PasteCoordinates.java
index 0228014..c9b2ad1 100644
--- a/tim/prune/function/PasteCoordinates.java
+++ b/tim/prune/function/PasteCoordinates.java
@@ -43,7 +43,7 @@ public class PasteCoordinates extends GenericFunction
 	private JTextField _nameField = null;
 	private JTextField _coordField = null;
 	private JButton _okButton = null;
-	private JComboBox _altUnitsDropDown;
+	private JComboBox<String> _altUnitsDropDown;
 
 
 	/**
@@ -122,7 +122,7 @@ public class PasteCoordinates extends GenericFunction
 		formatLabel.setHorizontalAlignment(SwingConstants.RIGHT);
 		grid.add(formatLabel);
 		final String[] altunits = {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")};
-		_altUnitsDropDown = new JComboBox(altunits);
+		_altUnitsDropDown = new JComboBox<String>(altunits);
 		grid.add(_altUnitsDropDown);
 		// Waypoint name
 		JLabel nameLabel = new JLabel(I18nManager.getText("dialog.pointnameedit.name"));
diff --git a/tim/prune/function/RearrangeWaypointsFunction.java b/tim/prune/function/RearrangeWaypointsFunction.java
index 9007391..e04aea0 100644
--- a/tim/prune/function/RearrangeWaypointsFunction.java
+++ b/tim/prune/function/RearrangeWaypointsFunction.java
@@ -64,6 +64,7 @@ public class RearrangeWaypointsFunction extends GenericFunction
 		}
 		if (success)
 		{
+			_app.getTrackInfo().getSelection().clearAll(); // clear selected point and range
 			_app.completeFunction(undo, I18nManager.getText("confirm.rearrangewaypoints"));
 		}
 		else
diff --git a/tim/prune/function/SelectTracksFunction.java b/tim/prune/function/SelectTracksFunction.java
index f2fe23b..fa06dae 100644
--- a/tim/prune/function/SelectTracksFunction.java
+++ b/tim/prune/function/SelectTracksFunction.java
@@ -32,7 +32,7 @@ public class SelectTracksFunction extends GenericFunction
 	private SourceInfo _sourceInfo = null;
 	private TrackNameList _trackNameList = null;
 	private JDialog _dialog = null;
-	private JList _trackList = null;
+	private JList<String> _trackList = null;
 
 	/**
 	 * Constructor
@@ -82,7 +82,7 @@ public class SelectTracksFunction extends GenericFunction
 			}
 			names[i] = name + " (" + _trackNameList.getNumPointsInTrack(i) + ")";
 		}
-		_trackList = new JList(names);
+		_trackList = new JList<String>(names);
 		_trackList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
 		mainPanel.add(new JScrollPane(_trackList), BorderLayout.CENTER);
 		// select all button
diff --git a/tim/prune/function/SetLanguage.java b/tim/prune/function/SetLanguage.java
index 3962787..27ec94e 100644
--- a/tim/prune/function/SetLanguage.java
+++ b/tim/prune/function/SetLanguage.java
@@ -36,7 +36,7 @@ import tim.prune.load.GenericFileFilter;
 public class SetLanguage extends GenericFunction
 {
 	private JDialog _dialog = null;
-	private JComboBox _languageDropDown = null;
+	private JComboBox<String> _languageDropDown = null;
 	private JTextField _langFileBox = null;
 	private int _startIndex = 0;
 
@@ -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",
-		"afrikaans", "rom\u00E2n\u0103"
+		"afrikaans", "rom\u00E2n\u0103", "\u0443\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430 \u043c\u043e\u0432\u0430 (ukrainian)"
 	};
 	/** 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", "af", "ro"
+		"nl", "pl", "pt", "ru", "zh", "ja", "ko", "de_ch", "tr", "af", "ro", "uk"
 	};
 
 
@@ -99,7 +99,7 @@ public class SetLanguage extends GenericFunction
 		builtinPanel.setLayout(new BoxLayout(builtinPanel, BoxLayout.X_AXIS));
 		builtinPanel.add(new JLabel(I18nManager.getText("dialog.setlanguage.language") + " : "));
 		// Language dropdown
-		_languageDropDown = new JComboBox(LANGUAGE_NAMES);
+		_languageDropDown = new JComboBox<String>(LANGUAGE_NAMES);
 		builtinPanel.add(_languageDropDown);
 		builtinPanel.add(Box.createHorizontalGlue());
 		JButton selectLangButton = new JButton(I18nManager.getText("button.select"));
diff --git a/tim/prune/function/SetMapBgFunction.java b/tim/prune/function/SetMapBgFunction.java
index a42b789..a875d00 100644
--- a/tim/prune/function/SetMapBgFunction.java
+++ b/tim/prune/function/SetMapBgFunction.java
@@ -37,7 +37,7 @@ import tim.prune.gui.map.MapSourceLibrary;
 public class SetMapBgFunction extends GenericFunction
 {
 	private JDialog _dialog = null;
-	private JList _list = null;
+	private JList<String> _list = null;
 	private MapSourceListModel _listModel = null;
 	private String _initialSource = null;
 	private JButton _okButton = null, _cancelButton = null;
@@ -96,7 +96,7 @@ public class SetMapBgFunction extends GenericFunction
 		dialogPanel.add(introLabel, BorderLayout.NORTH);
 		// list box
 		_listModel = new MapSourceListModel();
-		_list = new JList(_listModel);
+		_list = new JList<String>(_listModel);
 		_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 		dialogPanel.add(new JScrollPane(_list), BorderLayout.CENTER);
 		_list.addListSelectionListener(new ListSelectionListener() {
diff --git a/tim/prune/function/ShowThreeDFunction.java b/tim/prune/function/ShowThreeDFunction.java
index 8ba0f41..40c4e5a 100644
--- a/tim/prune/function/ShowThreeDFunction.java
+++ b/tim/prune/function/ShowThreeDFunction.java
@@ -1,19 +1,45 @@
 package tim.prune.function;
 
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
 import javax.swing.JOptionPane;
+import javax.swing.JPanel;
 
 import tim.prune.App;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.gui.BaseImageDefinitionPanel;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.TerrainDefinitionPanel;
+import tim.prune.threedee.TerrainDefinition;
 import tim.prune.threedee.ThreeDException;
 import tim.prune.threedee.ThreeDWindow;
 import tim.prune.threedee.WindowFactory;
+import tim.prune.tips.TipManager;
 
 /**
  * Class to show the 3d window
  */
 public class ShowThreeDFunction extends GenericFunction
 {
+	/** Dialog for input parameters */
+	private JDialog _dialog = null;
+	/** Field for altitude exaggeration value */
+	private DecimalNumberField _exaggField = null;
+	/** Component for defining the base image */
+	private BaseImageDefinitionPanel _baseImagePanel = null;
+	/** Component for defining the terrain */
+	private TerrainDefinitionPanel _terrainPanel = null;
+
 	/**
 	 * Constructor
 	 * @param inApp app object
@@ -31,7 +57,7 @@ public class ShowThreeDFunction extends GenericFunction
 	}
 
 	/**
-	 * Show the help screen
+	 * Begin the function
 	 */
 	public void begin()
 	{
@@ -43,10 +69,107 @@ public class ShowThreeDFunction extends GenericFunction
 		}
 		else
 		{
+			// See if the track has any altitudes at all - if not, show a tip to use SRTM
+			if (!_app.getTrackInfo().getTrack().hasAltitudeData()) {
+				_app.showTip(TipManager.Tip_UseSrtmFor3d);
+			}
+			// Show a dialog to get the parameters
+			if (_dialog == null)
+			{
+				_dialog = new JDialog(_app.getFrame(), I18nManager.getText(getNameKey()), true);
+				_dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+				_dialog.getContentPane().add(makeDialogComponents());
+				_dialog.pack();
+			}
+			final int exaggFactor = Config.getConfigInt(Config.KEY_HEIGHT_EXAGGERATION);
+			if (exaggFactor > 0) {
+				_exaggField.setValue(exaggFactor / 100.0);
+			}
+			_baseImagePanel.updateBaseImageDetails();
+			_dialog.setLocationRelativeTo(_app.getFrame());
+			_dialog.setVisible(true);
+		}
+	}
+
+	/**
+	 * Make the dialog components to select the options
+	 * @return JPanel holding the gui elements
+	 */
+	private JPanel makeDialogComponents()
+	{
+		JPanel mainPanel = new JPanel();
+		mainPanel.setLayout(new BorderLayout(4, 4));
+
+		JPanel innerPanel = new JPanel();
+		innerPanel.setLayout(new BoxLayout(innerPanel, BoxLayout.Y_AXIS));
+		// Panel for altitude exaggeration
+		JPanel exaggPanel = new JPanel();
+		exaggPanel.setLayout(new FlowLayout());
+		exaggPanel.add(new JLabel(I18nManager.getText("dialog.3d.altitudefactor") + ": "));
+		_exaggField = new DecimalNumberField(); // don't allow negative numbers
+		_exaggField.setText("5.0");
+		exaggPanel.add(_exaggField);
+		innerPanel.add(exaggPanel);
+		innerPanel.add(Box.createVerticalStrut(4));
+
+		// Panel for terrain
+		_terrainPanel = new TerrainDefinitionPanel();
+		innerPanel.add(_terrainPanel);
+		mainPanel.add(innerPanel, BorderLayout.NORTH);
+		innerPanel.add(Box.createVerticalStrut(4));
+
+		// Panel for base image (null because we don't need callback)
+		_baseImagePanel = new BaseImageDefinitionPanel(null, _dialog, _app.getTrackInfo().getTrack());
+		innerPanel.add(_baseImagePanel);
+
+		// OK, Cancel buttons
+		JPanel buttonPanel = new JPanel();
+		buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+		JButton okButton = new JButton(I18nManager.getText("button.ok"));
+		okButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e)
+			{
+				_dialog.dispose();
+				new Thread(new Runnable() {
+					public void run() {
+						finish();  // needs to be in separate thread
+					}
+				}).start();
+			}
+		});
+		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);
+		mainPanel.add(buttonPanel, BorderLayout.SOUTH);
+
+		return mainPanel;
+	}
+
+	/**
+	 * All parameters have been selected in the input dialog, now we can go to the 3d window
+	 */
+	private void finish()
+	{
+		// Store exaggeration factor in config
+		Config.setConfigInt(Config.KEY_HEIGHT_EXAGGERATION, (int) (_exaggField.getValue() * 100));
+		ThreeDWindow window = WindowFactory.getWindow(_parentFrame);
+		if (window != null)
+		{
 			try
 			{
-				// Pass the track object and show the window
+				// Pass the parameters to use and show the window
 				window.setTrack(_app.getTrackInfo().getTrack());
+				window.setAltitudeFactor(_exaggField.getValue());
+				// Also pass the base image parameters from input dialog
+				window.setBaseImageParameters(_baseImagePanel.getImageDefinition());
+				window.setTerrainParameters(new TerrainDefinition(_terrainPanel.getUseTerrain(), _terrainPanel.getGridSize()));
+				window.setDataStatus(_app.getCurrentDataStatus());
 				window.show();
 			}
 			catch (ThreeDException e)
diff --git a/tim/prune/function/browser/BrowserLauncher.java b/tim/prune/function/browser/BrowserLauncher.java
index d23a2f7..4ba4b8d 100644
--- a/tim/prune/function/browser/BrowserLauncher.java
+++ b/tim/prune/function/browser/BrowserLauncher.java
@@ -26,12 +26,15 @@ public abstract class BrowserLauncher
 		{
 			// which exists, so try browsers in turn
 			String[] browsersToTry = {"firefox", "iceweasel", "konqueror", "opera", "epiphany",
-				"mozilla", "safari", "google-chrome", "lynx"};
+				"mozilla", "chromium", "midori", "safari", "lynx"};
 			String browserFound = null;
-			for (int i=0; i<browsersToTry.length && browserFound == null; i++)
+			for (String browser : browsersToTry)
 			{
-				if (commandExists(browsersToTry[i]))
-					browserFound = browsersToTry[i];
+				if (commandExists(browser))
+				{
+					browserFound = browser;
+					break;
+				}
 			}
 			if (browserFound != null) {
 				_browserCommand = new String[] {browserFound, null};
diff --git a/tim/prune/function/cache/ManageCacheFunction.java b/tim/prune/function/cache/ManageCacheFunction.java
index 04e3cc1..59ef13a 100644
--- a/tim/prune/function/cache/ManageCacheFunction.java
+++ b/tim/prune/function/cache/ManageCacheFunction.java
@@ -365,8 +365,7 @@ public class ManageCacheFunction extends GenericFunction implements Runnable
 		if (totalDeleted > 0)
 		{
 			// Show confirmation message
-			JOptionPane.showMessageDialog(_dialog, I18nManager.getText("dialog.diskcache.deleted1")
-				+ " " + totalDeleted + " " + I18nManager.getText("dialog.diskcache.deleted2"),
+			JOptionPane.showMessageDialog(_dialog, I18nManager.getTextWithNumber("dialog.diskcache.deleted", totalDeleted),
 				I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
 			// reload model
 			_cards.first(_cardPanel);
diff --git a/tim/prune/function/compress/CompressTrackFunction.java b/tim/prune/function/compress/CompressTrackFunction.java
index 5aa76aa..ce0203c 100644
--- a/tim/prune/function/compress/CompressTrackFunction.java
+++ b/tim/prune/function/compress/CompressTrackFunction.java
@@ -31,6 +31,8 @@ public class CompressTrackFunction extends GenericFunction
 	private JButton _okButton = null;
 	private CompressionAlgorithm[] _algorithms = null;
 	private SummaryLabel _summaryLabel = null;
+	/** flag to remember whether the automatic deletion has been set to always */
+	private boolean _automaticallyDelete = false;
 
 
 	/**
@@ -183,12 +185,28 @@ public class CompressTrackFunction extends GenericFunction
 		UpdateMessageBroker.informSubscribers();
 		_dialog.dispose();
 		// Show confirmation dialog with OK button (not status bar message)
-		if (numMarked > 0) {
-			JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.compress.confirm1")
-				+ " " + numMarked + " " + I18nManager.getText("dialog.compress.confirm2"),
-				I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
+		if (numMarked > 0)
+		{
+			// Allow calling of delete function with one click
+			final String[] buttonTexts = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"),
+				I18nManager.getText("button.always")};
+			int answer = _automaticallyDelete ? JOptionPane.YES_OPTION :
+				JOptionPane.showOptionDialog(_parentFrame,
+				I18nManager.getTextWithNumber("dialog.compress.confirm", numMarked),
+				I18nManager.getText(getNameKey()), JOptionPane.YES_NO_CANCEL_OPTION,
+				JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]);
+			if (answer == JOptionPane.CANCEL_OPTION) {_automaticallyDelete = true;} // "always" is third option
+			if (_automaticallyDelete || answer == JOptionPane.YES_OPTION)
+			{
+				new Thread(new Runnable() {
+					public void run() {
+						_app.finishCompressTrack();
+					}
+				}).start();
+			}
 		}
-		else {
+		else
+		{
 			JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.compress.confirmnone"),
 				I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
 		}
diff --git a/tim/prune/function/compress/DouglasPeuckerAlgorithm.java b/tim/prune/function/compress/DouglasPeuckerAlgorithm.java
index 42d7ece..046e72e 100644
--- a/tim/prune/function/compress/DouglasPeuckerAlgorithm.java
+++ b/tim/prune/function/compress/DouglasPeuckerAlgorithm.java
@@ -107,7 +107,7 @@ public class DouglasPeuckerAlgorithm extends SingleParameterAlgorithm
 	private void compressSegment(int[] inFlags, int inSegStart, int inSegEnd,
 		double inThreshold)
 	{
-		//System.out.println("Compress segment " + inSegStart + "-" + inSegEnd);
+		// System.out.println("Compress segment " + inSegStart + "-" + inSegEnd);
 		final int numPoints = inSegEnd - inSegStart + 1;
 		if (numPoints < 3) {return;} // segment too short to compress
 		// Calculate parameters of straight line between first and last
@@ -118,6 +118,17 @@ public class DouglasPeuckerAlgorithm extends SingleParameterAlgorithm
 		// create unit vector perpendicular to AB
 		final double distAB = ab.len();
 		XYpoint perpendicular = new XYpoint(ab.y/distAB, -ab.x/distAB);
+		// Check whether distAB is 0.0 - if so, find furthest point from startxy and compress from start to here and here to end
+		if (distAB <= 0.0)
+		{
+			final int furthestIndex = getFurthestPointIndex(inSegStart, inSegEnd);
+			if (furthestIndex > inSegStart)
+			{
+				compressSegment(inFlags, inSegStart, furthestIndex, inThreshold);
+				compressSegment(inFlags, furthestIndex, inSegEnd, inThreshold);
+			}
+			return;
+		}
 
 		double maxDist = -1.0, dist = -1.0;
 		int furthestIndex = -1;
@@ -172,4 +183,35 @@ public class DouglasPeuckerAlgorithm extends SingleParameterAlgorithm
 	{
 		return "dialog.compress.douglaspeucker.title";
 	}
+
+	/**
+	 * Find the index of the point furthest away from the start and end points
+	 * @param inStartIndex start index of segment to check
+	 * @param inEndIndex end index of segment to check
+	 * @return index of furthest point, or -1 if none found
+	 */
+	private int getFurthestPointIndex(int inStartIndex, int inEndIndex)
+	{
+		int furthestIndex = -1;
+		if (inStartIndex >= 0 && inEndIndex > inStartIndex)
+		{
+			final DataPoint startPoint = _track.getPoint(inStartIndex);
+			double maxDist = 0.0;
+			// Loop over points between start and end
+			for (int i=inStartIndex+1; i<inEndIndex; i++)
+			{
+				DataPoint p = _track.getPoint(i);
+				if (!p.isWaypoint())
+				{
+					double distFromStart = DataPoint.calculateRadiansBetween(startPoint, p);
+					if (distFromStart > maxDist)
+					{
+						furthestIndex = i;
+						maxDist = distFromStart;
+					}
+				}
+			}
+		}
+		return furthestIndex;
+	}
 }
diff --git a/tim/prune/function/compress/MarkPointsInRectangleFunction.java b/tim/prune/function/compress/MarkPointsInRectangleFunction.java
index 73c867f..f6ffe20 100644
--- a/tim/prune/function/compress/MarkPointsInRectangleFunction.java
+++ b/tim/prune/function/compress/MarkPointsInRectangleFunction.java
@@ -17,6 +17,8 @@ public class MarkPointsInRectangleFunction extends GenericFunction
 	private double _minLat = 0.0, _maxLat = 0.0;
 	/** Minimum and maximum longitude values of rectangle */
 	private double _minLon = 0.0, _maxLon = 0.0;
+	/** flag to remember whether the automatic deletion has been set to always */
+	private boolean _automaticallyDelete = false;
 
 
 	/**
@@ -28,6 +30,11 @@ public class MarkPointsInRectangleFunction extends GenericFunction
 		super(inApp);
 	}
 
+	/** @return name key */
+	public String getNameKey() {
+		return "menu.track.markrectangle";
+	}
+
 	/**
 	 * Set the coordinates of the rectangle
 	 * @param inLon1 first longitude value
@@ -89,15 +96,25 @@ public class MarkPointsInRectangleFunction extends GenericFunction
 		// Inform subscribers to update display
 		UpdateMessageBroker.informSubscribers();
 		// Confirm message showing how many marked
-		if (numMarked > 0) {
-			JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.compress.confirm1")
-				+ " " + numMarked + " " + I18nManager.getText("dialog.compress.confirm2"),
-				I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
+		if (numMarked > 0)
+		{
+			// Allow calling of delete function with one click
+			final String[] buttonTexts = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"),
+				I18nManager.getText("button.always")};
+			int answer = _automaticallyDelete ? JOptionPane.YES_OPTION :
+				JOptionPane.showOptionDialog(_parentFrame,
+				I18nManager.getTextWithNumber("dialog.compress.confirm", numMarked),
+				I18nManager.getText(getNameKey()), JOptionPane.YES_NO_CANCEL_OPTION,
+				JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]);
+			if (answer == JOptionPane.CANCEL_OPTION) {_automaticallyDelete = true;} // "always" is third option
+			if (_automaticallyDelete || answer == JOptionPane.YES_OPTION)
+			{
+				new Thread(new Runnable() {
+					public void run() {
+						_app.finishCompressTrack();
+					}
+				}).start();
+			}
 		}
 	}
-
-	/** @return name key */
-	public String getNameKey() {
-		return "menu.track.markrectangle";
-	}
 }
diff --git a/tim/prune/function/estimate/EstimateTime.java b/tim/prune/function/estimate/EstimateTime.java
index 7a502e4..ef69793 100644
--- a/tim/prune/function/estimate/EstimateTime.java
+++ b/tim/prune/function/estimate/EstimateTime.java
@@ -29,6 +29,7 @@ import tim.prune.data.Unit;
 import tim.prune.gui.DecimalNumberField;
 import tim.prune.gui.DisplayUtils;
 import tim.prune.gui.GuiGridLayout;
+import tim.prune.tips.TipManager;
 
 /**
  * Class to calculate and show the results of estimating (hike) time for the current range
@@ -86,7 +87,8 @@ public class EstimateTime extends GenericFunction
 		}
 		if (_dialog == null)
 		{
-			// TODO: Check whether params are at default, show tip message if unaltered?
+			// First time in, check whether params are at default, show tip message if unaltered
+			showTip();
 			_dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
 			_dialog.setLocationRelativeTo(_parentFrame);
 			_dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
@@ -360,4 +362,16 @@ public class EstimateTime extends GenericFunction
 		}
 		_dialog.dispose();
 	}
+
+	/**
+	 * Show a tip to use the learn function, if appropriate
+	 */
+	private void showTip()
+	{
+		EstimationParameters currParams = new EstimationParameters(
+			Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
+		if (currParams.sameAsDefaults()) {
+			_app.showTip(TipManager.Tip_LearnTimeParams);
+		}
+	}
 }
diff --git a/tim/prune/function/estimate/EstimationParameters.java b/tim/prune/function/estimate/EstimationParameters.java
index e78994b..1b80467 100644
--- a/tim/prune/function/estimate/EstimationParameters.java
+++ b/tim/prune/function/estimate/EstimationParameters.java
@@ -62,6 +62,19 @@ public class EstimationParameters
 	}
 
 	/**
+	 * @return true if this set of parameters is the same as the default set
+	 */
+	public boolean sameAsDefaults()
+	{
+		EstimationParameters defaultParams = new EstimationParameters();
+		return _flatMins == defaultParams._flatMins
+			&& _gentleClimbMins == defaultParams._gentleClimbMins
+			&& _steepClimbMins == defaultParams._steepClimbMins
+			&& _gentleDescentMins == defaultParams._gentleDescentMins
+			&& _steepDescentMins  == defaultParams._steepDescentMins;
+	}
+
+	/**
 	 * Populate the values from the config, which means all values are metric
 	 * @param inString semicolon-separated string of five parameters
 	 */
diff --git a/tim/prune/function/estimate/LearnParameters.java b/tim/prune/function/estimate/LearnParameters.java
index 8f4ea68..74021dd 100644
--- a/tim/prune/function/estimate/LearnParameters.java
+++ b/tim/prune/function/estimate/LearnParameters.java
@@ -110,7 +110,7 @@ public class LearnParameters extends GenericFunction implements Runnable
 			int startIndex = i * sampleSize;
 			RangeStats stats = getRangeStats(track, startIndex, startIndex + sampleSize, prevStartIndex);
 			if (stats != null && stats.getMovingDistanceKilometres() > 1.0
-				&& !stats.getTimestampsIncomplete()
+				&& !stats.getTimestampsIncomplete() && !stats.getTimestampsOutOfSequence()
 				&& stats.getTotalDurationInSeconds() > 100
 				&& stats.getStartIndex() > prevStartIndex)
 			{
diff --git a/tim/prune/function/sew/CandidateSorter.java b/tim/prune/function/sew/CandidateSorter.java
new file mode 100644
index 0000000..6736f32
--- /dev/null
+++ b/tim/prune/function/sew/CandidateSorter.java
@@ -0,0 +1,29 @@
+package tim.prune.function.sew;
+
+import java.util.Comparator;
+
+/**
+ * Class to sort the candidates for segment splitting
+ */
+public class CandidateSorter implements Comparator<SplitPoint>
+{
+	/**
+	 * Sort the objects by distance (greatest first)
+	 */
+	public int compare(SplitPoint inFirst, SplitPoint inSecond)
+	{
+		if (inFirst == null)  return 1;
+		if (inSecond == null) return -1;
+		// First, sort by distance
+		final double dist1 = inFirst.getDistanceToPrevPoint();
+		final double dist2 = inSecond.getDistanceToPrevPoint();
+		if (dist1 > dist2) {
+			return -1;
+		}
+		if (dist1 < dist2) {
+			return 1;
+		}
+		// If the distances are identical, then just sort by point index
+		return inFirst.getPointIndex() - inSecond.getPointIndex();
+	}
+}
diff --git a/tim/prune/function/sew/SegmentEnd.java b/tim/prune/function/sew/SegmentEnd.java
new file mode 100644
index 0000000..9f075f1
--- /dev/null
+++ b/tim/prune/function/sew/SegmentEnd.java
@@ -0,0 +1,197 @@
+package tim.prune.function.sew;
+
+import tim.prune.data.Coordinate;
+import tim.prune.data.DataPoint;
+
+/**
+ * Class to represent one end of a segment, including the
+ * coordinates and the other end of the segment
+ */
+public class SegmentEnd implements Comparable<SegmentEnd>
+{
+	private SegmentEnd _otherEnd = null;
+	private Coordinate _longitude = null;
+	private Coordinate _latitude  = null;
+	private int        _pointIndex = 0;
+	private boolean    _active = true;
+
+
+	/**
+	 * Constructor
+	 * @param inPoint data point
+	 * @param inIndex point index within track
+	 */
+	public SegmentEnd(DataPoint inPoint, int inIndex)
+	{
+		_longitude = inPoint.getLongitude();
+		_latitude  = inPoint.getLatitude();
+		_pointIndex = inIndex;
+		_active    = true;
+	}
+
+	/**
+	 * @param inOther other end of the segment
+	 */
+	public void setOtherEnd(SegmentEnd inOther)
+	{
+		_otherEnd = inOther;
+	}
+
+	/**
+	 * @return other end
+	 */
+	public SegmentEnd getOtherEnd()
+	{
+		return _otherEnd;
+	}
+
+	/**
+	 * @return true if this is the start of the segment
+	 */
+	public boolean isStart()
+	{
+		return _otherEnd == null || _otherEnd._pointIndex > _pointIndex;
+	}
+
+	/** @return point index */
+	public int getPointIndex() {
+		return _pointIndex;
+	}
+
+	/** @return point index of other end */
+	public int getOtherPointIndex()
+	{
+		return _otherEnd == null ? _pointIndex : _otherEnd._pointIndex;
+	}
+
+	/** @return get the earlier of the two point indices */
+	public int getEarlierIndex() {
+		return isStart() ? _pointIndex : _otherEnd._pointIndex;
+	}
+
+	/** @return get the later of the two point indices */
+	public int getLaterIndex() {
+		return isStart() ? _otherEnd._pointIndex : _pointIndex;
+	}
+
+	/**
+	 * @return earlier end of this segment
+	 */
+	public SegmentEnd getEarlierEnd() {
+		return isStart() ? this : _otherEnd;
+	}
+
+	/**
+	 * @return later end of this segment
+	 */
+	public SegmentEnd getLaterEnd() {
+		return isStart() ? _otherEnd : this;
+	}
+
+	/**
+	 * Reverse this segment, by swapping the point indices of the start and end
+	 * isStart() will thereby also be reversed for both ends
+	 */
+	public void reverseSegment()
+	{
+		if (_otherEnd != null)
+		{
+			int pointIndex = _pointIndex;
+			_pointIndex = _otherEnd._pointIndex;
+			_otherEnd._pointIndex = pointIndex;
+		}
+	}
+
+	/**
+	 * @return true if this node is still active
+	 */
+	public boolean isActive() {
+		return _active;
+	}
+
+	/**
+	 * Deactive this node, don't use it any more (it's already been merged)
+	 */
+	public void deactivate() {
+		_active = false;
+	}
+
+	/**
+	 * @param inOther other segment end
+	 * @return true if the coordinates are identical
+	 */
+	public boolean atSamePointAs(SegmentEnd inOther)
+	{
+		return inOther != null && _latitude.equals(inOther._latitude) && _longitude.equals(inOther._longitude);
+	}
+
+	/**
+	 * Compare two objects for sorting
+	 */
+	public int compareTo(SegmentEnd o)
+	{
+		if (o == null) return -1;
+		// First, sort by latitude
+		if (!_latitude.equals(o._latitude)) {
+			return (_latitude.getDouble() < o._latitude.getDouble() ? -1 : 1);
+		}
+		// Latitudes same, so sort by longitude
+		if (!_longitude.equals(o._longitude)) {
+			return (_longitude.getDouble() < o._longitude.getDouble() ? -1 : 1);
+		}
+		// Points are identical so just sort by index
+		return _pointIndex - o._pointIndex;
+	}
+
+	/**
+	 * Adjust the point index as a result of a cut/move operation on the track
+	 * @param inSegmentStart index of start of segment to be moved
+	 * @param inSegmentEnd index of end of segment to be moved
+	 * @param inMoveTo index of point before which the segment should be moved
+	 */
+	public void adjustPointIndex(int inSegmentStart, int inSegmentEnd, int inMoveTo)
+	{
+		final int segmentSize = inSegmentEnd - inSegmentStart + 1; // number of points moved
+		final boolean forwardsMove = inMoveTo > inSegmentEnd;
+		// Min and max indices of affected points (apart from segment to be moved)
+		final int minIndex = forwardsMove ? inSegmentEnd + 1 : inMoveTo;
+		final int maxIndex = forwardsMove ? inMoveTo - 1 : inSegmentStart - 1;
+		if (_pointIndex >= minIndex && _pointIndex <= maxIndex)
+		{
+			// final int origIndex = _pointIndex;
+			if (forwardsMove) {
+				_pointIndex -= segmentSize; // segment moved forwards, point indices reduced
+			}
+			else {
+				_pointIndex += segmentSize; // segment moved backwards, point indices shifted forwards
+			}
+			// System.out.println("    Need to adjust index: " + origIndex + " -> " + _pointIndex);
+		}
+		else if (_pointIndex == inSegmentStart)
+		{
+			// final int origIndex = _pointIndex;
+			if (forwardsMove) {
+				_pointIndex = inMoveTo - segmentSize;
+			}
+			else
+			{
+				// Point index moves to moveTo
+				_pointIndex = inMoveTo;
+			}
+			// System.out.println("    Need to adjust movedseg: " + origIndex + " -> " + _pointIndex);
+		}
+		else if (_pointIndex == inSegmentEnd)
+		{
+			// final int origEndIndex = _otherEnd._pointIndex;
+			if (forwardsMove) {
+				_pointIndex = inMoveTo - 1;
+			}
+			else
+			{
+				// Point index moves to moveTo
+				_pointIndex = inMoveTo + inSegmentEnd - inSegmentStart;
+			}
+			// System.out.println("    Need to adjust movedseg: " + origEndIndex + " -> " + _pointIndex);
+		}
+	}
+}
diff --git a/tim/prune/function/sew/SewTrackSegmentsFunction.java b/tim/prune/function/sew/SewTrackSegmentsFunction.java
new file mode 100644
index 0000000..44ad9db
--- /dev/null
+++ b/tim/prune/function/sew/SewTrackSegmentsFunction.java
@@ -0,0 +1,331 @@
+package tim.prune.function.sew;
+
+import java.util.TreeSet;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Track;
+import tim.prune.function.Cancellable;
+import tim.prune.gui.GenericProgressDialog;
+import tim.prune.undo.UndoException;
+import tim.prune.undo.UndoSewSegments;
+
+/**
+ * Function to sew the track segments together if possible,
+ * reversing and moving as required
+ */
+public class SewTrackSegmentsFunction extends GenericFunction implements Runnable, Cancellable
+{
+	/** Set of sorted segment endpoints */
+	private TreeSet<SegmentEnd> _nodes = null;
+	/** Cancel flag */
+	private boolean _cancelled = false;
+
+
+	/** Constructor */
+	public SewTrackSegmentsFunction(App inApp) {
+		super(inApp);
+	}
+
+	/** @return name key */
+	public String getNameKey() {
+		return "function.sewsegments";
+	}
+
+	/**
+	 * Execute the function
+	 */
+	public void begin()
+	{
+		// Run in separate thread, with progress bar
+		new Thread(this).start();
+	}
+
+	/**
+	 * Run the function in a separate thread
+	 */
+	public void run()
+	{
+		// Make a progress bar
+		GenericProgressDialog progressDialog = new GenericProgressDialog(getNameKey(), null, _parentFrame, this);
+		progressDialog.show();
+		// Make an undo object to store the current points and sequence
+		UndoSewSegments undo = new UndoSewSegments(_app.getTrackInfo().getTrack());
+
+		// Make list of all the segment ends
+		_nodes = buildNodeList(_app.getTrackInfo().getTrack());
+		final int numNodes = (_nodes == null ? 0 : _nodes.size());
+		if (numNodes < 4)
+		{
+			System.out.println("Can't do anything with this, not enough segments");
+			progressDialog.close();
+		}
+		else
+		{
+			progressDialog.showProgress(10, 100); // Say 10% for building the nodes
+
+			// Disable messaging because we're probably doing a lot of reverses and moves
+			UpdateMessageBroker.enableMessaging(false);
+			// Set now contains all pairs of segment ends, ends at the same location are adjacent
+			// Now we're just interested in pairs of nodes, not three or more at the same location
+			SegmentEnd firstNode = null, secondNode = null;
+			int numJoins = 0, currNode = 0;
+			for (SegmentEnd node : _nodes)
+			{
+				if (!node.isActive()) {continue;}
+				if (firstNode == null)
+				{
+					firstNode = node;
+				}
+				else if (secondNode == null)
+				{
+					if (node.atSamePointAs(firstNode)) {
+						secondNode = node;
+					}
+					else {
+						firstNode = node;
+					}
+				}
+				else if (node.atSamePointAs(secondNode))
+				{
+					// Found three colocated nodes, not interested
+					firstNode = secondNode = null;
+				}
+				else
+				{
+					// Found a pair
+					joinSegments(firstNode, secondNode);
+					numJoins++;
+					firstNode = node; secondNode = null;
+				}
+				if (_cancelled) {break;}
+				final double fractionDone = 1.0 * currNode / numNodes;
+				progressDialog.showProgress(10 + (int) (fractionDone * 80), 100);
+				currNode++;
+			}
+			if (firstNode != null && secondNode != null)
+			{
+				joinSegments(firstNode, secondNode);
+				numJoins++;
+			}
+
+			progressDialog.showProgress(90, 100); // Say 90%, only duplicate point deletion left
+
+			// Delete the duplicate points
+			final int numDeleted = _cancelled ? 0 : deleteSegmentStartPoints(_app.getTrackInfo().getTrack());
+
+			progressDialog.close();
+			// Enable the messaging again
+			UpdateMessageBroker.enableMessaging(true);
+			if (_cancelled) // TODO: Also revert if any of the operations failed
+			{
+				// try to restore using undo object
+				try {
+					undo.performUndo(_app.getTrackInfo());
+				}
+				catch (UndoException ue) {
+					_app.showErrorMessage("oops", "CANNOT UNDO");
+				}
+			}
+			else if (numJoins > 0 || numDeleted > 0)
+			{
+				// Give Undo object back to App to confirm
+				final String confirmMessage = (numJoins > 0 ? I18nManager.getTextWithNumber("confirm.sewsegments", numJoins)
+					: "" + numDeleted + " " + I18nManager.getText("confirm.deletepoint.multi"));
+				_app.completeFunction(undo, confirmMessage);
+				UpdateMessageBroker.informSubscribers();
+			}
+			else
+			{
+				// Nothing done
+				_app.showErrorMessageNoLookup(getNameKey(), I18nManager.getTextWithNumber("error.sewsegments.nothingdone", numNodes/2));
+			}
+		}
+	}
+
+	/**
+	 * Build a sorted list of all the segment start points and end points
+	 * Creates a TreeSet containing two SegmentEnd objects for each segment
+	 * @param inTrack track object
+	 * @return sorted list of segment ends
+	 */
+	private static TreeSet<SegmentEnd> buildNodeList(Track inTrack)
+	{
+		TreeSet<SegmentEnd> nodes = new TreeSet<SegmentEnd>();
+		final int numPoints = inTrack.getNumPoints();
+		DataPoint prevTrackPoint = null;
+		int       prevTrackPointIndex = -1;
+		SegmentEnd segmentStart = null;
+		for (int i=0; i<numPoints; i++)
+		{
+			DataPoint point = inTrack.getPoint(i);
+			if (!point.isWaypoint() && !point.hasMedia())
+			{
+				if (point.getSegmentStart())
+				{
+					// Start of new segment - does previous one need to be saved?
+					if (segmentStart != null && prevTrackPointIndex > 0 && prevTrackPointIndex != segmentStart.getPointIndex())
+					{
+						// Finish previous segment and store in list
+						SegmentEnd segmentEnd = new SegmentEnd(prevTrackPoint, prevTrackPointIndex);
+						segmentStart.setOtherEnd(segmentEnd);
+						segmentEnd.setOtherEnd(segmentStart);
+						// Don't add closed loops
+						if (!segmentStart.atSamePointAs(segmentEnd))
+						{
+							nodes.add(segmentStart);
+							nodes.add(segmentEnd);
+						}
+					}
+					// Remember segment start
+					segmentStart = new SegmentEnd(point, i);
+				}
+				prevTrackPoint = point;
+				prevTrackPointIndex = i;
+			}
+		}
+		// Probably need to deal with segmentStart and prevTrackPoint, prevTrackPointIndex
+		if (segmentStart != null && prevTrackPointIndex > 0 && prevTrackPointIndex != segmentStart.getPointIndex())
+		{
+			// Finish last segment and store in list
+			SegmentEnd segmentEnd = new SegmentEnd(prevTrackPoint, prevTrackPointIndex);
+			segmentStart.setOtherEnd(segmentEnd);
+			segmentEnd.setOtherEnd(segmentStart);
+			// Don't add closed loops
+			if (!segmentStart.atSamePointAs(segmentEnd))
+			{
+				nodes.add(segmentStart);
+				nodes.add(segmentEnd);
+			}
+		}
+		return nodes;
+	}
+
+	/**
+	 * Join the two segments together represented by the given nodes
+	 * @param inFirstNode first node (order doesn't matter)
+	 * @param inSecondNode other node
+	 */
+	private void joinSegments(SegmentEnd inFirstNode, SegmentEnd inSecondNode)
+	{
+		final Track track = _app.getTrackInfo().getTrack();
+		// System.out.println("Join: (" + inFirstNode.getPointIndex() + "-" + inFirstNode.getOtherPointIndex() + ") with ("
+		//	+ inSecondNode.getPointIndex() + "-" + inSecondNode.getOtherPointIndex() + ")");
+		// System.out.println("    : " + (inFirstNode.isStart() ? "start" : "end") + " to " + (inSecondNode.isStart() ? "start" : "end"));
+		final boolean moveSecondBeforeFirst = inFirstNode.isStart();
+		if (inFirstNode.isStart() == inSecondNode.isStart())
+		{
+			if (track.reverseRange(inSecondNode.getEarlierIndex(), inSecondNode.getLaterIndex()))
+			{
+				inSecondNode.reverseSegment();
+				// System.out.println("    : Reverse segment: " + inSecondNode.getEarlierIndex() + " - " + inSecondNode.getLaterIndex());
+			}
+			else {
+				System.err.println("Oops, reverse range didn't work");
+				// TODO: Abort?
+			}
+		}
+		if (moveSecondBeforeFirst)
+		{
+			if ((inSecondNode.getLaterIndex()+1) != inFirstNode.getPointIndex())
+			{
+				// System.out.println("    : Move second segment before first");
+				cutAndMoveSegment(inSecondNode.getEarlierIndex(), inSecondNode.getLaterIndex(), inFirstNode.getPointIndex());
+			}
+		}
+		else if ((inFirstNode.getLaterIndex()+1) != inSecondNode.getPointIndex())
+		{
+			// System.out.println("    : Move first segment before second (because " + (inFirstNode.getLaterIndex()+1) + " isn't " + inSecondNode.getPointIndex() + ")");
+			cutAndMoveSegment(inFirstNode.getEarlierIndex(), inFirstNode.getLaterIndex(), inSecondNode.getPointIndex());
+		}
+		// Now merge the SegmentEnds so that they're not split up again
+		if (inSecondNode.getEarlierIndex() == (inFirstNode.getLaterIndex()+1)) {
+			// System.out.println("second node is now directly after the first node");
+		}
+		else if (inFirstNode.getEarlierIndex() == (inSecondNode.getLaterIndex()+1)) {
+			//System.out.println("first node is now directly after the second node");
+		}
+		else {
+			System.err.println("Why aren't the segments directly consecutive after the join?");
+		}
+		// Find the earliest and latest ends of these two segments
+		SegmentEnd earlierSegmentEnd = (inFirstNode.getEarlierIndex() < inSecondNode.getEarlierIndex() ? inFirstNode : inSecondNode).getEarlierEnd();
+		SegmentEnd laterSegmentEnd   = (inFirstNode.getLaterIndex() > inSecondNode.getLaterIndex() ? inFirstNode : inSecondNode).getLaterEnd();
+		// Get rid of the inner two segment ends, join the earliest and latest together
+		earlierSegmentEnd.getOtherEnd().deactivate();
+		laterSegmentEnd.getOtherEnd().deactivate();
+		earlierSegmentEnd.setOtherEnd(laterSegmentEnd);
+		laterSegmentEnd.setOtherEnd(earlierSegmentEnd);
+	}
+
+	/**
+	 * Cut and move the segment to a different position
+	 * @param inSegmentStart start index of segment
+	 * @param inSegmentEnd end index of segment
+	 * @param inMoveToPos index before which the segment should be moved
+	 */
+	private void cutAndMoveSegment(int inSegmentStart, int inSegmentEnd, int inMoveToPos)
+	{
+		if (!_app.getTrackInfo().getTrack().cutAndMoveSection(inSegmentStart, inSegmentEnd, inMoveToPos))
+		{
+			System.err.println("   Oops, cut and move didn't work");
+			// TODO: Throw exception? Return false?
+		}
+		else
+		{
+			// Loop over each node to inform it of the index changes
+			for (SegmentEnd node : _nodes) {
+				node.adjustPointIndex(inSegmentStart, inSegmentEnd, inMoveToPos);
+			}
+		}
+	}
+
+	/**
+	 * The final step of the sewing, removing the duplicate points at the start of each segment
+	 * @param inTrack track object
+	 * @return number of points deleted
+	 */
+	private static int deleteSegmentStartPoints(Track inTrack)
+	{
+		final int numPoints = inTrack.getNumPoints();
+		boolean[] deleteFlags = new boolean[numPoints];
+		// Loop over points in track, setting delete flags
+		int numToDelete = 0;
+		DataPoint prevPoint = null;
+		for (int i=0; i<numPoints; i++)
+		{
+			DataPoint point = inTrack.getPoint(i);
+			if (!point.isWaypoint())
+			{
+				if (prevPoint != null && point.getSegmentStart() && point.isDuplicate(prevPoint))
+				{
+					deleteFlags[i] = true;
+					numToDelete++;
+				}
+				prevPoint = point;
+			}
+		}
+		// Make new datapoint array of the right size
+		DataPoint[] pointCopies = new DataPoint[numPoints - numToDelete];
+		// Loop over points again, keeping the ones we want
+		int copyIndex = 0;
+		for (int i=0; i<numPoints; i++)
+		{
+			if (!deleteFlags[i]) {
+				pointCopies[copyIndex] = inTrack.getPoint(i);
+				copyIndex++;
+			}
+		}
+		// Finally, replace the copied points in the track
+		inTrack.replaceContents(pointCopies);
+		return numToDelete;
+	}
+
+	/** Function cancelled by progress dialog */
+	public void cancel() {
+		_cancelled = true;
+	}
+}
diff --git a/tim/prune/function/sew/SplitPoint.java b/tim/prune/function/sew/SplitPoint.java
new file mode 100644
index 0000000..c482cda
--- /dev/null
+++ b/tim/prune/function/sew/SplitPoint.java
@@ -0,0 +1,101 @@
+package tim.prune.function.sew;
+
+import tim.prune.data.Coordinate;
+import tim.prune.data.DataPoint;
+
+/**
+ * Class to represent a possible split point, including
+ * the distances to the previous and next points
+ */
+public class SplitPoint implements Comparable<SplitPoint>
+{
+	private SplitPoint _nextPoint = null;
+	private Coordinate _longitude = null;
+	private Coordinate _latitude  = null;
+	private int        _pointIndex = 0;
+	private double     _distToPrevPoint = 0.0;
+	private double     _distToNextPoint = -1.0;
+
+
+	/**
+	 * Constructor
+	 * @param inPoint data point
+	 * @param inIndex point index within track
+	 */
+	public SplitPoint(DataPoint inPoint, int inIndex)
+	{
+		_longitude = inPoint.getLongitude();
+		_latitude  = inPoint.getLatitude();
+		_pointIndex = inIndex;
+	}
+
+	/**
+	 * @param inDist distance to previous track point
+	 */
+	public void setDistanceToPrevPoint(double inDist) {
+		_distToPrevPoint = inDist;
+	}
+	/** @return distance to previous track point */
+	public double getDistanceToPrevPoint() {
+		return _distToPrevPoint;
+	}
+
+	/**
+	 * @param inDist distance to next track point, or -1.0
+	 */
+	public void setDistanceToNextPoint(double inDist) {
+		_distToNextPoint = inDist;
+	}
+	/** @return distance to next track point */
+	public double getDistanceToNextPoint() {
+		return _distToNextPoint;
+	}
+	/** @return true if this is closer to the next point than to the previous one */
+	public boolean closerToNext() {
+		return _distToNextPoint > 0.0 && _distToNextPoint < _distToPrevPoint;
+	}
+
+	/** @return point index */
+	public int getPointIndex() {
+		return _pointIndex;
+	}
+
+	/**
+	 * @param inOther the next point
+	 */
+	public void setNextPoint(SplitPoint inOther) {
+		_nextPoint = inOther;
+	}
+
+	/** @return the next point, or null */
+	public SplitPoint getNextPoint() {
+		return _nextPoint;
+	}
+
+	/**
+	 * @param inOther other segment end
+	 * @return true if the coordinates are identical
+	 */
+	public boolean atSamePointAs(SplitPoint inOther)
+	{
+		return inOther != null && _latitude.equals(inOther._latitude) && _longitude.equals(inOther._longitude);
+	}
+
+	/**
+	 * Compare two objects for sorting
+	 */
+	public int compareTo(SplitPoint o)
+	{
+		if (o == null) return -1;
+		// First, sort by latitude
+		if (!_latitude.equals(o._latitude)) {
+			return (_latitude.getDouble() < o._latitude.getDouble() ? -1 : 1);
+		}
+		// Latitudes same, so sort by longitude
+		if (!_longitude.equals(o._longitude)) {
+			return (_longitude.getDouble() < o._longitude.getDouble() ? -1 : 1);
+		}
+		// Points are identical so just sort by index
+		return _pointIndex - o._pointIndex;
+	}
+}
diff --git a/tim/prune/function/sew/SplitSegmentsFunction.java b/tim/prune/function/sew/SplitSegmentsFunction.java
new file mode 100644
index 0000000..91a7d52
--- /dev/null
+++ b/tim/prune/function/sew/SplitSegmentsFunction.java
@@ -0,0 +1,285 @@
+package tim.prune.function.sew;
+
+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.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Distance;
+import tim.prune.data.Field;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.WholeNumberField;
+import tim.prune.undo.UndoSplitSegments;
+
+/**
+ * Function to split a track into segments using
+ * either a distance limit or a time limit
+ */
+public class SplitSegmentsFunction extends GenericFunction
+{
+	/** Dialog */
+	private JDialog _dialog = null;
+	/** Radio buttons for splitting by distance and time */
+	private JRadioButton _distLimitRadio = null, _timeLimitRadio = null;
+	/** Dropdown for selecting distance units */
+	private JComboBox<String> _distUnitsDropdown = null;
+	/** Text field for entering distance */
+	private WholeNumberField _distanceField = null;
+	/** Text fields for entering distance */
+	private WholeNumberField _limitHourField = null, _limitMinField = null;
+	/** Ok button */
+	private JButton _okButton = null;
+
+
+	/**
+	 * React to item changes and key presses
+	 */
+	private abstract class ChangeListener extends KeyAdapter implements ItemListener
+	{
+		/** Method to be implemented */
+		public abstract void optionsChanged();
+
+		/** Item changed in ItemListener */
+		public void itemStateChanged(ItemEvent arg0) {
+			optionsChanged();
+		}
+
+		/** Key released in KeyListener */
+		public void keyReleased(KeyEvent arg0) {
+			optionsChanged();
+		}
+	}
+
+	/**
+	 * Constructor
+	 */
+	public SplitSegmentsFunction(App inApp) {
+		super(inApp);
+	}
+
+	/**
+	 * @return name key
+	 */
+	public String getNameKey() {
+		return "function.splitsegments";
+	}
+
+	/**
+	 * Begin the function
+	 */
+	public void begin()
+	{
+		if (_dialog == null)
+		{
+			_dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+			_dialog.setLocationRelativeTo(_parentFrame);
+			_dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+			_dialog.getContentPane().add(makeDialogComponents());
+			_dialog.pack();
+		}
+		enableOkButton();
+		// TODO: Maybe set distance units according to current Config setting?
+		final boolean hasTimestamps = _app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP);
+		_timeLimitRadio.setEnabled(hasTimestamps);
+		_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));
+
+		// Make radio buttons for three different options
+		_distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit") + ": ");
+		_timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + ": ");
+		ButtonGroup radioGroup = new ButtonGroup();
+		radioGroup.add(_distLimitRadio);
+		radioGroup.add(_timeLimitRadio);
+
+		// central panel for limits
+		JPanel limitsPanel = new JPanel();
+		limitsPanel.setLayout(new BoxLayout(limitsPanel, BoxLayout.Y_AXIS));
+		limitsPanel.add(Box.createVerticalStrut(8));
+		ChangeListener optionsChangedListener = new ChangeListener() {
+			public void optionsChanged() {
+				enableOkButton();
+			}
+		};
+		// distance limits
+		JPanel distLimitPanel = new JPanel();
+		distLimitPanel.setLayout(new FlowLayout());
+		_distLimitRadio.setSelected(true);
+		_distLimitRadio.addItemListener(optionsChangedListener);
+		distLimitPanel.add(_distLimitRadio);
+		_distanceField = new WholeNumberField(3);
+		_distanceField.addKeyListener(optionsChangedListener);
+		distLimitPanel.add(_distanceField);
+		String[] distUnitsOptions = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.metres"),
+			I18nManager.getText("units.miles")};
+		_distUnitsDropdown = new JComboBox<String>(distUnitsOptions);
+		_distUnitsDropdown.addItemListener(optionsChangedListener);
+		distLimitPanel.add(_distUnitsDropdown);
+		distLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+		limitsPanel.add(distLimitPanel);
+
+		// time limit panel
+		JPanel timeLimitPanel = new JPanel();
+		timeLimitPanel.setLayout(new FlowLayout());
+		_timeLimitRadio.addItemListener(optionsChangedListener);
+		timeLimitPanel.add(_timeLimitRadio);
+		_limitHourField = new WholeNumberField(2);
+		_limitHourField.addKeyListener(optionsChangedListener);
+		timeLimitPanel.add(_limitHourField);
+		timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.hours")));
+		_limitMinField = new WholeNumberField(3);
+		_limitMinField.addKeyListener(optionsChangedListener);
+		timeLimitPanel.add(_limitMinField);
+		timeLimitPanel.add(new JLabel(I18nManager.getText("units.minutes")));
+		timeLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+		limitsPanel.add(timeLimitPanel);
+
+		dialogPanel.add(limitsPanel, BorderLayout.NORTH);
+
+		// button panel at bottom
+		JPanel buttonPanel = new JPanel();
+		buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+		// OK button
+		_okButton = new JButton(I18nManager.getText("button.ok"));
+		_okButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				performSplit();
+			}
+		});
+		buttonPanel.add(_okButton);
+		// Cancel button
+		JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+		cancelButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				_dialog.dispose();
+			}
+		});
+		cancelButton.addKeyListener(new KeyAdapter() {
+			public void keyPressed(KeyEvent inE) {
+				if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+			}
+		});
+		buttonPanel.add(cancelButton);
+		dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+		return dialogPanel;
+	}
+
+	/**
+	 * Enable or disable the OK button according to the inputs
+	 */
+	private void enableOkButton()
+	{
+		boolean enabled = false;
+		if (_distLimitRadio.isSelected()) {
+			enabled = _distanceField.getValue() > 0;
+		}
+		else if (_timeLimitRadio.isSelected()) {
+			enabled = _limitHourField.getValue() > 0 || _limitMinField.getValue() > 0;
+		}
+		_okButton.setEnabled(enabled);
+
+		// Also enable/disable the other fields
+		_distanceField.setEnabled(_distLimitRadio.isSelected());
+		_distUnitsDropdown.setEnabled(_distLimitRadio.isSelected());
+		_limitHourField.setEnabled(_timeLimitRadio.isSelected());
+		_limitMinField.setEnabled(_timeLimitRadio.isSelected());
+	}
+
+	/**
+	 * The dialog has been completed and OK pressed, so do the split
+	 */
+	private void performSplit()
+	{
+		// Split either by distance or time
+		boolean checkTimeLimit = _timeLimitRadio.isSelected()
+			&& (_limitHourField.getValue() > 0 || _limitMinField.getValue() > 0);
+		int timeLimitSeconds = 0;
+		if (checkTimeLimit)
+		{
+			timeLimitSeconds = _limitHourField.getValue() * 60 * 60
+				+ _limitMinField.getValue() * 60;
+			if (timeLimitSeconds <= 0) {checkTimeLimit = false;}
+		}
+		double distLimitRadians = 0.0;
+		final boolean checkDistLimit = _distLimitRadio.isSelected()
+			&& _distanceField.getValue() > 0;
+		if (checkDistLimit)
+		{
+			final Unit[] distUnits = {UnitSetLibrary.UNITS_KILOMETRES,
+				UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_MILES};
+			Unit distUnit = distUnits[_distUnitsDropdown.getSelectedIndex()];
+			distLimitRadians = Distance.convertDistanceToRadians(_distanceField.getValue(), distUnit);
+		}
+		if (!checkTimeLimit && !checkDistLimit) {
+			return; // neither option selected
+		}
+
+		// Make undo object
+		UndoSplitSegments undo = new UndoSplitSegments(_app.getTrackInfo().getTrack());
+		final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
+		DataPoint currPoint = null, prevPoint = null;
+		int numSplitsMade = 0;
+
+		// Now actually do it, looping through the points in the track
+		for (int i=0; i<numPoints; i++)
+		{
+			currPoint = _app.getTrackInfo().getTrack().getPoint(i);
+			if (!currPoint.isWaypoint())
+			{
+				boolean splitHere = (prevPoint != null)
+					&& ((checkDistLimit && DataPoint.calculateRadiansBetween(prevPoint, currPoint) > distLimitRadians)
+						|| (checkTimeLimit && currPoint.hasTimestamp() && prevPoint.hasTimestamp()
+							&& currPoint.getTimestamp().getSecondsSince(prevPoint.getTimestamp()) > timeLimitSeconds));
+				if (splitHere && !currPoint.getSegmentStart())
+				{
+					currPoint.setSegmentStart(true);
+					numSplitsMade++;
+				}
+				prevPoint = currPoint;
+			}
+		}
+
+		if (numSplitsMade > 0)
+		{
+			_app.completeFunction(undo, I18nManager.getTextWithNumber("confirm.splitsegments", numSplitsMade));
+			UpdateMessageBroker.informSubscribers();
+			_dialog.dispose();
+		}
+		else
+		{
+			// Complain that no split was made
+			JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.tracksplit.nosplit"),
+				I18nManager.getText("error.function.noop.title"), JOptionPane.WARNING_MESSAGE);
+		}
+	}
+}
diff --git a/tim/prune/function/srtm/DownloadSrtmFunction.java b/tim/prune/function/srtm/DownloadSrtmFunction.java
new file mode 100644
index 0000000..d0d92bb
--- /dev/null
+++ b/tim/prune/function/srtm/DownloadSrtmFunction.java
@@ -0,0 +1,222 @@
+package tim.prune.function.srtm;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+
+import javax.swing.JOptionPane;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.GpsPrune;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.DoubleRange;
+import tim.prune.gui.ProgressDialog;
+
+/**
+ * Class to provide a download function for the Space Shuttle's SRTM data files.
+ * HGT files are downloaded into memory via HTTP and stored in the map cache.
+ */
+public class DownloadSrtmFunction extends GenericFunction implements Runnable
+{
+	/** Progress dialog */
+	private ProgressDialog _progress = null;
+	/** Flag to check whether this function is currently running or not */
+	private boolean _running = false;
+
+
+	/**
+	 * Constructor
+	 * @param inApp  App object
+	 */
+	public DownloadSrtmFunction(App inApp) {
+		super(inApp);
+	}
+
+	/** @return name key */
+	public String getNameKey() {
+		return "function.downloadsrtm";
+	}
+
+	/**
+	 * Begin the download
+	 */
+	public void begin()
+	{
+		_running = true;
+		if (_progress == null) {
+			_progress = new ProgressDialog(_parentFrame, getNameKey());
+		}
+		_progress.show();
+		// start new thread for time-consuming part
+		new Thread(this).start();
+	}
+
+	/**
+	 * Run method using separate thread
+	 */
+	public void run()
+	{
+		// Compile list of tiles to get
+		ArrayList<SrtmTile> tileList = new ArrayList<SrtmTile>();
+
+		// First, loop to see which tiles are needed
+		DoubleRange lonRange = _app.getTrackInfo().getTrack().getLonRange();
+		DoubleRange latRange = _app.getTrackInfo().getTrack().getLatRange();
+		final int minLon = (int) Math.floor(lonRange.getMinimum());
+		final int maxLon = (int) Math.floor(lonRange.getMaximum());
+		final int minLat = (int) Math.floor(latRange.getMinimum());
+		final int maxLat = (int) Math.floor(latRange.getMaximum());
+
+		for (int lon=minLon; lon<= maxLon; lon++)
+		{
+			for (int lat=minLat; lat <= maxLat; lat++)
+			{
+				SrtmTile tile = new SrtmTile(lat, lon);
+				boolean alreadyGot = false;
+				for (int t = 0; t < tileList.size(); t++)
+				{
+					if (tileList.get(t).equals(tile)) {
+						alreadyGot = true;
+					}
+				}
+				if (!alreadyGot) {tileList.add(tile);}
+			}
+		}
+
+		downloadTiles(tileList);
+		// Finished
+		_running = false;
+	}
+
+
+	/**
+	 * Download the tiles of SRTM data
+	 * @param inTileList list of tiles to get
+	 */
+	private void downloadTiles(ArrayList<SrtmTile> inTileList)
+	{
+		// Update progress bar
+		if (_progress != null)
+		{
+			_progress.setMaximum(inTileList.size());
+			_progress.setValue(0);
+		}
+
+		String errorMessage = null;
+
+		// Check the cache is ok
+		final String diskCachePath = Config.getConfigString(Config.KEY_DISK_CACHE);
+		if (diskCachePath != null)
+		{
+			File srtmDir = new File(diskCachePath, "srtm");
+			if (!srtmDir.exists() && !srtmDir.mkdir()) {
+				// can't create the srtm directory
+				errorMessage = I18nManager.getText("error.downloadsrtm.nocache");
+			}
+		}
+		else {
+			// no cache set up
+			errorMessage = I18nManager.getText("error.downloadsrtm.nocache");
+		}
+
+		// Get urls for each tile
+		URL[] urls = TileFinder.getUrls(inTileList);
+		int numDownloaded = 0;
+		for (int t=0; t<inTileList.size() && !_progress.isCancelled(); t++)
+		{
+			if (urls[t] != null)
+			{
+				// Define streams
+				FileOutputStream outStream = null;
+				InputStream inStream = null;
+				try
+				{
+					// Set progress
+					_progress.setValue(t);
+					// See if we've already got this tile or not
+					File outputFile = getFileToWrite(urls[t]);
+					if (outputFile != null)
+					{
+						// System.out.println("Download: Need to download: " + urls[t]);
+						outStream = new FileOutputStream(outputFile);
+						URLConnection conn = urls[t].openConnection();
+						conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
+						inStream = conn.getInputStream();
+						// Copy all the bytes to the file
+						int c;
+						while ((c = inStream.read()) != -1)
+						{
+							outStream.write(c);
+						}
+
+						numDownloaded++;
+					}
+					// else System.out.println("Don't need to download: " + urls[t].getFile());
+				}
+				catch (IOException ioe) {errorMessage = ioe.getClass().getName() + " - " + ioe.getMessage();
+				}
+				// Make sure streams are closed
+				try {inStream.close();} catch (Exception e) {}
+				try {outStream.close();} catch (Exception e) {}
+			}
+		}
+
+		_progress.dispose();
+		if (_progress.isCancelled()) {
+			return;
+		}
+
+		if (errorMessage != null) {
+			_app.showErrorMessageNoLookup(getNameKey(), errorMessage);
+		}
+		else if (numDownloaded == 1)
+		{
+			JOptionPane.showMessageDialog(_parentFrame, I18nManager.getTextWithNumber("confirm.downloadsrtm.1", numDownloaded),
+				I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
+		}
+		else if (numDownloaded > 1)
+		{
+			JOptionPane.showMessageDialog(_parentFrame, I18nManager.getTextWithNumber("confirm.downloadsrtm", numDownloaded),
+				I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
+		}
+		else if (inTileList.size() > 0) {
+			_app.showErrorMessage(getNameKey(), "confirm.downloadsrtm.none");
+		}
+	}
+
+	/**
+	 * See whether the SRTM file is already available locally
+	 * @param inUrl URL for online resource
+	 * @return file object to write to, or null if already there
+	 */
+	private static File getFileToWrite(URL inUrl)
+	{
+		String diskCachePath = Config.getConfigString(Config.KEY_DISK_CACHE);
+		if (diskCachePath != null)
+		{
+			File srtmDir = new File(diskCachePath, "srtm");
+			if (srtmDir.exists() && srtmDir.isDirectory() && srtmDir.canRead())
+			{
+				File srtmFile = new File(srtmDir, new File(inUrl.getFile()).getName());
+				if (!srtmFile.exists() || !srtmFile.canRead() || srtmFile.length() <= 1) {
+					return srtmFile;
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * @return true if a thread is currently running
+	 */
+	public boolean isRunning()
+	{
+		return _running;
+	}
+}
diff --git a/tim/prune/function/srtm/LookupSrtmFunction.java b/tim/prune/function/srtm/LookupSrtmFunction.java
index 4d13348..2ed1454 100644
--- a/tim/prune/function/srtm/LookupSrtmFunction.java
+++ b/tim/prune/function/srtm/LookupSrtmFunction.java
@@ -1,5 +1,7 @@
 package tim.prune.function.srtm;
 
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
@@ -13,35 +15,44 @@ import tim.prune.DataSubscriber;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
+import tim.prune.config.Config;
+import tim.prune.data.Altitude;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.Track;
+import tim.prune.data.UnitSetLibrary;
 import tim.prune.gui.ProgressDialog;
+import tim.prune.tips.TipManager;
 import tim.prune.undo.UndoLookupSrtm;
 
 /**
- * Class to provide a lookup function for point altitudes
- * using the Space Shuttle's SRTM data files.
- * HGT files are downloaded into memory via HTTP and point altitudes
- * can then be interpolated from the 3m grid data.
+ * Class to provide a lookup function for point altitudes using the Space
+ * Shuttle's SRTM data files. HGT files are downloaded into memory via HTTP and
+ * point altitudes can then be interpolated from the 3m grid data.
  */
 public class LookupSrtmFunction extends GenericFunction implements Runnable
 {
 	/** Progress dialog */
-	ProgressDialog _progress = null;
+	private ProgressDialog _progress = null;
+	/** Track to process */
+	private Track _track = null;
+	/** Flag for whether this is a real track or a terrain one */
+	private boolean _normalTrack = true;
+	/** Flag set when any tiles had to be downloaded (rather than just loaded locally) */
+	private boolean _hadToDownload = false;
+	/** Flag to check whether this function is currently running or not */
+	private boolean _running = false;
 
 	/** Expected size of hgt file in bytes */
 	private static final long HGT_SIZE = 2884802L;
 	/** Altitude below which is considered void */
 	private static final int VOID_VAL = -32768;
 
-
 	/**
 	 * Constructor
-	 * @param inApp App object
+	 * @param inApp  App object
 	 */
-	public LookupSrtmFunction(App inApp)
-	{
+	public LookupSrtmFunction(App inApp) {
 		super(inApp);
 	}
 
@@ -51,36 +62,54 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
 	}
 
 	/**
-	 * Begin the lookup
+	 * Begin the lookup using the normal track
+	 */
+	public void begin() {
+		begin(_app.getTrackInfo().getTrack(), true);
+	}
+
+	/**
+	 * Begin the lookup with an alternative track
+	 * @param inAlternativeTrack
 	 */
-	public void begin()
+	public void begin(Track inAlternativeTrack) {
+		begin(inAlternativeTrack, false);
+	}
+
+	/**
+	 * Begin the function with the given parameters
+	 * @param inTrack track to process
+	 * @param inNormalTrack true if this is a "normal" track, false for an artificially constructed one such as for terrain
+	 */
+	private void begin(Track inTrack, boolean inNormalTrack)
 	{
-		if (_progress == null)
-		{
+		_running = true;
+		_hadToDownload = false;
+		if (_progress == null) {
 			_progress = new ProgressDialog(_parentFrame, getNameKey());
 		}
 		_progress.show();
+		_track = inTrack;
+		_normalTrack = inNormalTrack;
 		// start new thread for time-consuming part
 		new Thread(this).start();
 	}
 
-
 	/**
 	 * Run method using separate thread
 	 */
 	public void run()
 	{
 		// Compile list of tiles to get
-		Track track = _app.getTrackInfo().getTrack();
 		ArrayList<SrtmTile> tileList = new ArrayList<SrtmTile>();
 		boolean hasZeroAltitudePoints = false;
 		boolean hasNonZeroAltitudePoints = false;
 		// First, loop to see what kind of points we have
-		for (int i=0; i<track.getNumPoints(); i++)
+		for (int i = 0; i < _track.getNumPoints(); i++)
 		{
-			if (track.getPoint(i).hasAltitude())
+			if (_track.getPoint(i).hasAltitude())
 			{
-				if (track.getPoint(i).getAltitude().getValue() == 0) {
+				if (_track.getPoint(i).getAltitude().getValue() == 0) {
 					hasZeroAltitudePoints = true;
 				}
 				else {
@@ -99,14 +128,16 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
 		}
 
 		// Now loop again to extract the required tiles
-		for (int i=0; i<track.getNumPoints(); i++)
+		for (int i = 0; i < _track.getNumPoints(); i++)
 		{
 			// Consider points which don't have altitudes or have zero values
-			if (!track.getPoint(i).hasAltitude() || (overwriteZeros && track.getPoint(i).getAltitude().getValue() == 0))
+			if (!_track.getPoint(i).hasAltitude()
+				|| (overwriteZeros && _track.getPoint(i).getAltitude().getValue() == 0))
 			{
-				SrtmTile tile = new SrtmTile(track.getPoint(i));
+				SrtmTile tile = new SrtmTile(_track.getPoint(i));
 				boolean alreadyGot = false;
-				for (int t=0; t<tileList.size(); t++) {
+				for (int t = 0; t < tileList.size(); t++)
+				{
 					if (tileList.get(t).equals(tile)) {
 						alreadyGot = true;
 					}
@@ -115,8 +146,15 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
 			}
 		}
 		lookupValues(tileList, overwriteZeros);
+		// Finished
+		_running = false;
+		// Show tip if lots of online lookups were necessary
+		if (_hadToDownload) {
+			_app.showTip(TipManager.Tip_DownloadSrtm);
+		}
 	}
 
+
 	/**
 	 * Lookup the values from SRTM data
 	 * @param inTileList list of tiles to get
@@ -124,12 +162,14 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
 	 */
 	private void lookupValues(ArrayList<SrtmTile> inTileList, boolean inOverwriteZeros)
 	{
-		Track track = _app.getTrackInfo().getTrack();
 		UndoLookupSrtm undo = new UndoLookupSrtm(_app.getTrackInfo());
 		int numAltitudesFound = 0;
 		// Update progress bar
-		_progress.setMaximum(inTileList.size());
-		_progress.setValue(0);
+		if (_progress != null)
+		{
+			_progress.setMaximum(inTileList.size());
+			_progress.setValue(0);
+		}
 		String errorMessage = null;
 		// Get urls for each tile
 		URL[] urls = TileFinder.getUrls(inTileList);
@@ -140,40 +180,49 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
 				SrtmTile tile = inTileList.get(t);
 				try
 				{
+					// Set progress
 					_progress.setValue(t);
-					final int ARRLENGTH = 1201*1201;
+					final int ARRLENGTH = 1201 * 1201;
 					int[] heights = new int[ARRLENGTH];
 					// Open zipinputstream on url and check size
-					ZipInputStream inStream = new ZipInputStream(urls[t].openStream());
-					ZipEntry entry = inStream.getNextEntry();
-					boolean entryOk = (entry.getSize() == HGT_SIZE);
-					if (entryOk)
+					ZipInputStream inStream = getStreamToHgtFile(urls[t]);
+					boolean entryOk = false;
+					if (inStream != null)
 					{
-						// Read entire file contents into one byte array
-						for (int i=0; i<ARRLENGTH; i++) {
-							heights[i] = inStream.read()*256 + inStream.read();
-							if (heights[i] >= 32768) {heights[i] -= 65536;}
+						ZipEntry entry = inStream.getNextEntry();
+						entryOk = (entry != null && entry.getSize() == HGT_SIZE);
+						if (entryOk)
+						{
+							// Read entire file contents into one byte array
+							for (int i = 0; i < ARRLENGTH; i++)
+							{
+								heights[i] = inStream.read() * 256 + inStream.read();
+								if (heights[i] >= 32768) {heights[i] -= 65536;}
+							}
 						}
+						// else {
+						//	System.out.println("length not ok: " + entry.getSize());
+						// }
+						// Close stream from url
+						inStream.close();
 					}
-					//else {
-					//	System.out.println("length not ok: " + entry.getSize());
-					//}
-					// Close stream from url
-					inStream.close();
 
 					if (entryOk)
 					{
 						// Loop over all points in track, try to apply altitude from array
-						for (int p=0; p<track.getNumPoints(); p++)
+						for (int p = 0; p < _track.getNumPoints(); p++)
 						{
-							DataPoint point = track.getPoint(p);
-							if (!point.hasAltitude() || (inOverwriteZeros && point.getAltitude().getValue() == 0)) {
+							DataPoint point = _track.getPoint(p);
+							if (!point.hasAltitude()
+								|| (inOverwriteZeros && point.getAltitude().getValue() == 0))
+							{
 								if (new SrtmTile(point).equals(tile))
 								{
 									double x = (point.getLongitude().getDouble() - tile.getLongitude()) * 1200;
 									double y = 1201 - (point.getLatitude().getDouble() - tile.getLatitude()) * 1200;
 									int idx1 = ((int)y)*1201 + (int)x;
-									try {
+									try
+									{
 										int[] fouralts = {heights[idx1], heights[idx1+1], heights[idx1-1201], heights[idx1-1200]};
 										int numVoids = (fouralts[0]==VOID_VAL?1:0) + (fouralts[1]==VOID_VAL?1:0)
 											+ (fouralts[2]==VOID_VAL?1:0) + (fouralts[3]==VOID_VAL?1:0);
@@ -186,33 +235,43 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
 											case 3: altitude = averageNonVoid(fouralts); break;
 											default: altitude = VOID_VAL;
 										}
-										if (altitude != VOID_VAL) {
+										if (altitude != VOID_VAL)
+										{
 											point.setFieldValue(Field.ALTITUDE, ""+altitude, false);
+											// depending on settings, this value may have been added as feet, we need to force metres
+											point.getAltitude().reset(new Altitude((int)altitude, UnitSetLibrary.UNITS_METRES));
 											numAltitudesFound++;
 										}
 									}
 									catch (ArrayIndexOutOfBoundsException obe) {
-										//System.err.println("lat=" + point.getLatitude().getDouble() + ", x=" + x + ", y=" + y + ", idx=" + idx1);
+										// System.err.println("lat=" + point.getLatitude().getDouble() + ", x=" + x + ", y=" + y + ", idx=" + idx1);
 									}
 								}
 							}
 						}
 					}
 				}
-				catch (IOException ioe) {
-					errorMessage = ioe.getClass().getName() + " - " + ioe.getMessage();
+				catch (IOException ioe) {errorMessage = ioe.getClass().getName() + " - " + ioe.getMessage();
 				}
 			}
 		}
+
 		_progress.dispose();
-		if (_progress.isCancelled()) {return;}
+		if (_progress.isCancelled()) {
+			return;
+		}
+
 		if (numAltitudesFound > 0)
 		{
 			// Inform app including undo information
-			track.requestRescale();
+			_track.requestRescale();
 			UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_ADDED_OR_REMOVED);
-			_app.completeFunction(undo, I18nManager.getText("confirm.lookupsrtm1") + " " + numAltitudesFound
-				+ " " + I18nManager.getText("confirm.lookupsrtm2"));
+			// Don't update app if we're doing another track
+			if (_normalTrack)
+			{
+				_app.completeFunction(undo,
+					I18nManager.getTextWithNumber("confirm.lookupsrtm", numAltitudesFound));
+			}
 		}
 		else if (errorMessage != null) {
 			_app.showErrorMessageNoLookup(getNameKey(), errorMessage);
@@ -226,6 +285,35 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
 	}
 
 	/**
+	 * See whether the SRTM file is already available locally first, then try online
+	 * @param inUrl URL for online resource
+	 * @return ZipInputStream either on the local file or on the downloaded zip file
+	 */
+	private ZipInputStream getStreamToHgtFile(URL inUrl)
+	throws IOException
+	{
+		String diskCachePath = Config.getConfigString(Config.KEY_DISK_CACHE);
+		if (diskCachePath != null)
+		{
+			File srtmDir = new File(diskCachePath, "srtm");
+			if (srtmDir.exists() && srtmDir.isDirectory() && srtmDir.canRead())
+			{
+				File srtmFile = new File(srtmDir, new File(inUrl.getFile()).getName());
+				if (srtmFile.exists() && srtmFile.isFile() && srtmFile.canRead())
+				{
+					// System.out.println("Lookup: Using file " + srtmFile.getAbsolutePath());
+					// File found, use this one
+					return new ZipInputStream(new FileInputStream(srtmFile));
+				}
+			}
+		}
+		// System.out.println("Lookup: Trying online: " + inUrl.toString());
+		_hadToDownload = true;
+		// MAYBE: Only download if we're in online mode?
+		return new ZipInputStream(inUrl.openStream());
+	}
+
+	/**
 	 * Perform a bilinear interpolation on the given altitude array
 	 * @param inAltitudes array of four altitude values on corners of square (bl, br, tl, tr)
 	 * @param inX x coordinate
@@ -249,7 +337,8 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
 	private static int[] fixVoid(int[] inAltitudes)
 	{
 		int[] fixed = new int[inAltitudes.length];
-		for (int i=0; i<inAltitudes.length; i++) {
+		for (int i = 0; i < inAltitudes.length; i++)
+		{
 			if (inAltitudes[i] == VOID_VAL) {
 				fixed[i] = (int) Math.round(averageNonVoid(inAltitudes));
 			}
@@ -269,8 +358,10 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
 	{
 		double totalAltitude = 0.0;
 		int numAlts = 0;
-		for (int i=0; i<inAltitudes.length; i++) {
-			if (inAltitudes[i] != VOID_VAL) {
+		for (int i = 0; i < inAltitudes.length; i++)
+		{
+			if (inAltitudes[i] != VOID_VAL)
+			{
 				totalAltitude += inAltitudes[i];
 				numAlts++;
 			}
@@ -278,4 +369,12 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
 		if (numAlts < 1) {return VOID_VAL;}
 		return totalAltitude / numAlts;
 	}
+
+	/**
+	 * @return true if a thread is currently running
+	 */
+	public boolean isRunning()
+	{
+		return _running;
+	}
 }
diff --git a/tim/prune/function/srtm/SrtmTile.java b/tim/prune/function/srtm/SrtmTile.java
index d7ab38f..301bbaf 100644
--- a/tim/prune/function/srtm/SrtmTile.java
+++ b/tim/prune/function/srtm/SrtmTile.java
@@ -10,7 +10,7 @@ public class SrtmTile
 {
 	/** Latitude in degrees north/south */
 	private int _latitude = 0;
-	/** Longitude ini degrees east/west */
+	/** Longitude in degrees east/west */
 	private int _longitude = 0;
 
 	/**
@@ -26,6 +26,17 @@ public class SrtmTile
 	}
 
 	/**
+	 * Constructor working out the tile for a single point
+	 * @param inLatitude latitude in degrees
+	 * @param inLongitude longitude in degrees
+	 */
+	public SrtmTile(int inLatitude, int inLongitude)
+	{
+		_latitude = inLatitude;
+		_longitude = inLongitude;
+	}
+
+	/**
 	 * Check for equality
 	 * @param inOther other tile object
 	 * @return true if both represent same tile
diff --git a/tim/prune/function/weather/GetWeatherForecastFunction.java b/tim/prune/function/weather/GetWeatherForecastFunction.java
new file mode 100644
index 0000000..7b6f8c7
--- /dev/null
+++ b/tim/prune/function/weather/GetWeatherForecastFunction.java
@@ -0,0 +1,480 @@
+package tim.prune.function.weather;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.table.TableCellRenderer;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.GpsPrune;
+import tim.prune.I18nManager;
+import tim.prune.data.DataPoint;
+import tim.prune.data.NumberUtils;
+import tim.prune.data.Track;
+import tim.prune.function.browser.BrowserLauncher;
+
+/**
+ * Function to display a weather forecast for the current location
+ * using the services of openweathermap.org
+ */
+public class GetWeatherForecastFunction extends GenericFunction implements Runnable
+{
+	/** Dialog object */
+	private JDialog _dialog = null;
+	/** Label for location */
+	private JLabel _locationLabel = null;
+	/** Label for the forecast update time */
+	private JLabel _updateTimeLabel = null;
+	/** Label for the sunrise and sunset times */
+	private JLabel _sunriseLabel = null;
+	/** Radio button for selecting current weather */
+	private JRadioButton _currentForecastRadio = null;
+	/** Radio button for selecting daily forecasts */
+	private JRadioButton _dailyForecastRadio = null;
+	/** Dropdown for selecting celsius / fahrenheit */
+	private JComboBox<String> _tempUnitsDropdown = null;
+	/** Table to hold the forecasts */
+	private JTable _forecastsTable = null;
+	/** Table model */
+	private WeatherTableModel _tableModel = new WeatherTableModel();
+	/** Set of previously obtained results, to avoid repeating calls */
+	private ResultSet _resultSet = new ResultSet();
+	/** Location id obtained from current forecast */
+	private String _locationId = null;
+	/** Flag to show that forecast is currently running, don't start another */
+	private boolean _isRunning = false;
+
+	/** True to just simulate the calls and read files instead, false to call real API */
+	private static final boolean SIMULATE_WITH_FILES = false;
+
+
+	/**
+	 * Inner class to pass results asynchronously to the table model
+	 */
+	private class ResultUpdater implements Runnable
+	{
+		private WeatherResults _results;
+		public ResultUpdater(WeatherResults inResults) {
+			_results = inResults;
+		}
+		public void run() {
+			_tableModel.setResults(_results);
+			adjustTable();
+		}
+	}
+
+
+	/** Constructor */
+	public GetWeatherForecastFunction(App inApp)
+	{
+		super(inApp);
+	}
+
+	/** @return name key */
+	public String getNameKey() {
+		return "function.getweatherforecast";
+	}
+
+	/**
+	 * Begin the function
+	 */
+	public void begin()
+	{
+		// Initialise dialog, show empty list
+		if (_dialog == null)
+		{
+			_dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+			_dialog.setLocationRelativeTo(_parentFrame);
+			_dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+			_dialog.getContentPane().add(makeDialogComponents());
+			_dialog.pack();
+		}
+		// Clear results
+		_locationId = null;
+		_tableModel.clear();
+		_locationLabel.setText(I18nManager.getText("confirm.running"));
+		_updateTimeLabel.setText("");
+		_sunriseLabel.setText("");
+		_currentForecastRadio.setSelected(true);
+
+		// Start new thread to load list asynchronously
+		new Thread(this).start();
+
+		_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(0, 4));
+
+		JPanel topPanel = new JPanel();
+		topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
+		_locationLabel = new JLabel(I18nManager.getText("confirm.running"));
+		_locationLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+		topPanel.add(_locationLabel);
+		_updateTimeLabel = new JLabel(" ");
+		_updateTimeLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+		topPanel.add(_updateTimeLabel);
+		_sunriseLabel = new JLabel(" ");
+		_sunriseLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+		topPanel.add(_sunriseLabel);
+		JPanel radioPanel = new JPanel();
+		radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.X_AXIS));
+		radioPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+		ButtonGroup forecastTypeGroup = new ButtonGroup();
+		_currentForecastRadio = new JRadioButton(I18nManager.getText("dialog.weather.currentforecast"));
+		_dailyForecastRadio = new JRadioButton(I18nManager.getText("dialog.weather.dailyforecast"));
+		JRadioButton threeHourlyRadio = new JRadioButton(I18nManager.getText("dialog.weather.3hourlyforecast"));
+		forecastTypeGroup.add(_currentForecastRadio);
+		forecastTypeGroup.add(_dailyForecastRadio);
+		forecastTypeGroup.add(threeHourlyRadio);
+		radioPanel.add(_currentForecastRadio);
+		radioPanel.add(_dailyForecastRadio);
+		radioPanel.add(threeHourlyRadio);
+		_currentForecastRadio.setSelected(true);
+		ActionListener radioListener = new ActionListener() {
+			public void actionPerformed(ActionEvent arg0) {
+				if (!_isRunning) new Thread(GetWeatherForecastFunction.this).start();
+			}
+		};
+		_currentForecastRadio.addActionListener(radioListener);
+		_dailyForecastRadio.addActionListener(radioListener);
+		threeHourlyRadio.addActionListener(radioListener);
+		radioPanel.add(Box.createHorizontalGlue());
+		radioPanel.add(Box.createHorizontalStrut(40));
+
+		// Dropdown for temperature units
+		radioPanel.add(new JLabel(I18nManager.getText("dialog.weather.temperatureunits") + ": "));
+		_tempUnitsDropdown = new JComboBox<String>(new String[] {
+			I18nManager.getText("units.degreescelsius"), I18nManager.getText("units.degreesfahrenheit")
+		});
+		_tempUnitsDropdown.setMaximumSize(_tempUnitsDropdown.getPreferredSize());
+		_tempUnitsDropdown.addActionListener(radioListener);
+		radioPanel.add(_tempUnitsDropdown);
+		radioPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+		topPanel.add(radioPanel);
+		dialogPanel.add(topPanel, BorderLayout.NORTH);
+
+		final IconRenderer iconRenderer = new IconRenderer();
+		_forecastsTable = new JTable(_tableModel)
+		{
+			public TableCellRenderer getCellRenderer(int row, int column) {
+				if ((row == WeatherTableModel.ROW_ICON)) {
+					return iconRenderer;
+				}
+				return super.getCellRenderer(row, column);
+			}
+		};
+		_forecastsTable.setRowSelectionAllowed(false);
+		_forecastsTable.setRowHeight(2, 55); // make just that row high enough to see icons
+		_forecastsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+		_forecastsTable.getTableHeader().setReorderingAllowed(false);
+		_forecastsTable.setShowHorizontalLines(false);
+
+		JScrollPane scroller = new JScrollPane(_forecastsTable);
+		scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
+		scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
+		scroller.setPreferredSize(new Dimension(500, 210));
+		scroller.getViewport().setBackground(Color.white);
+
+		dialogPanel.add(scroller, BorderLayout.CENTER);
+
+		// button panel at bottom
+		JPanel buttonPanel = new JPanel();
+		buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+		JButton launchButton = new JButton(I18nManager.getText("button.showwebpage"));
+		launchButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent arg0) {
+				BrowserLauncher.launchBrowser("http://openweathermap.org/city/" + (_locationId == null ? "" : _locationId));
+			}
+		});
+		buttonPanel.add(launchButton);
+		// close
+		JButton closeButton = new JButton(I18nManager.getText("button.close"));
+		closeButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				_dialog.dispose();
+			}
+		});
+		buttonPanel.add(closeButton);
+		// Add a holder panel with a static label to credit openweathermap
+		JPanel southPanel = new JPanel();
+		southPanel.setLayout(new BoxLayout(southPanel, BoxLayout.Y_AXIS));
+		southPanel.add(new JLabel(I18nManager.getText("dialog.weather.creditnotice")));
+		southPanel.add(buttonPanel);
+		dialogPanel.add(southPanel, BorderLayout.SOUTH);
+		dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+		return dialogPanel;
+	}
+
+	/**
+	 * Get the weather forecast in a separate thread
+	 */
+	public void run()
+	{
+		if (_isRunning) {return;} // don't run twice
+		_isRunning = true;
+
+		// Are we getting the current details, or getting a forecast?
+		final boolean isCurrent = _locationId == null || _currentForecastRadio.isSelected();
+		final boolean isDailyForecast = _dailyForecastRadio.isSelected() && !isCurrent;
+		final boolean isHourlyForecast = !isCurrent && !isDailyForecast;
+		final boolean isUsingCelsius  = _tempUnitsDropdown.getSelectedIndex() == 0;
+
+		// Have we got these results already?  Look in store
+		WeatherResults results = _resultSet.getWeather(_locationId, isCurrent, isDailyForecast, isHourlyForecast, isUsingCelsius);
+		if (results == null)
+		{
+			if (isCurrent)
+			{
+				// Get the current details using either lat/long or locationId
+				results = getCurrentWeather(isUsingCelsius);
+				// If the current radio isn't selected, select it
+				if (!_currentForecastRadio.isSelected()) {
+					_currentForecastRadio.setSelected(true);
+				}
+			}
+			else
+			{
+				// Get the specified forecast using the retrieved locationId
+				results = getWeatherForecast(isDailyForecast, isUsingCelsius);
+			}
+			// If it's a valid answer, store it for later
+			if (results != null)
+			{
+				_resultSet.setWeather(results, _locationId, isCurrent, isDailyForecast, isHourlyForecast, isUsingCelsius);
+			}
+		}
+
+		// update table contents and labels
+		if (results != null)
+		{
+			SwingUtilities.invokeLater(new ResultUpdater(results));
+			_locationLabel.setText(I18nManager.getText("dialog.weather.location") + ": " + results.getLocationName());
+			final String ut = results.getUpdateTime();
+			_updateTimeLabel.setText(I18nManager.getText("dialog.weather.update") + ": " + (ut == null ? "" : ut));
+			if (results.getSunriseTime() != null && results.getSunsetTime() != null)
+			{
+				_sunriseLabel.setText(I18nManager.getText("dialog.weather.sunrise") + ": " + results.getSunriseTime()
+					+ ", " + I18nManager.getText("dialog.weather.sunset") + ": " + results.getSunsetTime());
+			}
+			else {
+				_sunriseLabel.setText("");
+			}
+		}
+
+		// finished running
+		_isRunning = false;
+	}
+
+
+	/**
+	 * Adjust the column widths and row heights to fit the displayed data
+	 */
+	private void adjustTable()
+	{
+		if (!_tableModel.isEmpty())
+		{
+			// adjust column widths for all columns
+			for (int i=0; i<_forecastsTable.getColumnCount(); i++)
+			{
+				double maxWidth = 0.0;
+				for (int j=0; j<_forecastsTable.getRowCount(); j++)
+				{
+					final String value = _tableModel.getValueAt(j, i).toString();
+					maxWidth = Math.max(maxWidth, _forecastsTable.getCellRenderer(0, 0).getTableCellRendererComponent(
+						_forecastsTable, value, false, false, 0, 0).getPreferredSize().getWidth());
+				}
+				_forecastsTable.getColumnModel().getColumn(i).setMinWidth((int) maxWidth + 2);
+			}
+			// Set minimum row heights
+			final int labelHeight = (int) (_forecastsTable.getCellRenderer(0, 0).getTableCellRendererComponent(
+				_forecastsTable, "M", false, false, 0, 0).getMinimumSize().getHeight() * 1.2f + 4);
+			for (int i=0; i<_forecastsTable.getRowCount(); i++)
+			{
+				if (i == WeatherTableModel.ROW_ICON) {
+					_forecastsTable.setRowHeight(i, 55);
+				}
+				else {
+					_forecastsTable.setRowHeight(i, labelHeight);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Get the current weather using the lat/long and populate _results
+	 * @param inUseCelsius true for celsius, false for fahrenheit
+	 * @return weather results
+	 */
+	private WeatherResults getCurrentWeather(boolean inUseCelsius)
+	{
+		final Track track = _app.getTrackInfo().getTrack();
+		if (track.getNumPoints() < 1) {return null;}
+		// Get coordinates to lookup
+		double lat = 0.0, lon = 0.0;
+		// See if a point is selected, if so use that
+		DataPoint currPoint = _app.getTrackInfo().getCurrentPoint();
+		if (currPoint != null)
+		{
+			// Use selected point
+			lat = currPoint.getLatitude().getDouble();
+			lon = currPoint.getLongitude().getDouble();
+		}
+		else
+		{
+			lat = track.getLatRange().getMidValue();
+			lon = track.getLonRange().getMidValue();
+		}
+
+		InputStream inStream = null;
+		// Build url either with coordinates or with location id if available
+		final String urlString = "http://api.openweathermap.org/data/2.5/weather?"
+			+ (_locationId == null ? ("lat=" + NumberUtils.formatNumberUk(lat, 5) + "&lon=" + NumberUtils.formatNumberUk(lon, 5))
+				: ("id=" + _locationId))
+			+ "&lang=" + I18nManager.getText("openweathermap.lang")
+			+ "&mode=xml&units=" + (inUseCelsius ? "metric" : "imperial");
+		// System.out.println(urlString);
+
+		// Parse the returned XML with a special handler
+		OWMCurrentHandler xmlHandler = new OWMCurrentHandler();
+		try
+		{
+			URL url = new URL(urlString);
+			SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
+			// DEBUG: Simulate the call in case of no network connection
+			if (SIMULATE_WITH_FILES)
+			{
+				inStream = new FileInputStream(new File("tim/prune/test/examplecurrentweather.xml"));
+				try {
+					Thread.sleep(2000);
+				} catch (InterruptedException tie) {}
+			}
+			else
+			{
+				URLConnection conn = url.openConnection();
+				conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
+				inStream = conn.getInputStream();
+			}
+
+			saxParser.parse(inStream, xmlHandler);
+		}
+		catch (Exception e)
+		{
+			// Show error message but don't close dialog
+			_app.showErrorMessageNoLookup(getNameKey(), e.getClass().getName() + " - " + e.getMessage());
+			_isRunning = false;
+			return null;
+		}
+		// Close stream and ignore errors
+		try {
+			inStream.close();
+		} catch (Exception e) {}
+
+		// Save the location id
+		if (xmlHandler.getLocationId() != null) {
+			_locationId = xmlHandler.getLocationId();
+		}
+		// Get the results from the handler and return
+		WeatherResults results = new WeatherResults();
+		results.setForecast(xmlHandler.getCurrentWeather());
+		results.setLocationName(xmlHandler.getLocationName());
+		results.setUpdateTime(xmlHandler.getUpdateTime());
+		results.setSunriseSunsetTimes(xmlHandler.getSunriseTime(), xmlHandler.getSunsetTime());
+		results.setTempsCelsius(inUseCelsius);
+		return results;
+	}
+
+
+	/**
+	 * Get the weather forecast for the current location id and populate in _results
+	 * @param inDaily true for daily, false for 3-hourly
+	 * @param inCelsius true for celsius, false for fahrenheit
+	 * @return weather results
+	 */
+	private WeatherResults getWeatherForecast(boolean inDaily, boolean inCelsius)
+	{
+		InputStream inStream = null;
+		// Build URL
+		final String forecastCount = inDaily ? "8" : "3";
+		final String urlString = "http://api.openweathermap.org/data/2.5/forecast"
+			+ (inDaily ? "/daily" : "") + "?id=" + _locationId + "&lang=" + I18nManager.getText("openweathermap.lang")
+			+ "&mode=xml&units=" + (inCelsius ? "metric" : "imperial") + "&cnt=" + forecastCount;
+		// System.out.println(urlString);
+
+		// Parse the returned XML with a special handler
+		OWMForecastHandler xmlHandler = new OWMForecastHandler();
+		try
+		{
+			URL url = new URL(urlString);
+			SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
+			// DEBUG: Simulate the call in case of no network connection
+			if (SIMULATE_WITH_FILES)
+			{
+				inStream = new FileInputStream(new File("tim/prune/test/exampleweatherforecast.xml"));
+				try {
+					Thread.sleep(2000);
+				} catch (InterruptedException tie) {}
+			}
+			else
+			{
+				URLConnection conn = url.openConnection();
+				conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
+				inStream = conn.getInputStream();
+			}
+
+			saxParser.parse(inStream, xmlHandler);
+		}
+		catch (Exception e)
+		{
+			// Show error message but don't close dialog
+			_app.showErrorMessageNoLookup(getNameKey(), e.getClass().getName() + " - " + e.getMessage());
+			_isRunning = false;
+			return null;
+		}
+		// Close stream and ignore errors
+		try {
+			inStream.close();
+		} catch (Exception e) {}
+
+		// Get results from handler, put in model
+		WeatherResults results = new WeatherResults();
+		results.setForecasts(xmlHandler.getForecasts());
+		results.setLocationName(xmlHandler.getLocationName());
+		results.setUpdateTime(xmlHandler.getUpdateTime());
+		results.setTempsCelsius(inCelsius);
+		return results;
+	}
+}
diff --git a/tim/prune/function/weather/IconRenderer.java b/tim/prune/function/weather/IconRenderer.java
new file mode 100644
index 0000000..caeb184
--- /dev/null
+++ b/tim/prune/function/weather/IconRenderer.java
@@ -0,0 +1,37 @@
+package tim.prune.function.weather;
+
+import java.awt.Component;
+import java.awt.Dimension;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.table.TableCellRenderer;
+
+import tim.prune.gui.IconManager;
+
+/**
+ * Class to render the weather icons in the table
+ */
+public class IconRenderer extends JLabel implements TableCellRenderer
+{
+	/** Get the renderer component for the given row, column and value */
+	public Component getTableCellRendererComponent(JTable inTable, Object inValue, boolean inIsSelected,
+		boolean inHasFocus, int inRow, int inColumn)
+	{
+		if (inValue != null) {
+			setIcon(IconManager.getImageIcon("weather-" + inValue.toString()));
+			setHorizontalAlignment(SwingConstants.CENTER);
+		}
+		else {
+			setIcon(null);
+			setText("");
+		}
+		return this;
+	}
+
+	/** Override the minimum size method */
+	public Dimension getMinimumSize() {
+		return new Dimension(52, 52);
+	}
+}
diff --git a/tim/prune/function/weather/OWMCurrentHandler.java b/tim/prune/function/weather/OWMCurrentHandler.java
new file mode 100644
index 0000000..5380ba7
--- /dev/null
+++ b/tim/prune/function/weather/OWMCurrentHandler.java
@@ -0,0 +1,92 @@
+package tim.prune.function.weather;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+
+/**
+ * XML handler for dealing with the XML of current weather reports
+ * returned from openweathermap.org (forecasts have different structure)
+ */
+public class OWMCurrentHandler extends DefaultHandler
+{
+	/** The location name */
+	private String _locName = null;
+	/** The location id */
+	private String _locId = null;
+	/** The last update time */
+	private String _updateTime = null;
+	/** Sunrise and sunset times */
+	private String _sunriseTime = null, _sunsetTime = null;
+	/** The currently open forecast */
+	private SingleForecast _forecast = new SingleForecast();
+
+
+	/**
+	 * React to the start of an XML tag
+	 */
+	public void startElement(String inUri, String inLocalName, String inTagName,
+		Attributes inAttributes) throws SAXException
+	{
+		if (inTagName.equals("city")) {
+			_locName = inAttributes.getValue("name");
+			_locId   = inAttributes.getValue("id");
+		}
+		else if (inTagName.equals("weather")) {
+			// numeric code, owm image name, description
+			_forecast.setSymbol(inAttributes.getValue("number"), inAttributes.getValue("icon"), inAttributes.getValue("value"));
+		}
+		else if (inTagName.equals("speed")) {
+			_forecast.setWindDesc(inAttributes.getValue("name"));
+		}
+		else if (inTagName.equals("temperature"))
+		{
+			String currTemp = inAttributes.getValue("value");
+			_forecast.setTemps(currTemp, currTemp);
+			// We can ignore the min and max here
+		}
+		else if (inTagName.equals("humidity")) {
+			_forecast.setHumidity(inAttributes.getValue("value") + inAttributes.getValue("unit"));
+		}
+		else if (inTagName.equals("lastupdate")) {
+			_updateTime = inAttributes.getValue("value");
+		}
+		else if (inTagName.equals("sun"))
+		{
+			_sunriseTime = inAttributes.getValue("rise");
+			_sunsetTime  = inAttributes.getValue("set");
+		}
+
+		super.startElement(inUri, inLocalName, inTagName, inAttributes);
+	}
+
+	/** @return location name of forecast */
+	public String getLocationName() {
+		return _locName;
+	}
+
+	/** @return location id of forecast */
+	public String getLocationId() {
+		return _locId;
+	}
+
+	/** @return update time of report */
+	public String getUpdateTime() {
+		return _updateTime;
+	}
+
+	/** @return current weather conditions */
+	public SingleForecast getCurrentWeather() {
+		return _forecast;
+	}
+
+	/** @return sunrise time as 2013-07-25T03:55:14 */
+	public String getSunriseTime() {
+		return _sunriseTime;
+	}
+	/** @return sunset time as 2013-07-25T19:07:25 */
+	public String getSunsetTime() {
+		return _sunsetTime;
+	}
+}
diff --git a/tim/prune/function/weather/OWMForecastHandler.java b/tim/prune/function/weather/OWMForecastHandler.java
new file mode 100644
index 0000000..93bcc12
--- /dev/null
+++ b/tim/prune/function/weather/OWMForecastHandler.java
@@ -0,0 +1,102 @@
+package tim.prune.function.weather;
+
+import java.util.ArrayList;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+
+/**
+ * XML handler for dealing with the XML of weather forecasts
+ * returned from openweathermap.org (current weather has different structure)
+ */
+public class OWMForecastHandler extends DefaultHandler
+{
+	private String _value = null;
+	/** The location name */
+	private String _locName = null;
+	/** The forecast update time */
+	private String _updateTime = null;
+	/** The currently open forecast */
+	private SingleForecast _forecast = null;
+	/** List of all the forecasts found so far */
+	private ArrayList<SingleForecast> _forecastList = new ArrayList<SingleForecast>();
+
+
+	/**
+	 * React to the start of an XML tag
+	 */
+	public void startElement(String inUri, String inLocalName, String inTagName,
+		Attributes inAttributes) throws SAXException
+	{
+		if (inTagName.equals("time")) { // start of a new forecast
+			_forecast = new SingleForecast();
+			// date, timefrom, timeto
+			_forecast.setTime(inAttributes.getValue("day"), inAttributes.getValue("from"), inAttributes.getValue("to"));
+		}
+		else if (inTagName.equals("symbol")) {
+			// numeric code, owm image name, description
+			_forecast.setSymbol(inAttributes.getValue("number"), inAttributes.getValue("var"), inAttributes.getValue("name"));
+		}
+		else if (inTagName.equals("windSpeed")) {
+			_forecast.setWindDesc(inAttributes.getValue("name"));
+		}
+		else if (inTagName.equals("temperature")) {
+			_forecast.setTemps(inAttributes.getValue("min"), inAttributes.getValue("max"));
+		}
+		else if (inTagName.equals("humidity")) {
+			_forecast.setHumidity(inAttributes.getValue("value") + inAttributes.getValue("unit"));
+		}
+		_value = null;
+		super.startElement(inUri, inLocalName, inTagName, inAttributes);
+	}
+
+	/**
+	 * React to the end of an XML tag
+	 */
+	public void endElement(String inUri, String inLocalName, String inTagName)
+	throws SAXException
+	{
+		if (inTagName.equals("name")) {
+			_locName = _value;
+		}
+		else if (inTagName.equals("lastupdate")) {
+			_updateTime = _value;
+		}
+		else if (inTagName.equals("time"))
+		{
+			// End of a time tag, add the current forecast to the list
+			_forecastList.add(_forecast);
+		}
+		super.endElement(inUri, inLocalName, inTagName);
+	}
+
+	/**
+	 * React to characters received inside tags
+	 */
+	public void characters(char[] inCh, int inStart, int inLength)
+	throws SAXException
+	{
+		String value = new String(inCh, inStart, inLength);
+		_value = (_value==null?value:_value+value);
+		super.characters(inCh, inStart, inLength);
+	}
+
+	/** @return location name of forecast */
+	public String getLocationName() {
+		return _locName;
+	}
+
+	/** @return update time of forecast */
+	public String getUpdateTime() {
+		return _updateTime;
+	}
+
+	/**
+	 * @return the list of forecasts
+	 */
+	public ArrayList<SingleForecast> getForecasts() {
+		return _forecastList;
+	}
+}
diff --git a/tim/prune/function/weather/ResultSet.java b/tim/prune/function/weather/ResultSet.java
new file mode 100644
index 0000000..664d7ae
--- /dev/null
+++ b/tim/prune/function/weather/ResultSet.java
@@ -0,0 +1,83 @@
+package tim.prune.function.weather;
+
+/**
+ * Class to hold a set of (up to six) weather results,
+ * so that they don't have to be downloaded again
+ */
+public class ResultSet
+{
+	/** Array of six results */
+	private WeatherResults[] _results = new WeatherResults[6];
+	/** Location id for which these results apply */
+	private String _locationId = null;
+
+	/**
+	 * Clear the array, forget all results
+	 */
+	private void clear()
+	{
+		for (int i=0; i<6; i++) {
+			_results[i] = null;
+		}
+	}
+
+	/**
+	 * Get the specified weather results, if available
+	 * @param inLocationId location id
+	 * @param inCurrent true to get the current weather
+	 * @param inDaily true to get the daily forecast
+	 * @param inHourly true to get the three-hourly forecast
+	 * @param inCelsius true to get celsius
+	 * @return weather results, or null if not available
+	 */
+	public WeatherResults getWeather(String inLocationId,
+		boolean inCurrent, boolean inDaily, boolean inHourly, boolean inCelsius)
+	{
+		// Check location
+		if (inLocationId == null || _locationId == null || !inLocationId.equals(_locationId)) {
+			return null;
+		}
+		// check forecast type
+		final int numTypesGiven = (inCurrent ? 1 : 0) + (inDaily ? 1 : 0) + (inHourly ? 1 : 0);
+		if (numTypesGiven != 1) {
+			System.err.println("getWeather, numtypesgiven = " + numTypesGiven);
+			return null; // can't ask for more or less than one type
+		}
+		// Pull out from array
+		final int index = (inCurrent ? 0 : (inDaily ? 2 : 4)) + (inCelsius ? 1 : 0);
+		return _results[index];
+	}
+
+	/**
+	 * Store the given weather results
+	 * @param inResults results object
+	 * @param inLocationId location id
+	 * @param inCurrent true if this is the current weather
+	 * @param inDaily true if this is the daily forecast
+	 * @param inHourly true if this is the three-hourly forecast
+	 * @param inCelsius true if numbers are celsius
+	 */
+	public void setWeather(WeatherResults inResults, String inLocationId,
+		boolean inCurrent, boolean inDaily, boolean inHourly, boolean inCelsius)
+	{
+		// Check location
+		if (inLocationId == null || inLocationId.equals("")) {
+			return;
+		}
+		if (_locationId == null || !inLocationId.equals(_locationId))
+		{
+			// coordinates have changed
+			clear();
+			_locationId = inLocationId;
+		}
+		// check forecast type
+		final int numTypesGiven = (inCurrent ? 1 : 0) + (inDaily ? 1 : 0) + (inHourly ? 1 : 0);
+		if (numTypesGiven != 1) {
+			System.err.println("setWeather, numtypesgiven = " + numTypesGiven);
+			return; // can't set more or less than one type
+		}
+		// Store in array
+		final int index = (inCurrent ? 0 : (inDaily ? 2 : 4)) + (inCelsius ? 1 : 0);
+		_results[index] = inResults;
+	}
+}
diff --git a/tim/prune/function/weather/SingleForecast.java b/tim/prune/function/weather/SingleForecast.java
new file mode 100644
index 0000000..cfc1947
--- /dev/null
+++ b/tim/prune/function/weather/SingleForecast.java
@@ -0,0 +1,185 @@
+package tim.prune.function.weather;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+/**
+ * Class to represent a weather forecast
+ * for a single day or a 3-hour period
+ */
+public class SingleForecast
+{
+	private String _date = null;
+	private String _dayDescKey = null;
+	private String _timeFrom = null, _timeTo = null;
+	private String _imageName = null;
+	private String _desc = null;
+	private String _tempString = null;
+	private String _humidity = null;
+	private String _windDesc = null;
+
+	/** For getting today's and tomorrow's dates */
+	private static SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
+
+	/** Set the time of the forecast */
+	public void setTime(String inDate, String inTimeFrom, String inTimeTo)
+	{
+		_date = inDate;
+		if (inTimeFrom != null && inTimeFrom.length() > 10
+			&& inTimeTo != null && inTimeTo.length() > 10)
+		{
+			_date = inTimeFrom.substring(0, 10);
+			_timeFrom = inTimeFrom.substring(11, 16);
+			_timeTo   = inTimeTo.substring(11, 16);
+		}
+		_dayDescKey = getDayDescriptionKey(_date);
+		// System.out.println(_date + " is " + _dayDescKey);
+	}
+
+	/**
+	 * Set the symbol details
+	 */
+	public void setSymbol(String inNumber, String inImageCode, String inDesc)
+	{
+		_imageName = getIconName(inNumber, inImageCode);
+		// System.out.println("For number " + inNumber + "(" + inDesc + ") and code " + inImageCode + ", the symbol is " + _imageName);
+		_desc = inDesc;
+	}
+
+	/**
+	 * Set the minimum and maximum temperatures (will be rounded to nearest int)
+	 */
+	public void setTemps(String inMin, String inMax)
+	{
+		String tempMin = null, tempMax = null;
+		try {
+			tempMin = "" + Math.round(Double.parseDouble(inMin));
+		} catch (Exception e) {}; // tempMin stays null if temp can't be parsed
+		try {
+			tempMax = "" + Math.round(Double.parseDouble(inMax));
+		} catch (Exception e) {}; // tempMax stays null if temp can't be parsed
+
+		_tempString = tempMin;
+		if (tempMin != null && tempMax != null) {
+			if (!tempMin.equals(tempMax))
+			{
+				if (tempMin.charAt(0) == '-' && tempMax.charAt(0) != '-' && tempMax.charAt(0) != '0') {
+					// min temp is negative, max is positive, so add a + to the max
+					tempMax = "+" + tempMax;
+				}
+				_tempString = tempMin  + " — " + tempMax;
+			}
+		}
+		else if (tempMax != null) {
+			_tempString = tempMax;
+		}
+	}
+
+	/** Set humidity */
+	public void setHumidity(String inHumidity) {
+		_humidity = inHumidity;
+	}
+
+	/** Set description of wind */
+	public void setWindDesc(String inDesc) {
+		_windDesc = inDesc;
+	}
+
+	/**
+	 * Get the name of the image file for the given weather report
+	 * @param inCode numeric three-digit code, as string
+	 * @param inImage filename as given by openweather (just used for day/night)
+	 * @return image file using GpsPrune's icons
+	 */
+	public static String getIconName(String inCode, String inImage)
+	{
+		final boolean daytime = inImage == null || inImage.length() != 3 || inImage.charAt(2) != 'n';
+		final char leadDigit = (inCode == null || inCode.equals("")) ? '0' : inCode.charAt(0);
+		String iconName = null;
+		switch (leadDigit)
+		{
+			case '2':	return "storm.png";
+			case '3':	return "lightrain.png";
+			case '5':
+				iconName = "rain.png";
+				if (inCode.equals("500")) {iconName = "lightrain.png";}
+				else if (inCode.equals("511")) {iconName = "hail.png";}
+				break;
+			case '6':	return "snow.png";
+			case '7':	return "fog.png";
+			case '8':
+				iconName = daytime ? "clouds-day.png" : "clouds-night.png";
+				if (inCode.equals("800")) {iconName = daytime ? "clear-day.png" : "clear-night.png";}
+				else if (inCode.equals("804")) {iconName = "clouds.png";}
+				break;
+			case '9':
+				iconName = "extreme.png";
+				if (inCode.equals("906")) {iconName = "hail.png";}
+				break;
+		}
+		return iconName;
+	}
+
+	/**
+	 * MAYBE: Maybe split off into separate DateFunctions class?
+	 * @param inDate date
+	 * @return day description, such as "today" or "saturday"
+	 */
+	private static String getDayDescriptionKey(String inDate)
+	{
+		if (inDate == null || inDate.length() != 10) {return null;}
+		Calendar cal = Calendar.getInstance();
+		String todaysDate = DATE_FORMATTER.format(cal.getTime());
+		if (inDate.equals(todaysDate)) {return "today";}
+		cal.add(Calendar.DATE, 1);
+		String tomorrowsDate = DATE_FORMATTER.format(cal.getTime());
+		if (inDate.equals(tomorrowsDate)) {return "tomorrow";}
+		// Construct a date with this string and find out its day
+		try
+		{
+			cal.setTime(DATE_FORMATTER.parse(inDate));
+			switch (cal.get(Calendar.DAY_OF_WEEK))
+			{
+				case Calendar.MONDAY   : return "monday";
+				case Calendar.TUESDAY  : return "tuesday";
+				case Calendar.WEDNESDAY : return "wednesday";
+				case Calendar.THURSDAY : return "thursday";
+				case Calendar.FRIDAY   : return "friday";
+				case Calendar.SATURDAY : return "saturday";
+				case Calendar.SUNDAY   : return "sunday";
+			}
+		}
+		catch (ParseException pe) {}
+
+		return "other";
+	}
+
+	/** @return true if there are times present, not just a date */
+	public boolean hasTimes() {
+		return _timeFrom != null && _timeTo != null;
+	}
+	/** @return temperature range */
+	public String getTemps() {
+		return _tempString;
+	}
+
+	/** @return date */
+	public String getDate() {return _date;}
+	/** @return time from */
+	public String getTimeFrom() {return _timeFrom;}
+	/** @return time to */
+	public String getTimeTo() {return _timeTo;}
+	/** @return day description */
+	public String getDayDesc() {return _dayDescKey;}
+
+	/** @return image name */
+	public String getImageName() {return _imageName;}
+	/** @return description */
+	public String getDescription() {return _desc;}
+
+	/** @return humidity */
+	public String getHumidity() {return _humidity;}
+	/** @return wind description */
+	public String getWindDescription() {return _windDesc;}
+}
diff --git a/tim/prune/function/weather/WeatherResults.java b/tim/prune/function/weather/WeatherResults.java
new file mode 100644
index 0000000..10fd363
--- /dev/null
+++ b/tim/prune/function/weather/WeatherResults.java
@@ -0,0 +1,166 @@
+package tim.prune.function.weather;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import tim.prune.gui.DisplayUtils;
+
+
+/**
+ * Model for results of weather forecast from openweathermap.org
+ */
+public class WeatherResults
+{
+	/** List of forecasts */
+	private ArrayList<SingleForecast> _forecastList = new ArrayList<SingleForecast>();
+	/** Flag whether the units are metric (Celsius) or not (Fahrenheit) */
+	private boolean _tempsCelsius = true;
+	/** Location name */
+	private String _locationName = null;
+	/** Last update timestamp */
+	private String _updateTime = null;
+	/** Sunrise and sunset times as HH:MM */
+	private String _sunriseTime = null, _sunsetTime = null;
+
+
+	/**
+	 * Add a single forecast to this model (for the current weather)
+	 * @param inResults current results
+	 */
+	public void setForecast(SingleForecast inResults)
+	{
+		_forecastList.clear();
+		if (inResults != null) {
+			_forecastList.add(inResults);
+		}
+	}
+
+	/**
+	 * Add a list of forecasts to this model
+	 * @param inList list of forecasts to add
+	 */
+	public void setForecasts(ArrayList<SingleForecast> inList)
+	{
+		_forecastList.clear();
+		if (inList != null && inList.size() > 0) {
+			_forecastList.addAll(inList);
+		}
+	}
+
+	/** @return the number of forecasts */
+	public int getNumForecasts()
+	{
+		return _forecastList.size();
+	}
+
+	/**
+	 * @param inIndex index of forecast starting from 0
+	 * @return the specified forecast
+	 */
+	public SingleForecast getForecast(int inIndex)
+	{
+		if (inIndex < 0 || inIndex >= getNumForecasts()) {
+			return null;
+		}
+		return _forecastList.get(inIndex);
+	}
+
+	/**
+	 * Clear the list of forecasts
+	 */
+	public void clear()
+	{
+		_forecastList.clear();
+		_sunriseTime = _sunsetTime = null;
+		_updateTime = null;
+	}
+
+	/**
+	 * @param inCelsius true for celsius, false for fahrenheit
+	 */
+	public void setTempsCelsius(boolean inCelsius)
+	{
+		_tempsCelsius = inCelsius;
+	}
+
+	/** @return true if this forecast uses Celsius */
+	public boolean isCelsius() {
+		return _tempsCelsius;
+	}
+
+	/**
+	 * Set the sunrise and sunset times (only from current weather, not from forecast)
+	 * @param inRiseTime sunrise time as YYYY-MM-DDThh:mm:ss
+	 * @param inSetTime  sunset  time as YYYY-MM-DDThh:mm:ss
+	 */
+	public void setSunriseSunsetTimes(String inRiseTime, String inSetTime)
+	{
+		_sunriseTime = _sunsetTime = null;
+		if (inRiseTime != null && inRiseTime.length() == 19
+			&& inSetTime != null && inSetTime.length() == 19)
+		{
+			// Convert from UTC to system's time zone (not necessarily target's time zone!)
+			_sunriseTime = convertToLocalTimezone(inRiseTime.substring(11, 16));
+			_sunsetTime  = convertToLocalTimezone(inSetTime.substring(11, 16));
+		}
+	}
+
+	/** @return sunrise time as HH:MM */
+	public String getSunriseTime() {
+		return _sunriseTime;
+	}
+	/** @return sunset time as HH:MM */
+	public String getSunsetTime() {
+		return _sunsetTime;
+	}
+
+	/** @param inName location name */
+	public void setLocationName(String inName) {
+		_locationName = inName;
+	}
+
+	/** @return location name */
+	public String getLocationName() {
+		return _locationName;
+	}
+
+	/** @param inTime timestamp of forecast */
+	public void setUpdateTime(String inTime) {
+		_updateTime = inTime;
+	}
+
+	/** @return timestamp of last update */
+	public String getUpdateTime()
+	{
+		return _updateTime;
+	}
+
+	/**
+	 * Convert the given UTC time (HH:MM) into current timezone of computer
+	 * @param inTimeString sunrise/sunset time in UTC (HH:MM)
+	 * @return time in this timezone (HH:MM)
+	 */
+	private static String convertToLocalTimezone(String inTimeString)
+	{
+		if (inTimeString != null && inTimeString.length() == 5)
+		{
+			try
+			{
+				final int hour = Integer.parseInt(inTimeString.substring(0, 2));
+				final int min  = Integer.parseInt(inTimeString.substring(3));
+				Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+				utcCal.set(Calendar.HOUR_OF_DAY, hour);
+				utcCal.set(Calendar.MINUTE, min);
+				// Make a second calendar in the current time zone and apply values
+				Calendar currCal = Calendar.getInstance();
+				currCal.setTimeInMillis(utcCal.getTimeInMillis());
+				return DisplayUtils.makeTimeString(currCal.get(Calendar.HOUR_OF_DAY),
+					currCal.get(Calendar.MINUTE));
+			}
+			catch (NumberFormatException e) {} // ignore, just drop through
+		}
+		// Couldn't be parsed / converted
+		return inTimeString;
+	}
+}
diff --git a/tim/prune/function/weather/WeatherTableModel.java b/tim/prune/function/weather/WeatherTableModel.java
new file mode 100644
index 0000000..35444b6
--- /dev/null
+++ b/tim/prune/function/weather/WeatherTableModel.java
@@ -0,0 +1,124 @@
+package tim.prune.function.weather;
+
+import javax.swing.table.AbstractTableModel;
+
+import tim.prune.I18nManager;
+
+
+/**
+ * Table model for results of weather forecast
+ */
+public class WeatherTableModel extends AbstractTableModel
+{
+	/** Weather results */
+	private WeatherResults _results;
+
+	/** Row indices */
+	public static final int ROW_DAY  = 0;
+	public static final int ROW_DESC = 1;
+	public static final int ROW_WIND = 2;
+	public static final int ROW_ICON = 3;
+	public static final int ROW_TEMP = 4;
+	public static final int ROW_HUMID = 5;
+
+	/** String for degrees Celsius */
+	private static final String UNITS_DEGC = I18nManager.getText("units.degreescelsius.short");
+	/** String for degrees Fahrenheit */
+	private static final String UNITS_DEGF = I18nManager.getText("units.degreesfahrenheit.short");
+
+	/**
+	 * @return column count
+	 */
+	public int getColumnCount()
+	{
+		if (_results == null) {return 0;}
+		return _results.getNumForecasts();
+	}
+
+	/**
+	 * @param inColNum column number
+	 * @return column label for given column
+	 */
+	public String getColumnName(int inColNum)
+	{
+		if (_results != null && inColNum >= 0 && inColNum < getColumnCount())
+		{
+			SingleForecast forecast = _results.getForecast(inColNum);
+			if (!forecast.hasTimes() || forecast.getTimeFrom().startsWith("00")) {
+				return forecast.getDate();
+			}
+			return forecast.getTimeFrom();
+		}
+		return "";
+	}
+
+	/**
+	 * @return number of rows
+	 */
+	public int getRowCount()
+	{
+		return 6;
+	}
+
+	/** @return true if there are no columns */
+	public boolean isEmpty()
+	{
+		return getColumnCount() == 0;
+	}
+
+	/**
+	 * @param inRowNum row number
+	 * @param inColNum column number
+	 * @return cell entry at given row and column
+	 */
+	public Object getValueAt(int inRowNum, int inColNum)
+	{
+		if (inColNum < 0 || inColNum >= getColumnCount()) {return "";}
+		SingleForecast forecast = _results.getForecast(inColNum);
+		if (forecast != null)
+		{
+			switch (inRowNum)
+			{
+				case ROW_DAY:  {
+					final String dayDesc = forecast.getDayDesc() == null ? "now" : forecast.getDayDesc();
+					return buildDisplayString(null, I18nManager.getText("dialog.weather.day." + dayDesc));
+				}
+				case ROW_DESC: return buildDisplayString(null, forecast.getDescription());
+				case ROW_WIND: return buildDisplayString(I18nManager.getText("dialog.weather.wind"), forecast.getWindDescription());
+				case ROW_ICON: return forecast.getImageName();
+				case ROW_TEMP: return buildDisplayString(I18nManager.getText("dialog.weather.temp"), forecast.getTemps()
+					+ (_results.isCelsius() ? UNITS_DEGC : UNITS_DEGF));
+				case ROW_HUMID: return buildDisplayString(I18nManager.getText("dialog.weather.humidity"), forecast.getHumidity());
+			}
+		}
+		return "";
+	}
+
+	/**
+	 * Build a html string from the given title and value
+	 */
+	private static final String buildDisplayString(String inTitle, String inValue)
+	{
+		if (inValue == null) {return null;}
+		return "<html>" + (inTitle == null ? "" : (inTitle + ": "))
+			+ "<big>" + inValue.replaceAll(" ", " ") + "</big></html>";
+	}
+
+	/**
+	 * Set the results
+	 * @param inResults weather results including all forecasts
+	 */
+	public void setResults(WeatherResults inResults)
+	{
+		_results = inResults;
+		fireTableStructureChanged();
+	}
+
+	/**
+	 * Clear the list of forecasts
+	 */
+	public void clear()
+	{
+		setResults(null);
+	}
+}
diff --git a/tim/prune/gui/BaseImageDefinitionPanel.java b/tim/prune/gui/BaseImageDefinitionPanel.java
new file mode 100644
index 0000000..d186bd2
--- /dev/null
+++ b/tim/prune/gui/BaseImageDefinitionPanel.java
@@ -0,0 +1,157 @@
+package tim.prune.gui;
+
+import java.awt.BorderLayout;
+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.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EtchedBorder;
+
+import tim.prune.I18nManager;
+import tim.prune.data.Track;
+import tim.prune.gui.map.MapSource;
+import tim.prune.gui.map.MapSourceLibrary;
+import tim.prune.save.BaseImageConfigDialog;
+import tim.prune.save.BaseImageConsumer;
+import tim.prune.save.MapGrouter;
+import tim.prune.threedee.ImageDefinition;
+
+/**
+ * Panel used to show the current base image details
+ * and an edit button to change the definition
+ */
+public class BaseImageDefinitionPanel extends JPanel implements BaseImageConsumer
+{
+	/** Parent object (if any) */
+	private BaseImageConsumer _parent = null;
+	/** Label to describe the current settings */
+	private JLabel _baseImageLabel = null;
+	/** Button for changing the definition */
+	private JButton _editButton = null;
+	/** Dialog called by the "Edit" button to change the settings */
+	private BaseImageConfigDialog _baseImageConfig = null;
+
+
+	/**
+	 * Constructor
+	 * @param inParent parent object to inform about changes
+	 * @param inParentDialog parent dialog
+	 * @param inTrack track object
+	 */
+	public BaseImageDefinitionPanel(BaseImageConsumer inParent, JDialog inParentDialog,
+		Track inTrack)
+	{
+		_parent = inParent;
+		_baseImageConfig = new BaseImageConfigDialog(this, inParentDialog, inTrack);
+
+		// Etched border
+		setBorder(BorderFactory.createCompoundBorder(
+			BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4))
+		);
+		setLayout(new BorderLayout());
+
+		// GUI components
+		JPanel subPanel = new JPanel();
+		subPanel.setLayout(new BorderLayout(10, 4));
+		subPanel.add(new JLabel(I18nManager.getText("dialog.exportpov.baseimage") + ": "), BorderLayout.WEST);
+		_baseImageLabel = new JLabel("Typical sourcename");
+		subPanel.add(_baseImageLabel, BorderLayout.CENTER);
+		_editButton = new JButton(I18nManager.getText("button.edit"));
+		_editButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent event) {
+				changeBaseImage();
+			}
+		});
+		subPanel.add(_editButton, BorderLayout.EAST);
+		add(subPanel, BorderLayout.NORTH);
+	}
+
+	/**
+	 * @param inDefinition image definition from interactive step
+	 */
+	public void initImageParameters(ImageDefinition inDefinition)
+	{
+		_baseImageConfig.setImageDefinition(inDefinition);
+	}
+
+	/**
+	 * 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();
+		}
+		// TODO: What if it isn't possible?  Should the caller show the error message?
+		//else {
+		//	_app.showErrorMessage(getNameKey(), "dialog.exportimage.noimagepossible");
+		//}
+	}
+
+	/**
+	 * Callback from base image config dialog
+	 */
+	public void baseImageChanged()
+	{
+		updateBaseImageDetails();
+		if (_parent != null) {
+			_parent.baseImageChanged();
+		}
+	}
+
+	/**
+	 * Update the description label according to the selected base image details
+	 */
+	public void updateBaseImageDetails()
+	{
+		String desc = null;
+		ImageDefinition imageDef = _baseImageConfig.getImageDefinition();
+		// Check if selected zoom level is suitable or not, if not then set image to no
+		if (imageDef.getUseImage() && !_baseImageConfig.isSelectedZoomValid()) {
+			imageDef.setUseImage(false, imageDef.getSourceIndex(), 5);
+		}
+		// Make a description for the label
+		if (imageDef.getUseImage())
+		{
+			MapSource source = MapSourceLibrary.getSource(imageDef.getSourceIndex());
+			if (source != null)
+			{
+				desc = source.getName() + " (" + imageDef.getZoom() + ")";
+			}
+		}
+		if (desc == null) {
+			desc = I18nManager.getText("dialog.about.no");
+		}
+		_baseImageLabel.setText(desc);
+		_editButton.setEnabled(BaseImageConfigDialog.isImagePossible());
+	}
+
+	/**
+	 * @return the grouter object for reuse of the prepared images
+	 */
+	public MapGrouter getGrouter()
+	{
+		return _baseImageConfig.getGrouter();
+	}
+
+	/**
+	 * @return image definition
+	 */
+	public ImageDefinition getImageDefinition() {
+		return _baseImageConfig.getImageDefinition();
+	}
+
+	/**
+	 * @return true if any tiles were found
+	 */
+	public boolean getFoundData() {
+		return _baseImageConfig.getFoundData();
+	}
+}
diff --git a/tim/prune/gui/DecimalNumberField.java b/tim/prune/gui/DecimalNumberField.java
index f1baa2a..39c55d4 100644
--- a/tim/prune/gui/DecimalNumberField.java
+++ b/tim/prune/gui/DecimalNumberField.java
@@ -52,8 +52,7 @@ public class DecimalNumberField extends JTextField
 	 */
 	public DecimalNumberField()
 	{
-		super(6);
-		setDocument(new DecimalNumberDocument(false));
+		this(false);
 	}
 
 	/**
diff --git a/tim/prune/gui/DetailsDisplay.java b/tim/prune/gui/DetailsDisplay.java
index 13df7cd..f66d99b 100644
--- a/tim/prune/gui/DetailsDisplay.java
+++ b/tim/prune/gui/DetailsDisplay.java
@@ -82,8 +82,8 @@ public class DetailsDisplay extends GenericDisplay
 	private JPanel _playAudioPanel = null;
 
 	// Units
-	private JComboBox _coordFormatDropdown = null;
-	private JComboBox _distUnitsDropdown = null;
+	private JComboBox<String> _coordFormatDropdown = null;
+	private JComboBox<String> _distUnitsDropdown = null;
 
 	// Cached labels
 	private static final String LABEL_POINT_SELECTED = I18nManager.getText("details.index.selected") + ": ";
@@ -243,7 +243,7 @@ public class DetailsDisplay extends GenericDisplay
 		lowerPanel.add(coordFormatLabel);
 		String[] coordFormats = {I18nManager.getText("units.original"), I18nManager.getText("units.degminsec"),
 			I18nManager.getText("units.degmin"), I18nManager.getText("units.deg")};
-		_coordFormatDropdown = new JComboBox(coordFormats);
+		_coordFormatDropdown = new JComboBox<String>(coordFormats);
 		_coordFormatDropdown.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent e)
 			{
@@ -256,7 +256,7 @@ public class DetailsDisplay extends GenericDisplay
 		unitsLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
 		lowerPanel.add(unitsLabel);
 		// Make dropdown for distance units
-		_distUnitsDropdown = new JComboBox();
+		_distUnitsDropdown = new JComboBox<String>();
 		final UnitSet currUnits = Config.getUnitSet();
 		for (int i=0; i<UnitSetLibrary.getNumUnitSets(); i++) {
 			_distUnitsDropdown.addItem(I18nManager.getText(UnitSetLibrary.getUnitSet(i).getDistanceUnit().getNameKey()));
@@ -394,12 +394,13 @@ 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 + DisplayUtils.roundedNumber(selection.getDistance()) + " " + distUnitsStr);
-			if (selection.getNumSeconds() > 0)
+			_distanceLabel.setText(LABEL_RANGE_DISTANCE + DisplayUtils.roundedNumber(selection.getMovingDistance()) + " " + distUnitsStr);
+			final long numMovingSeconds = selection.getMovingSeconds();
+			if (numMovingSeconds > 0L)
 			{
-				_durationLabel.setText(LABEL_RANGE_DURATION + DisplayUtils.buildDurationString(selection.getNumSeconds()));
+				_durationLabel.setText(LABEL_RANGE_DURATION + DisplayUtils.buildDurationString(numMovingSeconds));
 				_aveSpeedLabel.setText(I18nManager.getText("details.range.avespeed") + ": "
-					+ DisplayUtils.roundedNumber(selection.getDistance()/selection.getNumSeconds()*3600.0) + " " + speedUnitsStr);
+					+ DisplayUtils.roundedNumber(selection.getMovingDistance()/numMovingSeconds*3600.0) + " " + speedUnitsStr);
 			}
 			else {
 				_durationLabel.setText("");
diff --git a/tim/prune/gui/DisplayUtils.java b/tim/prune/gui/DisplayUtils.java
index 49ca387..25640d8 100644
--- a/tim/prune/gui/DisplayUtils.java
+++ b/tim/prune/gui/DisplayUtils.java
@@ -70,4 +70,24 @@ public abstract class DisplayUtils
 		FORMAT_FLEX.setMinimumFractionDigits(numDigits);
 		return FORMAT_FLEX.format(inVal);
 	}
+
+	/**
+	 * Convert the given hour and minute values into a string H:MM
+	 * @param inHour hour of day, 0-24
+	 * @param inMinute minute, 0-59
+	 * @return string, either H:MM or HH:MM
+	 */
+	public static String makeTimeString(int inHour, int inMinute)
+	{
+		StringBuilder sb = new StringBuilder();
+		final int hour = (inHour >= 0 && inHour <= 24) ? inHour : 0;
+		sb.append(hour);
+
+		sb.append(':');
+
+		final int minute = (inMinute >= 0 && inMinute <= 59) ? inMinute : 0;
+		if (minute <= 9) {sb.append('0');}
+		sb.append(minute);
+		return sb.toString();
+	}
 }
diff --git a/tim/prune/load/MediaLoadProgressDialog.java b/tim/prune/gui/GenericProgressDialog.java
similarity index 73%
copy from tim/prune/load/MediaLoadProgressDialog.java
copy to tim/prune/gui/GenericProgressDialog.java
index a052b55..acef147 100644
--- a/tim/prune/load/MediaLoadProgressDialog.java
+++ b/tim/prune/gui/GenericProgressDialog.java
@@ -1,9 +1,10 @@
-package tim.prune.load;
+package tim.prune.gui;
 
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 
 import javax.swing.BorderFactory;
+import javax.swing.Box;
 import javax.swing.BoxLayout;
 import javax.swing.JButton;
 import javax.swing.JDialog;
@@ -16,24 +17,32 @@ import tim.prune.I18nManager;
 import tim.prune.function.Cancellable;
 
 /**
- * Class to show a progress dialog for loading media.
- * Used for regular photo / audio loads plus the async
- * loading function.
+ * Class to show a progress dialog for various time-consuming functions
  */
-public class MediaLoadProgressDialog
+public class GenericProgressDialog
 {
 	private JDialog _progressDialog   = null;
+	private String _dialogTitleKey    = null;
+	private String _labelKey          = null;
 	private JProgressBar _progressBar = null;
-	private JFrame _parentFrame = null;
-	private Cancellable _function = null;
+	private JFrame _parentFrame       = null;
+	private Cancellable _function     = null;
 
 	/**
 	 * Constructor
+	 * @param inTitleKey key for dialog title text
+	 * @param inLabelKey key for label text
 	 * @param inParentFrame parent frame for creating dialog
 	 * @param inFunction function which can be cancelled
 	 */
-	public MediaLoadProgressDialog(JFrame inParentFrame, Cancellable inFunction)
+	public GenericProgressDialog(String inTitleKey, String inLabelKey,
+		JFrame inParentFrame, Cancellable inFunction)
 	{
+		_dialogTitleKey = inTitleKey;
+		_labelKey = inLabelKey;
+		if (_labelKey == null) {
+			_labelKey = "confirm.running";
+		}
 		_parentFrame = inParentFrame;
 		_function = inFunction;
 	}
@@ -43,7 +52,7 @@ public class MediaLoadProgressDialog
 	 */
 	private void createProgressDialog()
 	{
-		_progressDialog = new JDialog(_parentFrame, I18nManager.getText("dialog.jpegload.progress.title"));
+		_progressDialog = new JDialog(_parentFrame, I18nManager.getText(_dialogTitleKey));
 		_progressDialog.setLocationRelativeTo(_parentFrame);
 		_progressBar = new JProgressBar(0, 100);
 		_progressBar.setValue(0);
@@ -51,9 +60,10 @@ public class MediaLoadProgressDialog
 		_progressBar.setString("");
 		JPanel panel = new JPanel();
 		panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
-		panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
-		panel.add(new JLabel(I18nManager.getText("dialog.jpegload.progress")));
+		panel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
+		panel.add(new JLabel(I18nManager.getText(_labelKey)));
 		panel.add(_progressBar);
+		panel.add(Box.createVerticalStrut(6)); // spacer
 		JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
 		cancelButton.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent e)
diff --git a/tim/prune/gui/MediaListModel.java b/tim/prune/gui/MediaListModel.java
index e2a7307..4de374b 100644
--- a/tim/prune/gui/MediaListModel.java
+++ b/tim/prune/gui/MediaListModel.java
@@ -8,7 +8,7 @@ import tim.prune.data.MediaList;
 /**
  * Class to act as list model for the photo list and audio list
  */
-public class MediaListModel extends AbstractListModel
+public class MediaListModel extends AbstractListModel<String>
 {
 	/** media list */
 	MediaList _media = null;
@@ -31,7 +31,7 @@ public class MediaListModel extends AbstractListModel
 	/**
 	 * @see javax.swing.ListModel#getElementAt(int)
 	 */
-	public Object getElementAt(int inIndex)
+	public String getElementAt(int inIndex)
 	{
 		MediaObject m = _media.getMedia(inIndex);
 		// * means modified since loading
diff --git a/tim/prune/gui/MenuManager.java b/tim/prune/gui/MenuManager.java
index 16a0a66..d2be8ad 100644
--- a/tim/prune/gui/MenuManager.java
+++ b/tim/prune/gui/MenuManager.java
@@ -73,6 +73,8 @@ public class MenuManager implements DataSubscriber
 	private JMenuItem _addAltitudeOffsetItem = null;
 	private JMenuItem _mergeSegmentsItem = null;
 	private JMenu     _rearrangeMenu = null;
+	private JMenuItem _splitSegmentsItem = null;
+	private JMenuItem _sewSegmentsItem = null;
 	private JMenuItem _cutAndMoveItem = null;
 	private JMenuItem _convertNamesToTimesItem = null;
 	private JMenuItem _deleteFieldValuesItem = null;
@@ -83,8 +85,10 @@ public class MenuManager implements DataSubscriber
 	private JMenuItem _getGpsiesItem = null;
 	private JMenuItem _uploadGpsiesItem = null;
 	private JMenuItem _lookupSrtmItem = null;
+	private JMenuItem _downloadSrtmItem = null;
 	private JMenuItem _lookupWikipediaItem = null;
 	private JMenuItem _downloadOsmItem = null;
+	private JMenuItem _getWeatherItem = null;
 	private JMenuItem _distanceItem = null;
 	private JMenuItem _fullRangeDetailsItem = null;
 	private JMenuItem _estimateTimeItem = null;
@@ -235,6 +239,34 @@ public class MenuManager implements DataSubscriber
 		fileMenu.add(exitMenuItem);
 		menubar.add(fileMenu);
 
+		////////////////////////////////////////////////////
+		// Online menu
+		JMenu onlineMenu = new JMenu(I18nManager.getText("menu.online"));
+		setAltKey(onlineMenu, "altkey.menu.online");
+		// SRTM
+		_lookupSrtmItem = makeMenuItem(FunctionLibrary.FUNCTION_LOOKUP_SRTM, false);
+		onlineMenu.add(_lookupSrtmItem);
+		_downloadSrtmItem = makeMenuItem(FunctionLibrary.FUNCTION_DOWNLOAD_SRTM, false);
+		onlineMenu.add(_downloadSrtmItem);
+		// Get gpsies tracks
+		_getGpsiesItem = makeMenuItem(FunctionLibrary.FUNCTION_GET_GPSIES, false);
+		onlineMenu.add(_getGpsiesItem);
+		// Upload to gpsies
+		_uploadGpsiesItem = makeMenuItem(FunctionLibrary.FUNCTION_UPLOAD_GPSIES, false);
+		onlineMenu.add(_uploadGpsiesItem);
+		onlineMenu.addSeparator();
+		_lookupWikipediaItem = makeMenuItem(FunctionLibrary.FUNCTION_LOOKUP_WIKIPEDIA, false);
+		onlineMenu.add(_lookupWikipediaItem);
+		JMenuItem searchWikipediaNamesItem = makeMenuItem(FunctionLibrary.FUNCTION_SEARCH_WIKIPEDIA);
+		onlineMenu.add(searchWikipediaNamesItem);
+		_downloadOsmItem = makeMenuItem(FunctionLibrary.FUNCTION_DOWNLOAD_OSM, false);
+		onlineMenu.add(_downloadOsmItem);
+		onlineMenu.addSeparator();
+		_getWeatherItem = makeMenuItem(FunctionLibrary.FUNCTION_GET_WEATHER_FORECAST, false);
+		onlineMenu.add(_getWeatherItem);
+		menubar.add(onlineMenu);
+
+		////////////////////////////////////////////////////
 		// Track menu
 		JMenu trackMenu = new JMenu(I18nManager.getText("menu.track"));
 		setAltKey(trackMenu, "altkey.menu.track");
@@ -306,20 +338,12 @@ public class MenuManager implements DataSubscriber
 		rearrangeNearestItem.setEnabled(true);
 		_rearrangeMenu.add(rearrangeNearestItem);
 		trackMenu.add(_rearrangeMenu);
-		// Get gpsies tracks
-		_getGpsiesItem = makeMenuItem(FunctionLibrary.FUNCTION_GET_GPSIES, false);
-		trackMenu.add(_getGpsiesItem);
-		// Upload to gpsies
-		_uploadGpsiesItem = makeMenuItem(FunctionLibrary.FUNCTION_UPLOAD_GPSIES, false);
-		trackMenu.add(_uploadGpsiesItem);
-		_lookupSrtmItem = makeMenuItem(FunctionLibrary.FUNCTION_LOOKUP_SRTM, false);
-		trackMenu.add(_lookupSrtmItem);
-		_lookupWikipediaItem = makeMenuItem(FunctionLibrary.FUNCTION_LOOKUP_WIKIPEDIA, false);
-		trackMenu.add(_lookupWikipediaItem);
-		JMenuItem searchWikipediaNamesItem = makeMenuItem(FunctionLibrary.FUNCTION_SEARCH_WIKIPEDIA);
-		trackMenu.add(searchWikipediaNamesItem);
-		_downloadOsmItem = makeMenuItem(FunctionLibrary.FUNCTION_DOWNLOAD_OSM, false);
-		trackMenu.add(_downloadOsmItem);
+		// Split track segments
+		_splitSegmentsItem = makeMenuItem(FunctionLibrary.FUNCTION_SPLIT_SEGMENTS, false);
+		trackMenu.add(_splitSegmentsItem);
+		// Sew track segments
+		_sewSegmentsItem = makeMenuItem(FunctionLibrary.FUNCTION_SEW_SEGMENTS, false);
+		trackMenu.add(_sewSegmentsItem);
 		trackMenu.addSeparator();
 		_learnEstimationParams = makeMenuItem(FunctionLibrary.FUNCTION_LEARN_ESTIMATION_PARAMS, false);
 		trackMenu.add(_learnEstimationParams);
@@ -854,6 +878,8 @@ public class MenuManager implements DataSubscriber
 		_markRectangleItem.setEnabled(hasData);
 		_deleteMarkedPointsItem.setEnabled(hasData && _track.hasMarkedPoints());
 		_rearrangeMenu.setEnabled(hasData && _track.hasTrackPoints() && _track.hasWaypoints());
+		_splitSegmentsItem.setEnabled(hasData && _track.hasTrackPoints() && _track.getNumPoints() > 3);
+		_sewSegmentsItem.setEnabled(hasData && _track.hasTrackPoints() && _track.getNumPoints() > 3);
 		_selectAllItem.setEnabled(hasData);
 		_selectNoneItem.setEnabled(hasData);
 		_show3dItem.setEnabled(hasMultiplePoints);
@@ -865,7 +891,10 @@ public class MenuManager implements DataSubscriber
 		_lookupSrtmItem.setEnabled(hasData);
 		_lookupWikipediaItem.setEnabled(hasData);
 		_downloadOsmItem.setEnabled(hasData);
+		_getWeatherItem.setEnabled(hasData);
 		_findWaypointItem.setEnabled(hasData && _track.hasWaypoints());
+		// have we got a cache?
+		_downloadSrtmItem.setEnabled(hasData && Config.getConfigString(Config.KEY_DISK_CACHE) != null);
 
 		// is undo available?
 		boolean hasUndo = !_app.getUndoStack().isEmpty();
diff --git a/tim/prune/gui/ProgressDialog.java b/tim/prune/gui/ProgressDialog.java
index d09087e..363989b 100644
--- a/tim/prune/gui/ProgressDialog.java
+++ b/tim/prune/gui/ProgressDialog.java
@@ -7,6 +7,7 @@ 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;
@@ -72,9 +73,14 @@ public class ProgressDialog
 		JPanel dialogPanel = new JPanel();
 		dialogPanel.setLayout(new BorderLayout());
 		dialogPanel.add(new JLabel(I18nManager.getText("confirm.running")), BorderLayout.NORTH);
+		// Centre panel with an empty border
+		JPanel centrePanel = new JPanel();
+		centrePanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+		centrePanel.setLayout(new BorderLayout());
 		_progressBar = new JProgressBar();
 		_progressBar.setPreferredSize(new Dimension(250, 30));
-		dialogPanel.add(_progressBar, BorderLayout.CENTER);
+		centrePanel.add(_progressBar, BorderLayout.CENTER);
+		dialogPanel.add(centrePanel, BorderLayout.CENTER);
 		// Cancel button at the bottom
 		JPanel buttonPanel = new JPanel();
 		buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
diff --git a/tim/prune/gui/SelectorDisplay.java b/tim/prune/gui/SelectorDisplay.java
index 0f2b66b..a4df9cd 100644
--- a/tim/prune/gui/SelectorDisplay.java
+++ b/tim/prune/gui/SelectorDisplay.java
@@ -43,15 +43,15 @@ public class SelectorDisplay extends GenericDisplay
 	private int _visiblePanels = 1;
 	// Waypoints
 	private JPanel _waypointListPanel = null;
-	private JList _waypointList = null;
+	private JList<String> _waypointList = null;
 	private WaypointListModel _waypointListModel = null;
 	// Photos
 	private JPanel _photoListPanel = null;
-	private JList _photoList = null;
+	private JList<String> _photoList = null;
 	private MediaListModel _photoListModel = null;
 	// Audio files
 	private JPanel _audioListPanel = null;
-	private JList _audioList = null;
+	private JList<String> _audioList = null;
 	private MediaListModel _audioListModel = null;
 
 	// scrollbar interval
@@ -106,7 +106,7 @@ public class SelectorDisplay extends GenericDisplay
 			BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
 		);
 		_waypointListModel = new WaypointListModel(_trackInfo.getTrack());
-		_waypointList = new JList(_waypointListModel);
+		_waypointList = new JList<String>(_waypointListModel);
 		_waypointList.setVisibleRowCount(NUM_LIST_ENTRIES);
 		_waypointList.addListSelectionListener(new ListSelectionListener() {
 			public void valueChanged(ListSelectionEvent e)
@@ -118,7 +118,7 @@ public class SelectorDisplay extends GenericDisplay
 		_listsPanel.add(_waypointListPanel);
 		// photo list
 		_photoListModel = new MediaListModel(_trackInfo.getPhotoList());
-		_photoList = new JList(_photoListModel);
+		_photoList = new JList<String>(_photoListModel);
 		_photoList.setVisibleRowCount(NUM_LIST_ENTRIES);
 		_photoList.addListSelectionListener(new ListSelectionListener() {
 			public void valueChanged(ListSelectionEvent e)
@@ -132,7 +132,7 @@ public class SelectorDisplay extends GenericDisplay
 
 		// List for audio clips
 		_audioListModel = new MediaListModel(_trackInfo.getAudioList());
-		_audioList = new JList(_audioListModel);
+		_audioList = new JList<String>(_audioListModel);
 		_audioList.addListSelectionListener(new ListSelectionListener() {
 			public void valueChanged(ListSelectionEvent e)
 			{
@@ -313,7 +313,7 @@ public class SelectorDisplay extends GenericDisplay
 	 * @param inList list object
 	 * @return panel object
 	 */
-	private static JPanel makeListPanel(String inNameKey, JList inList)
+	private static JPanel makeListPanel(String inNameKey, JList<String> inList)
 	{
 		JPanel panel = new JPanel();
 		panel.setLayout(new BorderLayout());
diff --git a/tim/prune/gui/TerrainDefinitionPanel.java b/tim/prune/gui/TerrainDefinitionPanel.java
new file mode 100644
index 0000000..690c1ae
--- /dev/null
+++ b/tim/prune/gui/TerrainDefinitionPanel.java
@@ -0,0 +1,85 @@
+package tim.prune.gui;
+
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.threedee.TerrainDefinition;
+
+/**
+ * Gui component for defining the 3d terrain,
+ * including whether to use one or not, and if so
+ * what resolution to use for the grid
+ */
+public class TerrainDefinitionPanel extends JPanel
+{
+	/** Checkbox to use a terrain or not */
+	private JCheckBox _useCheckbox = null;
+	/** Field for entering the grid size */
+	private WholeNumberField _gridSizeField = null;
+
+
+	/**
+	 * Constructor
+	 */
+	public TerrainDefinitionPanel()
+	{
+		setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+		// Components
+		_useCheckbox = new JCheckBox(I18nManager.getText("dialog.3d.useterrain"));
+		_useCheckbox.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent arg0) {
+				activateGridField();
+			}
+		});
+		add(_useCheckbox);
+		add(Box.createHorizontalGlue());
+		JLabel label = new JLabel(I18nManager.getText("dialog.3d.terraingridsize") + ": ");
+		add(label);
+		_gridSizeField = new WholeNumberField(4);
+		_gridSizeField.setValue(10);
+		_gridSizeField.setMaximumSize(new Dimension(100, 50));
+		_gridSizeField.setEnabled(false);
+		add(_gridSizeField);
+	}
+
+	/**
+	 * @param inDefinition terrain parameters to set
+	 */
+	public void initTerrainParameters(TerrainDefinition inDefinition)
+	{
+		_useCheckbox.setSelected(inDefinition != null && inDefinition.getUseTerrain());
+		if (inDefinition != null && inDefinition.getGridSize() > 0) {
+			_gridSizeField.setValue(inDefinition.getGridSize());
+		}
+		activateGridField();
+	}
+
+	/**
+	 * @return true if the terrain is selected
+	 */
+	public boolean getUseTerrain() {
+		return _useCheckbox.isSelected() && getGridSize() > 2;
+	}
+
+	/**
+	 * @return number of nodes along each side of the grid
+	 */
+	public int getGridSize() {
+		return _gridSizeField.getValue();
+	}
+
+	/**
+	 * Set the grid field to be enabled or not based on the checkbox
+	 */
+	private void activateGridField() {
+		_gridSizeField.setEnabled(_useCheckbox.isSelected());
+	}
+}
diff --git a/tim/prune/gui/UndoManager.java b/tim/prune/gui/UndoManager.java
index 1b77fe1..48307c1 100644
--- a/tim/prune/gui/UndoManager.java
+++ b/tim/prune/gui/UndoManager.java
@@ -28,7 +28,7 @@ public class UndoManager
 {
 	private App _app;
 	private JDialog _dialog;
-	private JList _actionList;
+	private JList<String> _actionList;
 
 
 	/**
@@ -52,7 +52,7 @@ public class UndoManager
 		{
 			undoActions[i] = undoStack.elementAt(undoStack.size()-1-i).getDescription();
 		}
-		_actionList = new JList(undoActions);
+		_actionList = new JList<String>(undoActions);
 		_actionList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
 		_actionList.setSelectedIndex(0);
 		_actionList.addListSelectionListener(new ListSelectionListener()
diff --git a/tim/prune/gui/WaypointListModel.java b/tim/prune/gui/WaypointListModel.java
index 3508697..3d5dd1f 100644
--- a/tim/prune/gui/WaypointListModel.java
+++ b/tim/prune/gui/WaypointListModel.java
@@ -9,7 +9,7 @@ import tim.prune.data.Track;
 /**
  * Class to act as list model for the waypoint list
  */
-public class WaypointListModel extends AbstractListModel
+public class WaypointListModel extends AbstractListModel<String>
 {
 	Track _track = null;
 	ArrayList<DataPoint> _waypoints = null;
@@ -36,7 +36,7 @@ public class WaypointListModel extends AbstractListModel
 	/**
 	 * @see javax.swing.ListModel#getElementAt(int)
 	 */
-	public Object getElementAt(int inIndex)
+	public String getElementAt(int inIndex)
 	{
 		DataPoint p = null;
 		if (inIndex < 0 || inIndex >= getSize()
diff --git a/tim/prune/gui/WaypointNameMatcher.java b/tim/prune/gui/WaypointNameMatcher.java
index db0f827..83a10bb 100644
--- a/tim/prune/gui/WaypointNameMatcher.java
+++ b/tim/prune/gui/WaypointNameMatcher.java
@@ -10,7 +10,7 @@ import tim.prune.data.Track;
  * Class to deal with the matching of waypoint names
  * and the representation in a list
  */
-public class WaypointNameMatcher extends AbstractListModel
+public class WaypointNameMatcher extends AbstractListModel<String>
 {
 	private ArrayList<DataPoint> _waypoints = null;
 	private int _numPoints = 0;
@@ -73,7 +73,7 @@ public class WaypointNameMatcher extends AbstractListModel
 	/**
 	 * @see javax.swing.ListModel#getElementAt(int)
 	 */
-	public Object getElementAt(int inIndex)
+	public String getElementAt(int inIndex)
 	{
 		return _matches.get(inIndex).getWaypointName();
 	}
diff --git a/tim/prune/gui/images/weather-clear-day.png b/tim/prune/gui/images/weather-clear-day.png
new file mode 100644
index 0000000..d6d011d
Binary files /dev/null and b/tim/prune/gui/images/weather-clear-day.png differ
diff --git a/tim/prune/gui/images/weather-clear-night.png b/tim/prune/gui/images/weather-clear-night.png
new file mode 100644
index 0000000..51580ea
Binary files /dev/null and b/tim/prune/gui/images/weather-clear-night.png differ
diff --git a/tim/prune/gui/images/weather-clouds-day.png b/tim/prune/gui/images/weather-clouds-day.png
new file mode 100644
index 0000000..bebf3fb
Binary files /dev/null and b/tim/prune/gui/images/weather-clouds-day.png differ
diff --git a/tim/prune/gui/images/weather-clouds-night.png b/tim/prune/gui/images/weather-clouds-night.png
new file mode 100644
index 0000000..a6e4bc5
Binary files /dev/null and b/tim/prune/gui/images/weather-clouds-night.png differ
diff --git a/tim/prune/gui/images/weather-clouds.png b/tim/prune/gui/images/weather-clouds.png
new file mode 100644
index 0000000..e4be440
Binary files /dev/null and b/tim/prune/gui/images/weather-clouds.png differ
diff --git a/tim/prune/gui/images/weather-extreme.png b/tim/prune/gui/images/weather-extreme.png
new file mode 100644
index 0000000..48264c7
Binary files /dev/null and b/tim/prune/gui/images/weather-extreme.png differ
diff --git a/tim/prune/gui/images/weather-fog.png b/tim/prune/gui/images/weather-fog.png
new file mode 100644
index 0000000..5df0e1c
Binary files /dev/null and b/tim/prune/gui/images/weather-fog.png differ
diff --git a/tim/prune/gui/images/weather-hail.png b/tim/prune/gui/images/weather-hail.png
new file mode 100644
index 0000000..563e92b
Binary files /dev/null and b/tim/prune/gui/images/weather-hail.png differ
diff --git a/tim/prune/gui/images/weather-lightrain.png b/tim/prune/gui/images/weather-lightrain.png
new file mode 100644
index 0000000..0ba21e2
Binary files /dev/null and b/tim/prune/gui/images/weather-lightrain.png differ
diff --git a/tim/prune/gui/images/weather-rain.png b/tim/prune/gui/images/weather-rain.png
new file mode 100644
index 0000000..35d7b1d
Binary files /dev/null and b/tim/prune/gui/images/weather-rain.png differ
diff --git a/tim/prune/gui/images/weather-snow.png b/tim/prune/gui/images/weather-snow.png
new file mode 100644
index 0000000..286fb28
Binary files /dev/null and b/tim/prune/gui/images/weather-snow.png differ
diff --git a/tim/prune/gui/images/weather-storm.png b/tim/prune/gui/images/weather-storm.png
new file mode 100644
index 0000000..86e4271
Binary files /dev/null and b/tim/prune/gui/images/weather-storm.png differ
diff --git a/tim/prune/gui/map/CloudmadeMapSource.java b/tim/prune/gui/map/CloudmadeMapSource.java
deleted file mode 100644
index 17d4efe..0000000
--- a/tim/prune/gui/map/CloudmadeMapSource.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package tim.prune.gui.map;
-
-/**
- * Class to act as a source for Cloudmade maps with a given style
- */
-public class CloudmadeMapSource extends OsmMapSource
-{
-	/** Selected style number */
-	private String _style = null;
-	/** Server prefix including API-key unique to GpsPrune application */
-	private static final String SERVER_PREFIX = "[abc].tile.cloudmade.com/03d86b66f51f4a3b8c236ac06f2a2e57/";
-
-	/**
-	 * Constructor
-	 * @param inName name to use for map source
-	 * @param inStyle style, given as integer
-	 * @param inMaxZoom maximum zoom level, 18 by default
-	 */
-	public CloudmadeMapSource(String inName, String inStyle, int inMaxZoom)
-	{
-		// Note: Could check style for valid integer value here
-		super(inName, SERVER_PREFIX + inStyle + "/256/", null, inMaxZoom);
-		_style = inStyle;
-	}
-
-	/**
-	 * @return semicolon-separated list of all fields
-	 */
-	public String getConfigString()
-	{
-		return "c:" +  getName() + ";" + _style + ";" + getMaxZoomLevel();
-	}
-
-	/**
-	 * Construct a new map source from its config string
-	 * @param inConfigString string from Config, separated by semicolons
-	 * @return new map source, or null if not parseable
-	 */
-	public static CloudmadeMapSource fromConfig(String inConfigString)
-	{
-		CloudmadeMapSource source = null;
-		if (inConfigString.startsWith("c:"))
-		{
-			String[] items = inConfigString.substring(2).split(";");
-			try {
-				if (items.length == 3) {
-					source = new CloudmadeMapSource(items[0], items[1], Integer.parseInt(items[2]));
-				}
-			} catch (NumberFormatException nfe) {}
-		}
-		return source;
-	}
-
-	/**
-	 * @return style as string, only required to populate edit dialog
-	 */
-	public String getStyle()
-	{
-		return _style;
-	}
-}
diff --git a/tim/prune/gui/map/DiskTileCacher.java b/tim/prune/gui/map/DiskTileCacher.java
index 350b3b4..459b140 100644
--- a/tim/prune/gui/map/DiskTileCacher.java
+++ b/tim/prune/gui/map/DiskTileCacher.java
@@ -63,7 +63,9 @@ public class DiskTileCacher implements Runnable
 				try {
 					image = Toolkit.getDefaultToolkit().createImage(tileFile.getAbsolutePath());
 				}
-				catch (Exception e) {}
+				catch (Exception e) {
+					System.err.println("createImage: " + e.getClass().getName() + " _ " + e.getMessage());
+				}
 			}
 		}
 		return image;
@@ -110,7 +112,12 @@ public class DiskTileCacher implements Runnable
 	private static boolean isBeingLoaded(File inFile)
 	{
 		File tempFile = new File(inFile.getAbsolutePath() + ".temp");
-		return tempFile.exists();
+		if (!tempFile.exists()) {
+			return false;
+		}
+		// File exists, so check if it was created recently
+		final long fileAge = System.currentTimeMillis() - tempFile.lastModified();
+		return fileAge < 1000000L; // overwrite if the temp file is still there after 1000s
 	}
 
 	/**
@@ -125,7 +132,7 @@ public class DiskTileCacher implements Runnable
 		// Use a synchronized block across all threads to make sure this url is only fetched once
 		synchronized (DiskTileCacher.class)
 		{
-			if (tempFile.exists()) {return;}
+			if (tempFile.exists()) {tempFile.delete();}
 			try {
 				if (!tempFile.createNewFile()) {return;}
 			}
diff --git a/tim/prune/gui/map/MapCanvas.java b/tim/prune/gui/map/MapCanvas.java
index a6d5a88..a62b108 100644
--- a/tim/prune/gui/map/MapCanvas.java
+++ b/tim/prune/gui/map/MapCanvas.java
@@ -31,6 +31,7 @@ import javax.swing.JMenuItem;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.JSlider;
+import javax.swing.SwingUtilities;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 
@@ -57,12 +58,13 @@ import tim.prune.function.compress.MarkPointsInRectangleFunction;
 import tim.prune.function.edit.FieldEdit;
 import tim.prune.function.edit.FieldEditList;
 import tim.prune.gui.IconManager;
+import tim.prune.tips.TipManager;
 
 /**
  * Class for the map canvas, to display a background map and draw on it
  */
 public class MapCanvas extends JPanel implements MouseListener, MouseMotionListener, DataSubscriber,
-	KeyListener, MouseWheelListener
+	KeyListener, MouseWheelListener, TileConsumer
 {
 	/** App object for callbacks */
 	private App _app = null;
@@ -292,6 +294,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
 		add(_scaleBar, BorderLayout.SOUTH);
 		// Make popup menu
 		makePopup();
+		// Get currently selected map from Config, pass to MapTileManager
+		_tileManager.setMapSource(Config.getConfigInt(Config.KEY_MAPSOURCE_INDEX));
 	}
 
 
@@ -508,8 +512,19 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
 		g.fillRect(0, 0, getWidth(), getHeight());
 
 		// Check whether maps are on or not
-		boolean showMap = Config.getConfigBoolean(Config.KEY_SHOW_MAP);
+		final boolean showMap = Config.getConfigBoolean(Config.KEY_SHOW_MAP);
 		_mapCheckBox.setSelected(showMap);
+		// Check whether disk cache is on or not
+		final boolean usingDiskCache = Config.getConfigString(Config.KEY_DISK_CACHE) != null;
+		// Show tip to recommend setting up a cache
+		if (showMap && !usingDiskCache && Config.getConfigBoolean(Config.KEY_ONLINE_MODE))
+		{
+			SwingUtilities.invokeLater(new Runnable() {
+				public void run() {
+					_app.showTip(TipManager.Tip_UseAMapCache);
+				}
+			});
+		}
 
 		// reset error message
 		if (!showMap) {_shownOsmErrorAlready = false;}
@@ -544,7 +559,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
 						// Loop over layers
 						for (int l=0; l<numLayers; l++)
 						{
-							Image image = _tileManager.getTile(l, tileX, tileY);
+							Image image = _tileManager.getTile(l, tileX, tileY, true);
 							if (image != null) {
 								g.drawImage(image, x, y, 256, 256, null);
 							}
@@ -860,22 +875,25 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
 	 * Inform that tiles have been updated and the map can be repainted
 	 * @param inIsOk true if data loaded ok, false for error
 	 */
-	public synchronized void tilesUpdated(boolean inIsOk)
+	public void tilesUpdated(boolean inIsOk)
 	{
-		// Show message if loading failed (but not too many times)
-		if (!inIsOk && !_shownOsmErrorAlready && _mapCheckBox.isSelected())
+		synchronized(this)
 		{
-			_shownOsmErrorAlready = true;
-			// use separate thread to show message about failing to load osm images
-			new Thread(new Runnable() {
-				public void run() {
-					try {Thread.sleep(500);} catch (InterruptedException ie) {}
-					_app.showErrorMessage("error.osmimage.dialogtitle", "error.osmimage.failed");
-				}
-			}).start();
+			// Show message if loading failed (but not too many times)
+			if (!inIsOk && !_shownOsmErrorAlready && _mapCheckBox.isSelected())
+			{
+				_shownOsmErrorAlready = true;
+				// use separate thread to show message about failing to load osm images
+				new Thread(new Runnable() {
+					public void run() {
+						try {Thread.sleep(500);} catch (InterruptedException ie) {}
+						_app.showErrorMessage("error.osmimage.dialogtitle", "error.osmimage.failed");
+					}
+				}).start();
+			}
+			_recalculate = true;
+			repaint();
 		}
-		_recalculate = true;
-		repaint();
 	}
 
 	/**
@@ -1168,7 +1186,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
 			_app.setCurrentMode(App.AppMode.NORMAL);
 			_drawMode = MODE_DEFAULT;
 			// Call a function to mark the points
-			MarkPointsInRectangleFunction marker = new MarkPointsInRectangleFunction(_app);
+			MarkPointsInRectangleFunction marker = (MarkPointsInRectangleFunction) FunctionLibrary.FUNCTION_MARK_IN_RECTANGLE;
 			double lon1 = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_dragFromX, getWidth()));
 			double lat1 = MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(_dragFromY, getHeight()));
 			double lon2 = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_dragToX, getWidth()));
@@ -1298,7 +1316,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
 			_checkBounds = true;
 		}
 		if ((inUpdateType & DataSubscriber.MAPSERVER_CHANGED) > 0) {
-			_tileManager.resetConfig();
+			// Get the selected map source index and pass to tile manager
+			_tileManager.setMapSource(Config.getConfigInt(Config.KEY_MAPSOURCE_INDEX));
 		}
 		if ((inUpdateType & (DataSubscriber.DATA_ADDED_OR_REMOVED + DataSubscriber.DATA_EDITED)) > 0) {
 			_midpoints.updateData(_track);
diff --git a/tim/prune/gui/map/MapSourceLibrary.java b/tim/prune/gui/map/MapSourceLibrary.java
index 5c20eac..b82fd4e 100644
--- a/tim/prune/gui/map/MapSourceLibrary.java
+++ b/tim/prune/gui/map/MapSourceLibrary.java
@@ -47,7 +47,6 @@ public abstract class MapSourceLibrary
 			"http://toolserver.org/~cmarqu/hill/", 18));
 		_sourceList.add(new OsmMapSource("Openseamap", "http://tile.openstreetmap.org/",
 			"http://tiles.openseamap.org/seamark/", 18));
-		_sourceList.add(new CloudmadeMapSource("Pale Dawn", "998", 18));
 	}
 
 	/**
@@ -64,7 +63,6 @@ public abstract class MapSourceLibrary
 			{
 				String sourceString = configString.substring(0, splitPos);
 				MapSource source = OsmMapSource.fromConfig(sourceString);
-				if (source == null) {source = CloudmadeMapSource.fromConfig(sourceString);}
 				if (source != null) {
 					_sourceList.add(source);
 				}
diff --git a/tim/prune/gui/map/MapTileManager.java b/tim/prune/gui/map/MapTileManager.java
index 75f170a..ed8849b 100644
--- a/tim/prune/gui/map/MapTileManager.java
+++ b/tim/prune/gui/map/MapTileManager.java
@@ -7,18 +7,23 @@ import java.net.URL;
 
 import tim.prune.config.Config;
 
+
 /**
  * Class responsible for managing the map tiles,
  * including invoking the correct memory cacher(s) and/or disk cacher(s)
  */
 public class MapTileManager implements ImageObserver
 {
-	/** Parent object to inform when tiles received */
-	private MapCanvas _parent = null;
+	/** Consumer object to inform when tiles received */
+	private TileConsumer _consumer = null;
 	/** Current map source */
 	private MapSource _mapSource = null;
 	/** Array of tile caches, one per layer */
 	private MemTileCacher[] _tempCaches = null;
+	/** Flag for whether to download any tiles or just pull from disk */
+	private boolean _downloadTiles = true;
+	/** Flag for whether to return incomplete images or just pass to tile cache until they're done */
+	private boolean _returnIncompleteImages = false;
 	/** Number of layers */
 	private int _numLayers = -1;
 	/** Current zoom level */
@@ -29,14 +34,11 @@ public class MapTileManager implements ImageObserver
 
 	/**
 	 * Constructor
-	 * @param inParent parent canvas to be informed of updates
+	 * @param inConsumer consumer object to be notified
 	 */
-	public MapTileManager(MapCanvas inParent)
+	public MapTileManager(TileConsumer inConsumer)
 	{
-		_parent = inParent;
-		// Adjust the index of the selected map source
-		adjustSelectedMap();
-		resetConfig();
+		_consumer = inConsumer;
 	}
 
 	/**
@@ -47,9 +49,7 @@ public class MapTileManager implements ImageObserver
 	 */
 	public void centreMap(int inZoom, int inTileX, int inTileY)
 	{
-		_zoom = inZoom;
-		// Calculate number of tiles = 2^^zoom
-		_numTileIndices = 1 << _zoom;
+		setZoom(inZoom);
 		// Pass params onto all memory cachers
 		if (_tempCaches != null) {
 			for (int i=0; i<_tempCaches.length; i++) {
@@ -58,6 +58,14 @@ public class MapTileManager implements ImageObserver
 		}
 	}
 
+	/** @param inZoom zoom level to set */
+	public void setZoom(int inZoom)
+	{
+		_zoom = inZoom;
+		// Calculate number of tiles = 2^^zoom
+		_numTileIndices = 1 << _zoom;
+	}
+
 	/**
 	 * @return true if zoom is too high for tiles
 	 */
@@ -69,6 +77,21 @@ public class MapTileManager implements ImageObserver
 	}
 
 	/**
+	 * Enable or disable tile downloading
+	 * @param inEnabled true to enable downloading, false to just get tiles from disk
+	 */
+	public void enableTileDownloading(boolean inEnabled)
+	{
+		_downloadTiles = inEnabled;
+	}
+
+	/** Configure to return incomplete images instead of going via caches (and another call) */
+	public void setReturnIncompleteImages()
+	{
+		_returnIncompleteImages = true;
+	}
+
+	/**
 	 * Clear all the memory caches due to changed config / zoom
 	 */
 	public void clearMemoryCaches()
@@ -91,35 +114,22 @@ public class MapTileManager implements ImageObserver
 	}
 
 	/**
-	 * Reset the map source configuration, apparently it has changed
+	 * @param inSourceNum selected map source index
 	 */
-	public void resetConfig()
+	public void setMapSource(int inSourceNum)
 	{
-		int sourceNum = Config.getConfigInt(Config.KEY_MAPSOURCE_INDEX);
-		_mapSource = MapSourceLibrary.getSource(sourceNum);
-		if (_mapSource == null) {_mapSource = MapSourceLibrary.getSource(0);}
-		clearMemoryCaches();
-		_numLayers = _mapSource.getNumLayers();
+		setMapSource(MapSourceLibrary.getSource(inSourceNum));
 	}
 
 	/**
-	 * Adjust the index of the selected map
-	 * (only required if config was loaded from a previous version of GpsPrune)
+	 * @param inMapSource selected map source
 	 */
-	private void adjustSelectedMap()
+	public void setMapSource(MapSource inMapSource)
 	{
-		int sourceNum = Config.getConfigInt(Config.KEY_MAPSOURCE_INDEX);
-		int prevNumFixed = Config.getConfigInt(Config.KEY_NUM_FIXED_MAPS);
-		// Number of fixed maps not specified in version <=13, default to 6
-		if (prevNumFixed == 0) prevNumFixed = 6;
-		int currNumFixed = MapSourceLibrary.getNumFixedSources();
-		// Only need to do something if the number has changed
-		if (currNumFixed != prevNumFixed && (sourceNum >= prevNumFixed || sourceNum >= currNumFixed))
-		{
-			sourceNum += (currNumFixed - prevNumFixed);
-			Config.setConfigInt(Config.KEY_MAPSOURCE_INDEX, sourceNum);
-		}
-		Config.setConfigInt(Config.KEY_NUM_FIXED_MAPS, currNumFixed);
+		_mapSource = inMapSource;
+		if (_mapSource == null) {_mapSource = MapSourceLibrary.getSource(0);}
+		clearMemoryCaches();
+		_numLayers = _mapSource.getNumLayers();
 	}
 
 	/**
@@ -131,22 +141,29 @@ public class MapTileManager implements ImageObserver
 	}
 
 	/**
+	 * Get a tile from the currently selected map source
 	 * @param inLayer layer number, starting from 0
 	 * @param inX x index of tile
 	 * @param inY y index of tile
+	 * @param inDownloadIfNecessary true to download the file if it's not available
 	 * @return selected tile if already loaded, or null otherwise
 	 */
-	public Image getTile(int inLayer, int inX, int inY)
+	public Image getTile(int inLayer, int inX, int inY, boolean inDownloadIfNecessary)
 	{
 		if (inY < 0 || inY >= _numTileIndices) return null;
 		// Wrap tile indices which are too big or too small
 		inX = ((inX % _numTileIndices) + _numTileIndices) % _numTileIndices;
 
 		// Check first in memory cache for tile
-		MemTileCacher tempCache = _tempCaches[inLayer]; // Should probably guard against nulls and array indexes here
-		Image tile = tempCache.getTile(inX, inY);
-		if (tile != null) {
-			return tile;
+		Image tile = null;
+		MemTileCacher tempCache = null;
+		if (_tempCaches != null)
+		{
+			tempCache = _tempCaches[inLayer]; // Should probably guard array indexes here
+			tile = tempCache.getTile(inX, inY);
+			if (tile != null) {
+				return tile;
+			}
 		}
 
 		// Tile wasn't in memory, but maybe it's in disk cache (if there is one)
@@ -158,18 +175,23 @@ public class MapTileManager implements ImageObserver
 			tile = DiskTileCacher.getTile(diskCachePath, _mapSource.makeFilePath(inLayer, _zoom, inX, inY), onlineMode);
 			if (tile != null)
 			{
+				if (_returnIncompleteImages) {return tile;}
 				// Pass tile to memory cache
-				tempCache.setTile(tile, inX, inY, _zoom);
+				if (tempCache != null) {
+					tempCache.setTile(tile, inX, inY, _zoom);
+				}
 				if (tile.getWidth(this) > 0) {return tile;}
 				return null;
 			}
+			// else System.out.println("DTC gave null tile for " + _zoom + ", " + inX + ", " + inY);
 		}
 		// Tile wasn't in memory or on disk, so if online let's get it
-		if (onlineMode)
+		if (onlineMode && _downloadTiles && inDownloadIfNecessary)
 		{
 			try
 			{
 				URL tileUrl = new URL(_mapSource.makeURL(inLayer, _zoom, inX, inY));
+				// System.out.println("Going to fetch: " + tileUrl);
 				if (useDisk && DiskTileCacher.saveTile(tileUrl, diskCachePath,
 					_mapSource.makeFilePath(inLayer, _zoom, inX, inY), this))
 				{
@@ -178,7 +200,6 @@ public class MapTileManager implements ImageObserver
 				else
 				{
 					// Load image asynchronously, using observer
-					// tile = Toolkit.getDefaultToolkit().createImage(tileUrl);
 					// In order to set the http user agent, need to use a TileDownloader instead
 					TileDownloader.triggerLoad(this, tileUrl, inLayer, inX, inY, _zoom);
 				}
@@ -203,7 +224,7 @@ public class MapTileManager implements ImageObserver
 		boolean loaded = (infoflags & ImageObserver.ALLBITS) > 0;
 		boolean error = (infoflags & ImageObserver.ERROR) > 0;
 		if (loaded || error) {
-			_parent.tilesUpdated(loaded);
+			_consumer.tilesUpdated(loaded);
 		}
 		return !loaded;
 	}
@@ -218,7 +239,7 @@ public class MapTileManager implements ImageObserver
 	 */
 	public void notifyImageLoaded(Image inTile, int inLayer, int inX, int inY, int inZoom)
 	{
-		if (inTile != null)
+		if (inTile != null && _tempCaches != null)
 		{
 			MemTileCacher tempCache = _tempCaches[inLayer]; // Should probably guard against nulls and array indexes here
 			if (tempCache.getTile(inX, inY) == null)
@@ -228,5 +249,8 @@ public class MapTileManager implements ImageObserver
 				inTile.getWidth(this); // trigger imageUpdate when image is ready
 			}
 		}
+		else if (inTile != null) {
+			inTile.getWidth(this);
+		}
 	}
 }
diff --git a/tim/prune/gui/map/TileConsumer.java b/tim/prune/gui/map/TileConsumer.java
new file mode 100644
index 0000000..35a557e
--- /dev/null
+++ b/tim/prune/gui/map/TileConsumer.java
@@ -0,0 +1,10 @@
+package tim.prune.gui.map;
+
+/**
+ * Interface used by the MapTileManager to communicate back to its consumers
+ */
+public interface TileConsumer
+{
+	/** Let the consumer know that the tiles have been updated */
+	public void tilesUpdated(boolean inIsOk);
+}
diff --git a/tim/prune/gui/map/TileDownloader.java b/tim/prune/gui/map/TileDownloader.java
index df820fc..7c75400 100644
--- a/tim/prune/gui/map/TileDownloader.java
+++ b/tim/prune/gui/map/TileDownloader.java
@@ -60,8 +60,10 @@ public class TileDownloader implements Runnable
 		if (inManager != null && inUrl != null)
 		{
 			String url = inUrl.toString();
+			// System.out.println("Trigger load: " + url);
 			if (!BLOCKED_URLS.contains(url) && !LOADING_URLS.contains(url))
 			{
+				// System.out.println("Not blocked: " + url);
 				LOADING_URLS.add(url);
 				new Thread(new TileDownloader(inManager, inUrl, inLayer, inX, inY, inZoom)).start();
 			}
@@ -76,6 +78,7 @@ public class TileDownloader implements Runnable
 		InputStream in = null;
 		try
 		{
+			// System.out.println("TD Running thread to get: " + _url.toString());
 			// Set http user agent on connection
 			URLConnection conn = _url.openConnection();
 			conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
diff --git a/tim/prune/jpeg/ExifGateway.java b/tim/prune/jpeg/ExifGateway.java
index 9fefadf..c11ba2e 100644
--- a/tim/prune/jpeg/ExifGateway.java
+++ b/tim/prune/jpeg/ExifGateway.java
@@ -47,7 +47,10 @@ public abstract class ExifGateway
 				return data;
 			}
 		}
-		catch (LinkageError nolib) {}
+		catch (LinkageError nolib) {
+			System.err.println("Link: " + nolib.getMessage());
+			nolib.printStackTrace();
+		}
 		// Not successful - warn if necessary
 		if (!_exifFailWarned)
 		{
@@ -69,7 +72,6 @@ public abstract class ExifGateway
 	}
 
 
-
 	/**
 	 * @param inNumerator numerator from Rational
 	 * @param inDenominator denominator from Rational
@@ -88,4 +90,19 @@ public abstract class ExifGateway
 		if (inDenominator < 0) denomDbl += correction;
 		return numeratorDbl / denomDbl;
 	}
+
+
+	/**
+	 * @param inNumerator numerator from Rational
+	 * @param inDenominator denominator from Rational
+	 * @return the value of the specified number as a positive <code>double</code>.
+	 * Forces a positive answer
+	 */
+	public static final double convertToPositiveValue(long inNumerator, long inDenominator)
+	{
+		if (inDenominator == 0L) return 0.0;
+		final double numeratorDbl = inNumerator;
+		final double denomDbl = inDenominator;
+		return numeratorDbl / denomDbl;
+	}
 }
diff --git a/tim/prune/jpeg/ExternalExifLibrary.java b/tim/prune/jpeg/ExternalExifLibrary.java
index 0d94f03..496646e 100644
--- a/tim/prune/jpeg/ExternalExifLibrary.java
+++ b/tim/prune/jpeg/ExternalExifLibrary.java
@@ -2,12 +2,14 @@ package tim.prune.jpeg;
 
 import java.io.File;
 
+import com.drew.imaging.ImageMetadataReader;
 import com.drew.lang.Rational;
 import com.drew.metadata.Directory;
 import com.drew.metadata.Metadata;
-import com.drew.metadata.MetadataException;
-import com.drew.metadata.exif.ExifDirectory;
+import com.drew.metadata.exif.ExifSubIFDDirectory;
+import com.drew.metadata.exif.ExifIFD0Directory;
 import com.drew.metadata.exif.ExifReader;
+import com.drew.metadata.exif.ExifThumbnailDirectory;
 import com.drew.metadata.exif.GpsDirectory;
 
 /**
@@ -31,8 +33,7 @@ public class ExternalExifLibrary implements ExifLibrary
 		// Read exif data from picture
 		try
 		{
-			Metadata metadata = new Metadata();
-			new ExifReader(inFile).extract(metadata);
+			Metadata metadata = ImageMetadataReader.readMetadata(inFile);
 			if (metadata.containsDirectory(GpsDirectory.class))
 			{
 				Directory gpsdir = metadata.getDirectory(GpsDirectory.class);
@@ -61,22 +62,18 @@ public class ExternalExifLibrary implements ExifLibrary
 					data.setAltitudeRef(altRef);
 				}
 
-				try
+				// Timestamp and datestamp (if present)
+				final int TAG_GPS_DATESTAMP = 0x001d;
+				if (gpsdir.containsTag(GpsDirectory.TAG_GPS_TIME_STAMP) && gpsdir.containsTag(TAG_GPS_DATESTAMP))
 				{
-					// Timestamp and datestamp (if present)
-					final int TAG_GPS_DATESTAMP = 0x001d;
-					if (gpsdir.containsTag(GpsDirectory.TAG_GPS_TIME_STAMP) && gpsdir.containsTag(TAG_GPS_DATESTAMP))
-					{
-						Rational[] times = gpsdir.getRationalArray(GpsDirectory.TAG_GPS_TIME_STAMP);
-						data.setGpsTimestamp(new int[] {times[0].intValue(), times[1].intValue(),
-							times[2].intValue()});
-						Rational[] dates = gpsdir.getRationalArray(TAG_GPS_DATESTAMP);
-						if (dates != null) {
-							data.setGpsDatestamp(new int[] {dates[0].intValue(), dates[1].intValue(), dates[2].intValue()});
-						}
+					Rational[] times = gpsdir.getRationalArray(GpsDirectory.TAG_GPS_TIME_STAMP);
+					data.setGpsTimestamp(new int[] {times[0].intValue(), times[1].intValue(),
+						times[2].intValue()});
+					Rational[] dates = gpsdir.getRationalArray(TAG_GPS_DATESTAMP);
+					if (dates != null) {
+						data.setGpsDatestamp(new int[] {dates[0].intValue(), dates[1].intValue(), dates[2].intValue()});
 					}
 				}
-				catch (MetadataException me) {} // ignore, use other tags instead
 
 				// Image bearing (if present)
 				if (gpsdir.containsTag(GpsDirectory.TAG_GPS_IMG_DIRECTION) && gpsdir.containsTag(GpsDirectory.TAG_GPS_IMG_DIRECTION_REF))
@@ -89,30 +86,39 @@ public class ExternalExifLibrary implements ExifLibrary
 			}
 
 			// Tags from Exif directory
-			if (metadata.containsDirectory(ExifDirectory.class))
+			if (metadata.containsDirectory(ExifSubIFDDirectory.class))
 			{
-				Directory exifdir = metadata.getDirectory(ExifDirectory.class);
+				Directory exifdir = metadata.getDirectory(ExifSubIFDDirectory.class);
 
 				// Take time and date from exif tags
-				if (exifdir.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) {
-					data.setOriginalTimestamp(exifdir.getString(ExifDirectory.TAG_DATETIME_ORIGINAL));
+				if (exifdir.containsTag(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)) {
+					data.setOriginalTimestamp(exifdir.getString(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL));
 				}
 				// Also take "digitized" timestamp
-				if (exifdir.containsTag(ExifDirectory.TAG_DATETIME_DIGITIZED)) {
-					data.setDigitizedTimestamp(exifdir.getString(ExifDirectory.TAG_DATETIME_DIGITIZED));
+				if (exifdir.containsTag(ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED)) {
+					data.setDigitizedTimestamp(exifdir.getString(ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED));
 				}
+			}
+			if (metadata.containsDirectory(ExifIFD0Directory.class))
+			{
+				Directory exifdir = metadata.getDirectory(ExifIFD0Directory.class);
 
 				// Photo rotation code
-				if (exifdir.containsTag(ExifDirectory.TAG_ORIENTATION)) {
-					data.setOrientationCode(exifdir.getInt(ExifDirectory.TAG_ORIENTATION));
+				if (exifdir.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
+					data.setOrientationCode(exifdir.getInt(ExifIFD0Directory.TAG_ORIENTATION));
 					// NOTE: this presumably takes the _last_ orientation value found, not the first.
 				}
+			}
+
+			if (metadata.containsDirectory(ExifThumbnailDirectory.class))
+			{
+				ExifThumbnailDirectory exifdir = metadata.getDirectory(ExifThumbnailDirectory.class);
 
-				// Thumbnail
-				if (exifdir.containsTag(ExifDirectory.TAG_THUMBNAIL_DATA))
+				// TODO: Check this thumbnail stuff
+				if (exifdir.hasThumbnailData())
 				{
-					// Make a copy of the byte data rather than keeping a reference to extracted array
-					byte[] tdata = exifdir.getByteArray(ExifDirectory.TAG_THUMBNAIL_DATA);
+					// Make a copy of the byte data
+					byte[] tdata = exifdir.getThumbnailData();
 					byte[] thumb = new byte[tdata.length];
 					System.arraycopy(tdata, 0, thumb, 0, tdata.length);
 					data.setThumbnailImage(thumb);
diff --git a/tim/prune/lang/prune-texts_af.properties b/tim/prune/lang/prune-texts_af.properties
index cc8f6b5..f208cf0 100644
--- a/tim/prune/lang/prune-texts_af.properties
+++ b/tim/prune/lang/prune-texts_af.properties
@@ -29,7 +29,7 @@ menu.point=Punt
 menu.point.editpoint=Redigeer Punt
 menu.point.deletepoint=Punt Uitvee
 menu.photo=Foto
-menu.photo.saveexif=Stoor na EXIF
+menu.photo.saveexif=Stoor na Exif
 menu.audio=Audio
 menu.view=Kyk
 menu.view.showsidebars=Wys kantstawe
@@ -109,7 +109,7 @@ function.correlatephotos=Korreleer Fotos
 function.rearrangephotos=Herrangskik fotos
 function.rotatephotoleft=Roteer foto links
 function.rotatephotoright=Roteer foto regs
-function.ignoreexifthumb=Ignoreer EXIF thumbnail
+function.ignoreexifthumb=Ignoreer Exif thumbnail
 function.help=Hulp
 function.showkeys=Wys Kortpad sleutels
 function.about=Omtrent GpsPrune
@@ -254,7 +254,8 @@ dialog.correlate.photoselect.intro=Selekteer een van die gekorreleerde fotos om
 dialog.correlate.select.photoname=Foto naam
 dialog.correlate.select.timediff=Tyd verskil
 dialog.correlate.select.photolater=Foto later
-dialog.correlate.options.tip=Wenk: Deur een item te verbind, kan die tyd afset bereken word vir jou.
+tip.title=Wenk
+tip.manuallycorrelateone=Deur een item te verbind, kan die tyd afset bereken word vir jou.
 dialog.correlate.options.intro=Seleketeer die opsies vir automatiese korrelasie
 dialog.correlate.options.offsetpanel=Tyd afset
 dialog.correlate.options.offset=Afset
@@ -291,7 +292,7 @@ dialog.deletemarked.nonefound=Geen data punt kon verwyder word
 dialog.pastecoordinates.desc=Sleutel of plak die koordinate hier
 dialog.pastecoordinates.coords=Koordinate
 dialog.pastecoordinates.nothingfound=Gaan asseblief koordinate na en probeer weer
-dialog.help.help=Sien asseblief\n http://activityworkshop.net/software/gpsprune/\n vir meer inligting en gebruikers handleidings.
+dialog.help.help=Sien asseblief\n http://gpsprune.activityworkshop.net/\n vir meer inligting en gebruikers handleidings.
 dialog.about.version=Weergawe
 dialog.about.build=Bou
 dialog.about.summarytext1=GpsPrune is 'n program vir die laai, vertoon en wysiging van data vanaf GPS ontvangers.
@@ -334,7 +335,6 @@ fieldname.newsegment=Segment
 fieldname.custom=Persoonlike
 fieldname.prefix=Veld
 fieldname.distance=Afstand
-fieldname.movingdistance=Beweeg afstand
 fieldname.duration=Tydperk
 fieldname.speed=Spoed
 fieldname.verticalspeed=Vertikale spoed
@@ -394,9 +394,8 @@ error.save.failed=Stoor van data na l\u00eaer het misluk
 error.saveexif.filenotfound=Find van foto het misluk
 error.saveexif.cannotoverwrite1=Foto leer
 error.saveexif.cannotoverwrite2=is lees-alleen en kan nie oorskruif word nie. Skyf na kopie?
-error.saveexif.failed1=Stoor het misluk
-error.saveexif.failed2=van die beelde
-error.saveexif.forced2=van die beelde vereis vorsering
+error.saveexif.failed=Stoor het misluk %d van die beelde
+error.saveexif.forced=%d van die beelde vereis vorsering
 error.load.dialogtitle=Fout met laai van data
 error.load.noread=Kan nie leer lees
 error.load.nopoints=Geen koordinaat informasie gevind in the leer
diff --git a/tim/prune/lang/prune-texts_cz.properties b/tim/prune/lang/prune-texts_cz.properties
index 931a00d..4d7a661 100644
--- a/tim/prune/lang/prune-texts_cz.properties
+++ b/tim/prune/lang/prune-texts_cz.properties
@@ -7,6 +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.online=Online
 menu.track=Stopa
 menu.track.undo=Undo
 menu.track.clearundo=Vypr\u00e1zdnit pam\u011b\u0165 undo
@@ -57,6 +58,7 @@ menu.map.editmode=Edita\u010dn\u00ed m\u00f3d
 
 # Alt keys for menus
 altkey.menu.file=S
+altkey.menu.online=O
 altkey.menu.track=T
 altkey.menu.range=R
 altkey.menu.point=B
@@ -104,9 +106,12 @@ function.estimatetime=Odhad \u010dasu
 function.learnestimationparams=Anal\u00fdza stopy pro odhad \u010dasu
 function.setmapbg=Nastavit pozad\u00ed
 function.setpaths=Nastavit cestu k program\u016fm
+function.splitsegments=Rozd\u011blit stopu na \u010d\u00e1sti
+function.sewsegments=Spojit \u010d\u00e1sti stopy
 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.downloadsrtm=St\u00e1hnout dla\u017edice ze SRTM
 function.getwikipedia=Hledat na Wikipedii podle vzd\u00e1lenosti
 function.searchwikipedianames=Hledat na Wikipedii podle jm\u00e9na
 function.downloadosm=St\u00e1hnout data OSM pro oblast
@@ -135,6 +140,7 @@ function.checkversion=Zkontrolovat existenci nov\u00e9 verze
 function.saveconfig=Ulo\u017eit nastaven\u00ed
 function.diskcache=Ulo\u017eit mapy na disk
 function.managetilecache=Upravit cache map
+function.getweatherforecast=St\u00e1hnout p\u0159edpov\u011b\u010f po\u010das\u00ed
 
 # Dialogs
 dialog.exit.confirm.title=Ukon\u010dit GpsPrune
@@ -242,6 +248,8 @@ dialog.exportpov.modelstyle=Model
 dialog.exportpov.ballsandsticks=Koule a ty\u010dky
 dialog.exportpov.tubesandwalls=Roury a st\u011bny
 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.3d.useterrain=Zobrazit ter\u00e9n
+dialog.3d.terraingridsize=Rozli\u0161en\u00ed ter\u00e9nu
 dialog.exportpov.baseimage=Obr\u00e1zek jako podklad
 dialog.exportpov.cannotmakebaseimage=Nelze zapsat podklad
 dialog.baseimage.title=Podklad
@@ -256,7 +264,8 @@ 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.drawtrack=Vykreslit linii stopy
+dialog.exportimage.drawtrackpoints=Vykreslit body stopy
 dialog.exportimage.textscalepercent=Zv\u011bt\u0161en\u00ed fontu (%)
 dialog.pointtype.desc=Ulo\u017eit body n\u00e1sleduj\u00edc\u00edch typ\u016f:
 dialog.pointtype.track=Body stopy
@@ -378,7 +387,6 @@ dialog.correlate.photoselect.intro=Vyberte jednu z t\u011bchto slad\u011bn\u00fd
 dialog.correlate.select.photoname=N\u00e1zev fotografie
 dialog.correlate.select.timediff=\u010casov\u00fd rozd\u00edl
 dialog.correlate.select.photolater=Vyfoceno pozd\u011bji
-dialog.correlate.options.tip=Tip: kdy\u017e ru\u010dn\u011b slad\u00edte aspo\u0148 jednu fotografii, \u010dasov\u00fd posun bude vypo\u010d\u00edtat za v\u00e1s.
 dialog.correlate.options.intro=Upravte mo\u017enosti automatick\u00e9ho slad\u011bn\u00ed
 dialog.correlate.options.offsetpanel=\u010casov\u00fd posun
 dialog.correlate.options.offset=Posun
@@ -421,8 +429,7 @@ dialog.compress.duplicates.title=Odstran\u011bn\u00ed zdvojen\u00fdch bod\u016f
 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 Stopa->Smazat ozna\u010den\u00e9 body
+dialog.compress.confirm=Bylo ozna\u010deno celkem %d bod\u016f.\nStopa->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 stopy
 dialog.pastecoordinates.desc=Zadejte sou\u0159adnice
@@ -467,7 +474,7 @@ dialog.checkversion.newversion1=Nov\u00e1 verze GpsPrune je u\u017e dostupn\u00e
 dialog.checkversion.newversion2=.
 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.checkversion.download=Novou verzi m\u016f\u017eete st\u00e1hnout na adrese http://gpsprune.activityworkshop.net/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=<table><tr><td>\u0160ipky</td><td>Posunout mapu vlevo, vpravo, nahoru, dol\u016f</td></tr><tr><td>Ctrl + \u0161ipka vlevo, vpravo</td><td>Vybrat p\u0159edchoz\u00ed nebo n\u00e1sleduj\u00edc\u00ed bod</td></tr><tr><td>Ctrl + \u0161ipka nahoru, dol\u016f</td><td>P\u0159ibl\u00ed\u017eit, odd\u00e1lit</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>Vybrat p\u0159edchoz\u00ed, n\u00e1sleduj\u00edc\u00ed \u010d\u00e1st stopy</td></tr><tr><td>Ctrl + Home, End</td><td>Vybrat p [...]
 dialog.keys.normalmodifier=Ctrl
@@ -530,13 +537,31 @@ dialog.diskcache.tileset.multiple=v\u00edce sad
 dialog.diskcache.deleteold=Smazat star\u00e9 soubory
 dialog.diskcache.maximumage=Ponechat maxim\u00e1ln\u011b dn\u00ed
 dialog.diskcache.deleteall=Smazat v\u0161echny soubory
-dialog.diskcache.deleted1=Smaz\u00e1no
-dialog.diskcache.deleted2=soubor\u016f z cache
+dialog.diskcache.deleted=Smaz\u00e1no %d 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 stopa (1-4)
 dialog.downloadosm.desc=Potvr\u010fte, \u017ee se maj\u00ed k dan\u00e9 oblasti st\u00e1hnout data OSM:
 dialog.searchwikipedianames.search=Vyhledat:
+dialog.weather.location=M\u00edsto
+dialog.weather.update=Posledn\u00ed aktualizace
+dialog.weather.sunrise=V\u00fdchod slunce
+dialog.weather.sunset=Z\u00e1pad slunce
+dialog.weather.temperatureunits=Stupn\u011b
+dialog.weather.currentforecast=Aktu\u00e1ln\u00ed po\u010das\u00ed
+dialog.weather.dailyforecast=P\u0159edpov\u011b\u010f co den
+dialog.weather.3hourlyforecast=P\u0159edpov\u011b\u010f co t\u0159i hodiny
+dialog.weather.day.now=Te\u010f
+dialog.weather.day.today=Dnes
+dialog.weather.day.tomorrow=Z\u00edtra
+dialog.weather.day.monday=Pond\u011bl\u00ed
+dialog.weather.day.tuesday=\u00dater\u00fd
+dialog.weather.day.wednesday=St\u0159eda
+dialog.weather.day.thursday=\u010ctvrtek
+dialog.weather.day.friday=P\u00e1tek
+dialog.weather.day.saturday=Sobota
+dialog.weather.day.sunday=Ned\u011ble
+dialog.weather.creditnotice=Data poskytuje slu\u017eba openweathermap.org, v\u00edce informac\u00ed na t\u00e9to adrese.
 
 # 3d window
 dialog.3d.title=Trojrozm\u011brn\u00e9 zobrazen\u00ed GpsPrune
@@ -555,11 +580,12 @@ confirm.addtimeoffset=\u010casov\u00fd posun zm\u011bn\u011bn
 confirm.addaltitudeoffset=V\u00fd\u0161kov\u00fd posun zm\u011bn\u011bn
 confirm.rearrangewaypoints=Body p\u0159euspo\u0159\u00e1d\u00e1ny
 confirm.rearrangephotos=Fotografie p\u0159euspo\u0159\u00e1d\u00e1ny
+confirm.splitsegments=\u00dasp\u011b\u0161n\u011b rozd\u011bleno v %d bodech
+confirm.sewsegments=\u00dasp\u011b\u0161n\u011b spojeno v %d bodech
 confirm.cutandmove=V\u00fdb\u011br p\u0159esunut
 confirm.interpolate=Body p\u0159id\u00e1ny
 confirm.convertnamestotimes=N\u00e1zvy bod\u016f p\u0159evedeny
-confirm.saveexif.ok1=Ulo\u017eeno
-confirm.saveexif.ok2=fotografi\u00ed
+confirm.saveexif.ok=Ulo\u017eeno %d fotografi\u00ed
 confirm.undo.single=operace vr\u00e1cena
 confirm.undo.multi=operac\u00ed vr\u00e1ceno
 confirm.jpegload.single=fotografie p\u0159id\u00e1na
@@ -573,13 +599,23 @@ confirm.correlatephotos.multi=fotografie slad\u011bny
 confirm.createpoint=bod vytvo\u0159en
 confirm.rotatephoto=fotografie oto\u010dena
 confirm.running=Prob\u00edh\u00e1 ...
-confirm.lookupsrtm1=Nalezeno
-confirm.lookupsrtm2=v\u00fd\u0161kov\u00fdch hodnot
+confirm.lookupsrtm=Nalezeno %d v\u00fd\u0161kov\u00fdch hodnot
+confirm.downloadsrtm=Do cache bylo sta\u017eeno %d soubor\u016f
+confirm.downloadsrtm.1=Do cache bylo sta\u017eeno %d soubor\u016f
+confirm.downloadsrtm.none=\u017d\u00e1dn\u00fd soubor nebylo t\u0159eba stahovat, v\u0161e u\u017e je v cachi.
 confirm.deletefieldvalues=Hodnoty pole smaz\u00e1ny
 confirm.audioload=Audionahr\u00e1vky p\u0159id\u00e1ny
 confirm.correlateaudios.single=Audionahr\u00e1vka slad\u011bna
 confirm.correlateaudios.multi=Audionahr\u00e1vky slad\u011bny
 
+# Tips, shown just once when appropriate
+tip.title=Tip
+tip.useamapcache=Kdy\u017e nastav\u00edte odkl\u00e1dac\u00ed prostor na disku \u010dili cache (Nastaven\u00ed -> Ulo\u017eit mapy na disk),\nzrychl\u00ed se zobrazov\u00e1n\u00ed a zmen\u0161\u00ed se mno\u017estv\u00ed p\u0159en\u00e1\u0161en\u00fdch dat.
+tip.learntimeparams=V\u00fdsledky budou p\u0159esn\u011bj\u0161\u00ed, kdy\u017e na na\u010dten\u00e9 stopy pou\u017eijete funkci\nAnal\u00fdza stopy pro odhad \u010dasu.
+tip.downloadsrtm=Na\u010d\u00edt\u00e1n\u00ed nadmo\u0159sk\u00fdch v\u00fd\u0161ek bude rychlej\u0161\u00ed, pokud st\u00e1hnete dla\u017edice do cache pomoc\u00ed\nOnline -> St\u00e1hnout dla\u017edice ze SRTM.
+tip.usesrtmfor3d=Tato stopa neobsahuje informaci o nadmo\u0159sk\u00e9 v\u00fd\u0161ce.\nPro na\u010dten\u00ed p\u0159ibli\u017en\u00fdch nadmo\u0159sk\u00fdch v\u00fd\u0161ek pot\u0159ebn\u00fdch\nna trojrozm\u011brn\u00e9 zobrazen\u00ed m\u016f\u017eete pou\u017e\u00edt funkce SRTM.
+tip.manuallycorrelateone=Kdy\u017e ru\u010dn\u011b slad\u00edte aspo\u0148 jednu fotografii, \u010dasov\u00fd posun bude vypo\u010d\u00edtat za v\u00e1s.
+
 # Buttons
 button.ok=OK
 button.back=Zp\u011bt
@@ -597,6 +633,7 @@ button.yes=Ano
 button.no=Ne
 button.yestoall=Ano u v\u0161eho
 button.notoall=Ne u v\u0161eho
+button.always=V\u017edy
 button.select=Vybrat
 button.selectall=Vybrat v\u0161e
 button.selectnone=Zru\u0161it v\u00fdb\u011br
@@ -685,7 +722,6 @@ fieldname.newsegment=Segment
 fieldname.custom=Vlastn\u00ed
 fieldname.prefix=Pole
 fieldname.distance=Vzd\u00e1lenost
-fieldname.movingdistance=Najeto
 fieldname.duration=Celkov\u00fd \u010das
 fieldname.speed=Rychlost
 fieldname.verticalspeed=Vertik. rychlost
@@ -720,6 +756,10 @@ units.degminsec=Deg-min-sec
 units.degmin=Deg-min
 units.deg=Stupn\u011b
 units.iso8601=ISO 8601
+units.degreescelsius=Celsia
+units.degreescelsius.short=\u00b0C
+units.degreesfahrenheit=Fahrenheita
+units.degreesfahrenheit.short=\u00b0F
 
 # How to combine conditions, such as filters
 logic.and=a
@@ -728,6 +768,7 @@ logic.or=nebo
 # External urls
 url.googlemaps=maps.google.cz
 wikipedia.lang=cs
+openweathermap.lang=en
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -749,6 +790,8 @@ undo.deletemarked=zkomprimovat stopu
 undo.insert=vlo\u017eit body
 undo.reverse=obr\u00e1tit rozmez\u00ed
 undo.mergetracksegments=slou\u010dit \u010d\u00e1sti stopy
+undo.splitsegments=rozd\u011blit stopu na \u010d\u00e1sti
+undo.sewsegments=spojit \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
@@ -771,10 +814,8 @@ error.save.failed=Chyba p\u0159i ukl\u00e1d\u00e1n\u00ed dat do souboru
 error.saveexif.filenotfound=Soubor s fotografi\u00ed nenalezen
 error.saveexif.cannotoverwrite1=Soubor
 error.saveexif.cannotoverwrite2=je jen ke \u010dten\u00ed a nelze ho p\u0159epsat. Ulo\u017eit do kopie?
-error.saveexif.failed1=Nepoda\u0159ilo se ulo\u017eit
-error.saveexif.failed2=fotografi\u00ed
-error.saveexif.forced1=P\u0159i ukl\u00e1d\u00e1n\u00ed
-error.saveexif.forced2=fotografi\u00ed do\u0161lo k nepodstatn\u00e9 chyb\u011b
+error.saveexif.failed=Nepoda\u0159ilo se ulo\u017eit %d fotografi\u00ed
+error.saveexif.forced=P\u0159i ukl\u00e1d\u00e1n\u00ed %d fotografi\u00ed do\u0161lo k nepodstatn\u00e9 chyb\u011b
 error.load.dialogtitle=Chyba p\u0159i na\u010d\u00edt\u00e1n\u00ed
 error.load.noread=Nelze na\u010d\u00edst soubor
 error.load.nopoints=V souboru nenalezeny \u017e\u00e1dn\u00e9 informace o sou\u0159adnic\u00edch
@@ -785,7 +826,7 @@ error.jpegload.dialogtitle=Chyba p\u0159i na\u010d\u00edt\u00e1n\u00ed fotografi
 error.jpegload.nofilesfound=Nenalezeny \u017e\u00e1dn\u00e9 soubory
 error.jpegload.nojpegsfound=Nenalezeny \u017e\u00e1dn\u00e9 soubory jpeg
 error.jpegload.nogpsfound=Nenalezena informace GPS
-error.jpegload.exifreadfailed=Nepoda\u0159ilo se na\u010d\u00edst informaci EXIF. Tu nelze na\u010d\u00edst\nbez intern\u00ed nebo extern\u00ed knihovny.
+error.jpegload.exifreadfailed=Nepoda\u0159ilo se na\u010d\u00edst informaci Exif. Tu nelze na\u010d\u00edst\nbez intern\u00ed nebo extern\u00ed knihovny.
 error.audioload.nofilesfound=Nebyly nalezeny \u017e\u00e1dn\u00e9 zvukov\u00e9 soubory.
 error.gpsload.unknown=Nezn\u00e1m\u00e1 chyba
 error.undofailed.title=Selhalo undo
@@ -811,3 +852,6 @@ 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.
+error.tracksplit.nosplit=Stopu nen\u00ed mo\u017en\u00e9 rozd\u011blit
+error.downloadsrtm.nocache=Nepoda\u0159ilo se ulo\u017eit soubory.\nPros\u00edm ov\u011b\u0159te diskovou cache.
+error.sewsegments.nothingdone=Nelze spojit \u010d\u00e1sti stopy dohromady.\nStopa se skl\u00e1d\u00e1 z %d \u010d\u00e1st\u00ed.
diff --git a/tim/prune/lang/prune-texts_da.properties b/tim/prune/lang/prune-texts_da.properties
new file mode 100644
index 0000000..ea394b9
--- /dev/null
+++ b/tim/prune/lang/prune-texts_da.properties
@@ -0,0 +1,84 @@
+# Text entries for the GpsPrune application
+# Danish translations
+
+# Menu entries
+menu.file=Fil
+menu.file.addphotos=Tilf\u00f8j billeder
+menu.file.recentfiles=Seneste filer
+menu.file.save=Gem som tekst
+menu.file.exit=Afslut
+menu.track.undo=Fortryd
+menu.track.clearundo=Nulstil fortrydelsesliste
+menu.track.deletemarked=Slet markerede punkter
+menu.track.rearrange=Omorganis\u00e9r waypoints
+menu.track.rearrange.nearest=Hvert waypoint til n\u00e6rmeste nabo
+menu.range=Omr\u00e5de
+menu.range.all=V\u00e6lg alle
+menu.range.none=Ingen valgt
+menu.range.start=V\u00e6lg omr\u00e5des startpunkt
+menu.range.end=V\u00e6lg omr\u00e5des slutpunkt
+menu.range.average=Dan gennemsnit af valgte omr\u00e5de
+menu.range.reverse=Dan omvendt r\u00e6kkef\u00f8lge
+menu.range.mergetracksegments=Sammensmelt sporsegmenter
+menu.range.cutandmove=Afsk\u00e6r og flyt valgte omr\u00e5de
+menu.point=Punkt
+menu.point.editpoint=Redig\u00e8r punkt
+menu.point.deletepoint=Fjern punkt
+menu.photo=Foto
+menu.photo.saveexif=Gem Exif-data
+menu.audio=Lyd
+menu.view=Udseende
+menu.view.showsidebars=Vis sidepanel
+menu.view.browser=Kort i browser
+menu.settings=Indstillinger
+menu.settings.onlinemode=Hent kort fra Internettet
+menu.settings.autosave=Gem indstillinger automatisk ved aflutning
+menu.help=Hj\u00e6lp
+# Popup menu for map
+menu.map.zoomin=Zoom ind
+menu.map.zoomout=Zoom ud
+menu.map.newpoint=Skab nyt punkt
+menu.map.drawpoints=Skab serie af punkter
+menu.map.connect=Saml punkter p\u00e5 linie
+menu.map.autopan=Autocentrering
+menu.map.showmap=Vis kort
+menu.map.showscalebar=Vis m\u00e5lestok
+
+# Alt keys for menus
+altkey.menu.file=F
+altkey.menu.track=S
+altkey.menu.range=O
+altkey.menu.point=P
+altkey.menu.view=U
+altkey.menu.photo=T
+altkey.menu.audio=L
+altkey.menu.settings=I
+altkey.menu.help=H
+
+# Functions
+function.open=\u00c5bn fil
+function.importwithgpsbabel=Import\u00e9r fil med GPSBabel
+function.loadfromgps=Hent data fra GPS
+function.sendtogps=Overf\u00f8r data til GPS
+function.exportkml=Eksport\u00e9r KML
+function.exportgpx=Eksport\u00e9r GPX
+function.exportpov=Eksport\u00e9r POV
+function.exportsvg=Eksport\u00e9r SVG
+function.editwaypointname=Ret waypoint-navn
+function.compress=Komprim\u00e9r spor
+function.deleterange=Fjern det valgte omr\u00e5de
+function.croptrack=Afgr\u00e6ns spor
+function.interpolate=Interpol\u00e9r
+function.addtimeoffset=Tilf\u00f8j offset p\u00e5 tiden
+function.addaltitudeoffset=Tilf\u00f8j offset p\u00e5 h\u00f8jde
+function.convertnamestotimes=Ret waypoint-navne til tidspunkter
+function.deletefieldvalues=Fjern feltv\u00e6rdier
+function.findwaypoint=Find waypoint
+function.pastecoordinates=Indf\u00f8j nye koordinater
+function.charts=Kort
+function.show3d=3-D view
+function.distances=Afstande
+function.fullrangedetails=Vis alle detaljer
+function.setmapbg=V\u00e6lg kort som baggrund
+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 deb44ed..021a5f0 100644
--- a/tim/prune/lang/prune-texts_de.properties
+++ b/tim/prune/lang/prune-texts_de.properties
@@ -7,6 +7,7 @@ menu.file.addphotos=Fotos laden
 menu.file.recentfiles=Zuletzt verwendete Dateien
 menu.file.save=Als Text speichern
 menu.file.exit=Beenden
+menu.online=Online
 menu.track=Track
 menu.track.undo=R\u00fcckg\u00e4ngig
 menu.track.clearundo=Liste der letzten \u00c4nderungen l\u00f6schen
@@ -57,6 +58,7 @@ menu.map.editmode=Punkte verschieben
 
 # Alt keys for menus
 altkey.menu.file=D
+altkey.menu.online=O
 altkey.menu.track=T
 altkey.menu.range=B
 altkey.menu.point=P
@@ -104,9 +106,12 @@ function.estimatetime=Zeit absch\u00e4tzen
 function.learnestimationparams=Zeitparameter erlernen
 function.setmapbg=Karte Hintergrund setzen
 function.setpaths=Programmpfade setzen
+function.splitsegments=In Trackabschnitte schneiden
+function.sewsegments=Trackabschnitte zusammenf\u00fcgen
 function.getgpsies=Tracks bei GPSies.com herunterladen
 function.uploadgpsies=Track zu GPSies.com hochladen
-function.lookupsrtm=H\u00f6hendaten von SRTM herunterladen
+function.lookupsrtm=H\u00f6hendaten von SRTM nachschlagen
+function.downloadsrtm=SRTM Dateien herunterladen
 function.getwikipedia=Wikipediaartikel in der N\u00e4he nachschlagen
 function.searchwikipedianames=Wikipedia mit Name durchsuchen
 function.downloadosm=OSM-Daten f\u00fcr dieses Gebiet herunterladen
@@ -135,6 +140,7 @@ function.checkversion=Nach neuen Versionen suchen
 function.saveconfig=Einstellungen speichern
 function.diskcache=Karten auf Festplatte speichern
 function.managetilecache=Kartenkacheln verwalten
+function.getweatherforecast=Wettervorhersage herunterladen
 
 # Dialogs
 dialog.exit.confirm.title=GpsPrune beenden
@@ -242,6 +248,8 @@ dialog.exportpov.modelstyle=Modellstil
 dialog.exportpov.ballsandsticks=B\u00e4lle und Stangen
 dialog.exportpov.tubesandwalls=R\u00f6hren und W\u00e4nde
 dialog.3d.warningtracksize=Dieser Track hat sehr viele Punkte, die Java3D vielleicht nicht bearbeiten kann.\nM\u00f6chten Sie den Vorgang trotzdem fortsetzen?
+dialog.3d.useterrain=Gel\u00e4nde anzeigen
+dialog.3d.terraingridsize=Gittergr\u00f6\u00dfe
 dialog.exportpov.baseimage=Grundbild
 dialog.exportpov.cannotmakebaseimage=Bild kann nicht gespeichert werden
 dialog.baseimage.title=Kartenbild
@@ -257,6 +265,7 @@ 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.drawtrackpoints=Trackpunkte zeichnen
 dialog.exportimage.textscalepercent=Text Skalierung (%)
 dialog.pointtype.desc=Folgende Punkttypen speichern:
 dialog.pointtype.track=Trackpunkte
@@ -378,7 +387,6 @@ dialog.correlate.photoselect.intro=W\u00e4hlen Sie eines dieser Fotos aus, um di
 dialog.correlate.select.photoname=Bezeichnung des Fotos
 dialog.correlate.select.timediff=Zeitdifferenz
 dialog.correlate.select.photolater=Foto sp\u00e4ter
-dialog.correlate.options.tip=Tipp: Mit mindestens einem manuell verbundenen Element kann die Zeitdifferenz automatisch berechnet werden.
 dialog.correlate.options.intro=W\u00e4hlen Sie die Optionen f\u00fcr die Korrelation aus
 dialog.correlate.options.offsetpanel=Zeitunterschied
 dialog.correlate.options.offset=Unterschied
@@ -421,14 +429,13 @@ dialog.compress.duplicates.title=Duplikate entfernen
 dialog.compress.douglaspeucker.title=Douglas-Peucker-Komprimierung
 dialog.compress.douglaspeucker.paramdesc=Span-Faktor
 dialog.compress.summarylabel=Zu entfernende Punkte
-dialog.compress.confirm1=Es wurden
-dialog.compress.confirm2=Punkte markiert.\nMit Track->Markierte Punkte l\u00f6schen werden sie gel\u00f6scht
+dialog.compress.confirm=Es wurden %d Punkte markiert.\nWollen Sie die Punkte sofort l\u00f6schen?
 dialog.compress.confirmnone=es wurden keine Punkte markiert
 dialog.deletemarked.nonefound=Es konnten keine Punkte entfernt werden
 dialog.pastecoordinates.desc=Koordinaten eingeben oder einf\u00fcgen
 dialog.pastecoordinates.coords=Koordinaten
 dialog.pastecoordinates.nothingfound=Bitte pr\u00fcfen Sie die Koordinaten und versuchen Sie es nochmals
-dialog.help.help=Weitere Informationen und Benutzeranleitungen finden Sie unter\n http://activityworkshop.net/software/gpsprune/
+dialog.help.help=Weitere Informationen und Benutzeranleitungen finden Sie unter\n http://gpsprune.activityworkshop.net/
 dialog.about.version=Version
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune ist ein Programm zum Laden, Darstellen und Editieren der Daten von GPS- Ger\u00e4ten.
@@ -467,7 +474,7 @@ dialog.checkversion.newversion1=Eine neue Version von GpsPrune ist jetzt verf\u0
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Diese neue Version ist am
 dialog.checkversion.releasedate2=ver\u00f6ffentlicht worden.
-dialog.checkversion.download=Um die neue Version herunterzuladen, gehen Sie zu http://activityworkshop.net/software/gpsprune/download.html.
+dialog.checkversion.download=Um die neue Version herunterzuladen, gehen Sie zu http://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=Anstelle der Maus k\u00f6nnen Sie folgende Tastenkombinationen nutzen
 dialog.keys.keylist=<table><tr><td>Pfeil Tasten</td><td>Karte verschieben</td></tr><tr><td>Strg + Links-, Rechts-Pfeil</td><td>Vorherigen oder n\u00e4chsten Punkt markieren</td></tr><tr><td>Strg + Auf-, Abw\u00e4rts-Pfeil</td><td>Ein- oder Auszoomen</td></tr><tr><td>Strg + Bild auf, ab</td><td>Vorheriges oder n\u00e4chstes Segment markieren</td></tr><tr><td>Strg + Pos1, Ende</td><td>Ersten oder letzten Punkt markieren</td></tr><tr><td>Entf</td><td>Aktuellen Punkt entfernen</td></tr></table>
 dialog.keys.normalmodifier=Strg
@@ -530,13 +537,34 @@ dialog.diskcache.tileset.multiple=mehrere
 dialog.diskcache.deleteold=Veraltete Kacheln l\u00f6schen
 dialog.diskcache.maximumage=Maximales Alter (Tage)
 dialog.diskcache.deleteall=Alle Kacheln l\u00f6schen
-dialog.diskcache.deleted1=Es wurden
-dialog.diskcache.deleted2=Dateien aus dem Ordner gel\u00f6scht
+dialog.diskcache.deleted=Es wurden %d Dateien aus dem Ordner gel\u00f6scht
 dialog.deletefieldvalues.intro=W\u00e4hlen Sie das Feld aus, das Sie l\u00f6schen m\u00f6chten
 dialog.deletefieldvalues.nofields=Es sind keine Felder zu l\u00f6schen f\u00fcr diesen Bereich
 dialog.setlinewidth.text=Geben Sie die Dicke der Linien ein (1-4)
 dialog.downloadosm.desc=Die OpenStreetMap-Daten f\u00fcr das folgende Gebiet werden heruntergeladen (.osm-Datei):
 dialog.searchwikipedianames.search=Suche nach:
+dialog.weather.location=Ort
+dialog.weather.update=Vorhersage aktualisiert
+dialog.weather.sunrise=Sonnenaufgang
+dialog.weather.sunset=Sonnenuntergang
+dialog.weather.temperatureunits=Temperaturen
+dialog.weather.currentforecast=Aktuell
+dialog.weather.dailyforecast=T\u00e4gliche Vorhersage
+dialog.weather.3hourlyforecast=Drei-st\u00fcndliche Vorhersage
+dialog.weather.day.now=Aktuell
+dialog.weather.day.today=Heute
+dialog.weather.day.tomorrow=Morgen
+dialog.weather.day.monday=Montag
+dialog.weather.day.tuesday=Dienstag
+dialog.weather.day.wednesday=Mittwoch
+dialog.weather.day.thursday=Donnerstag
+dialog.weather.day.friday=Freitag
+dialog.weather.day.saturday=Samstag
+dialog.weather.day.sunday=Sonntag
+dialog.weather.wind=Wind
+dialog.weather.temp=Temp
+dialog.weather.humidity=R.L.
+dialog.weather.creditnotice=Diese Daten wurden von openweathermap.org zur Verf\u00fcgung gestellt. Die Webseite hat mehr Information.
 
 # 3d window
 dialog.3d.title=GpsPrune-3D-Ansicht
@@ -555,11 +583,12 @@ confirm.addtimeoffset=Zeitverschiebung aufgerechnet
 confirm.addaltitudeoffset=H\u00f6henverschiebung aufgerechnet
 confirm.rearrangewaypoints=Wegpunkte neu angeordnet
 confirm.rearrangephotos=Fotos neu angeordnet
+confirm.splitsegments=Es wurden %d Schnitte gemacht
+confirm.sewsegments=Es wurden %d Verbindungen gemacht
 confirm.cutandmove=Bereich verschoben
 confirm.interpolate=Punkte eingef\u00fcgt
 confirm.convertnamestotimes=Wegpunktnamen umgewandelt
-confirm.saveexif.ok1=Es wurden
-confirm.saveexif.ok2=Fotodateien geschrieben
+confirm.saveexif.ok=Es wurden %d Fotodateien geschrieben
 confirm.undo.single=Operation r\u00fcckg\u00e4ngig gemacht
 confirm.undo.multi=Operationen r\u00fcckg\u00e4ngig gemacht
 confirm.jpegload.single=Foto wurde geladen
@@ -573,13 +602,23 @@ confirm.correlatephotos.multi=Fotos wurden korreliert
 confirm.createpoint=Punkt erzeugt
 confirm.rotatephoto=Foto gedreht
 confirm.running=In Bearbeitung ...
-confirm.lookupsrtm1=Es wurden
-confirm.lookupsrtm2=H\u00f6henwerte gefunden
+confirm.lookupsrtm=Es wurden %d H\u00f6henwerte gefunden
+confirm.downloadsrtm=Es wurden %d Dateien heruntergeladen
+confirm.downloadsrtm.1=Es wurde %d Datei heruntergeladen
+confirm.downloadsrtm.none=Keine Dateien heruntergeladen, alle waren schon gespeichert.
 confirm.deletefieldvalues=Feldwerte gel\u00f6scht
 confirm.audioload=Audiodateien geladen
 confirm.correlateaudios.single=Audio wurde korreliert
 confirm.correlateaudios.multi=Audios wurden korreliert
 
+# Tips
+tip.title=Tipp
+tip.useamapcache=Mit lokal-gespeicherten Kartenkacheln (Einstellungen -> Karten auf Festplatte speichern)\nk\u00f6nnen Sie die Darstellung beschleunigen und Netzwerkverkehr reduzieren.
+tip.learntimeparams=Wenn Sie Track -> Zeitparameter erlernen mit Ihren Tracks benutzen\ndann werden die berechneten Werten genauer.
+tip.downloadsrtm=Sie k\u00f6nnen diese Funktion beschleunigen indem Sie\nOnline -> SRTM Dateien herunterladen aufrufen\num die Daten lokal zu speichern.
+tip.usesrtmfor3d=Dieser Track hat keine H\u00f6heninformation.\nSie k\u00f6nnen die SRTM Funktionen verwenden, um\nH\u00f6henwerte abzusch\u00e4tzen.
+tip.manuallycorrelateone=Mit mindestens einem manuell verbundenen Element kann die Zeitdifferenz automatisch berechnet werden.
+
 # Buttons
 button.ok=OK
 button.back=Zur\u00fcck
@@ -597,6 +636,7 @@ button.yes=Ja
 button.no=Nein
 button.yestoall=Ja f\u00fcr alle
 button.notoall=Nein f\u00fcr alle
+button.always=Ja, immer
 button.select=Ausw\u00e4hlen
 button.selectall=Alle ausw\u00e4hlen
 button.selectnone=Nichts ausw\u00e4hlen
@@ -685,7 +725,6 @@ fieldname.newsegment=Segment
 fieldname.custom=Custom
 fieldname.prefix=Feld
 fieldname.distance=L\u00e4nge
-fieldname.movingdistance=Wegstrecke
 fieldname.duration=Zeitdauer
 fieldname.speed=Geschwindigkeit
 fieldname.verticalspeed=Vertikale Geschwindigkeit
@@ -726,6 +765,7 @@ logic.or=oder
 # External urls
 url.googlemaps=maps.google.de
 wikipedia.lang=de
+openweathermap.lang=de
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -747,6 +787,8 @@ undo.deletemarked=Punkte l\u00f6schen
 undo.insert=Punkte hinzuf\u00fcgen
 undo.reverse=Bereich umdrehen
 undo.mergetracksegments=Trackabschnitte verbinden
+undo.splitsegments=in Trackabschnitte schneiden
+undo.sewsegments=Trackabschnitte zusammenf\u00fcgen
 undo.addtimeoffset=Zeitverschiebung aufrechnen
 undo.addaltitudeoffset=H\u00f6henverschiebung aufrechnen
 undo.rearrangewaypoints=Wegpunkte neu anordnen
@@ -769,10 +811,8 @@ error.save.failed=Speichern von Daten in Datei fehlgeschlagen
 error.saveexif.filenotfound=Bilddatei nicht gefunden
 error.saveexif.cannotoverwrite1=Bilddatei
 error.saveexif.cannotoverwrite2=ist schreibgesch\u00fctzt. Als Kopie speichern?
-error.saveexif.failed1=
-error.saveexif.failed2=Bilder konnten nicht gespeichert werden
-error.saveexif.forced1=Bei
-error.saveexif.forced2=der Bilder musste das Speichern erzwungen werden
+error.saveexif.failed=%d Bilder konnten nicht gespeichert werden
+error.saveexif.forced=Bei %d der Bilder musste das Speichern erzwungen werden
 error.load.dialogtitle=Fehler beim Laden
 error.load.noread=Datei konnte nicht gelesen werden
 error.load.nopoints=Keine g\u00fcltigen Daten in Datei gefunden
@@ -809,3 +849,6 @@ 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.
+error.tracksplit.nosplit=Der Track konnte nicht aufgesplittet werden.
+error.downloadsrtm.nocache=Die Dateien konnten nicht gespeichert werden.\nBitte pr\u00fcfen Sie den Kartenordner nach.
+error.sewsegments.nothingdone=Es wurden keine Verbindungen gemacht.\nEs gibt jetzt %d Trackabschnitte.
diff --git a/tim/prune/lang/prune-texts_de_CH.properties b/tim/prune/lang/prune-texts_de_CH.properties
index 808bfae..0c192e8 100644
--- a/tim/prune/lang/prune-texts_de_CH.properties
+++ b/tim/prune/lang/prune-texts_de_CH.properties
@@ -7,6 +7,7 @@ menu.file.addphotos=F\u00f6telis inn\u00e4tue
 menu.file.recentfiles=Letzschti aagluegte Files
 menu.file.save=Als Text Speichere
 menu.file.exit=Be\u00e4nde
+menu.online=Online
 menu.track=Track
 menu.track.undo=Undo
 menu.track.clearundo=Undo-Liste l\u00f6sche
@@ -14,7 +15,7 @@ menu.track.markrectangle=P\u00fcnkte inem Viereck markiere
 menu.track.deletemarked=Markierte P\u00fcnkte l\u00f6sche
 menu.track.rearrange=Waypoints reorganisiere
 menu.track.rearrange.start=Alli zum Aafang
-menu.track.rearrange.end=Alli zum �nde
+menu.track.rearrange.end=Alli zum \u00c4nde
 menu.track.rearrange.nearest=Jede zum n\u00f6chsti Trackpunkt
 menu.range=Beriich
 menu.range.all=Alles selektiere
@@ -56,6 +57,7 @@ menu.map.editmode=P\u00fcnkte verschiebe
 
 # Alt keys for menus
 altkey.menu.file=F
+altkey.menu.online=O
 altkey.menu.track=T
 altkey.menu.range=B
 altkey.menu.point=P
@@ -101,9 +103,12 @@ function.fullrangedetails=Zues\u00e4tzlichi Beriichinfos
 function.estimatetime=Ziit absch\u00e4tze
 function.learnestimationparams=Ziitparameter erlerne
 function.setmapbg=Karte Hintegrund setz\u00e4
+function.splitsegments=In Tracksegm\u00e4nte schniide
+function.sewsegments=Tracksegm\u00e4nte z\u00e4mef\u00fcge
 function.getgpsies=Gpsies Tracks hol\u00e4
 function.uploadgpsies=Date zum Gpsies uufalad\u00e4
 function.lookupsrtm=H\u00f6hendate vonem SRTM hole
+function.downloadsrtm=SRTM Files abalade
 function.getwikipedia=Im Wikipedia in dr N\u00f6chi naaluege
 function.searchwikipedianames=Wikipedia mit Name durasueche
 function.downloadosm=OSM-Date f\u00fcr dere Gebiet abalad\u00e4
@@ -125,11 +130,12 @@ function.setlinewidth=Liniedicke setz\u00e4
 function.setlanguage=Sproch setz\u00e4
 function.help=Hilfe
 function.showkeys=Tastekombinatione aazeige
-function.about=�ber GpsPrune
+function.about=\u00dcber GpsPrune
 function.checkversion=Pruef nach ne noie Version
 function.saveconfig=Iistellige speichere
 function.diskcache=Karten uufem Disk speichere
 function.managetilecache=Kartebildli verwolte
+function.getweatherforecast=W\u00e4tterprognose abalade
 
 # Dialogs
 dialog.exit.confirm.title=GpsPrune be\u00e4nde
@@ -141,7 +147,7 @@ dialog.deletepoint.deletephoto=s F\u00f6teli vonem Punkt au l\u00f6sch\u00e4?
 dialog.deletephoto.title=F\u00f6teli entfern\u00e4
 dialog.deletephoto.deletepoint=Punkt vonem F\u00f6teli au l\u00f6sch\u00e4?
 dialog.deleteaudio.deletepoint=Punkt vonem Audio au l\u00f6sch\u00e4?
-dialog.openoptions.title=�ffne Optionen
+dialog.openoptions.title=\u00d6ffne Optionen
 dialog.openoptions.filesnippet=Extrakt vom File
 dialog.load.table.field=F\u00e4ld
 dialog.load.table.datatype=Date Typ
@@ -214,7 +220,7 @@ dialog.save.overwrite.title=s'File existiert scho
 dialog.save.overwrite.text=s'File existiert scho. Sind Sie sicher, Sie wend s'File \u00fcberschriibe?
 dialog.save.notypesselected=Kei Punktetype sin uusgew\u00e4hlt worde
 dialog.exportkml.text=Titel f\u00fcr die Date
-dialog.exportkml.altitude=Absolut H\u00f6chiinformation (f\u00fcrs Fliege)
+dialog.exportkml.altitude=Absolut H\u00f6chiinformation (f\u00fcrs Fl\u00fc\u00fcge)
 dialog.exportkml.kmz=Date ins kmz File komprimier\u00e4
 dialog.exportkml.exportimages=Bildli ins Kmz exportier\u00e4
 dialog.exportkml.imagesize=Bildligr\u00f6sse
@@ -237,6 +243,8 @@ dialog.exportpov.modelstyle=Modellstil
 dialog.exportpov.ballsandsticks=B\u00e4lle und Schtange
 dialog.exportpov.tubesandwalls=R\u00f6hre und W\u00e4nde
 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.3d.useterrain=Gel\u00e4nde aazeige
+dialog.3d.terraingridsize=Gittergr\u00f6sse
 dialog.exportpov.baseimage=Grundbild
 dialog.exportpov.cannotmakebaseimage=Bild chann n\u00f6d gspeicheret werde
 dialog.baseimage.title=Kartenbild
@@ -252,6 +260,7 @@ 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.drawtrackpoints=Trackp\u00fcnkte au zeichne
 dialog.exportimage.textscalepercent=Text Skalierig (%)
 dialog.pointtype.desc=Folgende Punkttype speichere:
 dialog.pointtype.track=Trackp\u00fcnkte
@@ -373,7 +382,6 @@ dialog.correlate.photoselect.intro=W\u00e4hlet Sie eini vo deren F\u00f6teli uus
 dialog.correlate.select.photoname=F\u00f6teli Name
 dialog.correlate.select.timediff=Ziitdiffer\u00e4nz
 dialog.correlate.select.photolater=F\u00f6teli sp\u00f6ter
-dialog.correlate.options.tip=Tipp: Mit mindeschtens einem verbundenen Elem\u00e4nt kann die Ziitdiffer\u00e4nz automatisch ber\u00e4chnet werd\u00e4.
 dialog.correlate.options.intro=W\u00e4hlet Sie die Optione uus f\u00fcr die Korrelierig
 dialog.correlate.options.offsetpanel=Ziitunterschied
 dialog.correlate.options.offset=Unterschied
@@ -396,13 +404,13 @@ dialog.correlate.filetimes2=der Tonspuren
 dialog.correlate.correltimes=F\u00fcrs Korreliere, folgendes verw\u00e4nde:
 dialog.correlate.timestamp.beginning=Aafang
 dialog.correlate.timestamp.middle=Mitti
-dialog.correlate.timestamp.end=�nde
+dialog.correlate.timestamp.end=\u00c4nde
 dialog.correlate.audioselect.intro=W\u00e4hlet Sie eini vo deren Audios uus, um die Ziitdiffer\u00e4nz zu ber\u00e4chn\u00e4
 dialog.correlate.select.audioname=Audio Name
 dialog.correlate.select.audiolater=Audio sp\u00f6ter
 dialog.rearrangephotos.desc=Bitte Ziel und Reihefolge von d P\u00fcnkte setze
 dialog.rearrangephotos.tostart=zum Aafang
-dialog.rearrangephotos.toend=zum �nde
+dialog.rearrangephotos.toend=zum \u00c4nde
 dialog.rearrangephotos.nosort=N\u00f6d sortiere
 dialog.rearrangephotos.sortbyfilename=per Filename sortiere
 dialog.rearrangephotos.sortbytime=per Ziit sortiere
@@ -416,21 +424,20 @@ dialog.compress.singletons.paramdesc=Distanz faktor
 dialog.compress.douglaspeucker.title=Douglas-Peucker Komprimierig
 dialog.compress.douglaspeucker.paramdesc=Span Faktor
 dialog.compress.summarylabel=P\u00fcnkte zu entf\u00e4rn\u00e4
-dialog.compress.confirm1=Es sin
-dialog.compress.confirm2=P\u00fcnkt markiert.\nMit Track->Markierte P\u00fcnkte l\u00f6sche werdet sie gl\u00f6scht
+dialog.compress.confirm=Es sin %s P\u00fcnkt markiert worde.\nWend Sie die jetz l\u00f6sche?
 dialog.compress.confirmnone=es sin kei P\u00fcnkte markiert worde
 dialog.deletemarked.nonefound=Kei P\u00fcnkte h\u00e4tte gel\u00f6scht werde k\u00f6nne
 dialog.pastecoordinates.desc=G\u00e4bet Sie hier die Koordinaten inn\u00e4
 dialog.pastecoordinates.coords=Koordinate
 dialog.pastecoordinates.nothingfound=Pr\u00fcefet Sie die Koordinate und versuechet nomal
-dialog.help.help=Bitte lueg na\n http://activityworkshop.net/software/gpsprune/\nf\u00fcr wiitere Information und Benutzeraaleitige.
+dialog.help.help=Bitte lueg na\n http://gpsprune.activityworkshop.net/\nf\u00fcr wiitere Information und Benutzeraaleitige.
 dialog.about.version=Version
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune isch s Programm f\u00fcrs Lade, Darstelle und Editiere vo Date von GPS Ger\u00e4te.
 dialog.about.summarytext2=Es isch unter den Gnu GPL zur Verf\u00fcegig gstellt,f\u00fcr frei, gratis und offen Gebruuch und Wiiterentwicklig.<br>Kopiere, Wiiterverbreitig und Ver\u00e4nderige sin erlaubt und willkomme<br>unter die Bedingige im enthaltene <code>license.txt</code> File.
 dialog.about.summarytext3=Bitte lueget Sie na <code style="font-weight:bold">http://activityworkshop.net/</code> f\u00fcr wiitere Informatione und Benutzeraaleitige.
 dialog.about.languages=Verf\u00fcegbare Sproche
-dialog.about.translatedby=Schwiizerd\u00fc\u00fctschi �bersetzig vo activityworkshop.
+dialog.about.translatedby=Schwiizerd\u00fc\u00fctschi \u00dcbersetzig vo activityworkshop.
 dialog.about.systeminfo=Syschtem Info
 dialog.about.systeminfo.os=Betriebsyschtem
 dialog.about.systeminfo.java=Version vonem Java
@@ -451,7 +458,7 @@ dialog.about.credits.code=GpsPrune Code gschrieb\u00e4 vo
 dialog.about.credits.exifcode=Exif Code vo
 dialog.about.credits.icons=Einigi Bilder vo
 dialog.about.credits.translators=Dolm\u00e4tscher
-dialog.about.credits.translations=�bersetzige mit dr Hilfe vo
+dialog.about.credits.translations=\u00dcbersetzige mit dr Hilfe vo
 dialog.about.credits.devtools=Entwicklungsw\u00e4rkz\u00fc\u00fcge
 dialog.about.credits.othertools=Anderi W\u00e4rkz\u00fc\u00fcge
 dialog.about.credits.thanks=Danke an
@@ -462,9 +469,9 @@ dialog.checkversion.newversion1=Ne noii Version vonem GpsPrune isch jetzt usse!
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Die noii Version isch am
 dialog.checkversion.releasedate2=ussecho.
-dialog.checkversion.download=Um die noii Version runterzlade, schauet Sie na http://activityworkshop.net/software/gpsprune/download.html.
+dialog.checkversion.download=Um die noii Version runterzlade, schauet Sie na http://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=Aastatt d'Muus k\u00f6nnet Sie diese Tastekombinationen nutze
-dialog.keys.keylist=<table><tr><td>Pfiil Taste</td><td>Karte verschiebe</td></tr><tr><td>Strg + links, r\u00e4chts Pfiil</td><td>Vorherigi oder n\u00f6chsti Punkt markiere</td></tr><tr><td>Strg + uuf, aba Pfiil</td><td>Ii- oder Uusezoome</td></tr><tr><td>Strg + Bild uuf, ab</td><td>Vorherigi oder n\u00f6chsti Segm\u00e4nt markiere</td></tr><tr><td>Strg + Pos1, �nde</td><td>Erschti oder letschti Punkt markiere</td></tr><tr><td>Entf</td><td>Aktuelli Punkt l\u00f6sche</td></tr></table>
+dialog.keys.keylist=<table><tr><td>Pfiil Taste</td><td>Karte verschiebe</td></tr><tr><td>Strg + links, r\u00e4chts Pfiil</td><td>Vorherigi oder n\u00f6chsti Punkt markiere</td></tr><tr><td>Strg + uuf, aba Pfiil</td><td>Ii- oder Uusezoome</td></tr><tr><td>Strg + Bild uuf, ab</td><td>Vorherigi oder n\u00f6chsti Segm\u00e4nt markiere</td></tr><tr><td>Strg + Pos1, \u00c4nde</td><td>Erschti oder letschti Punkt markiere</td></tr><tr><td>Entf</td><td>Aktuelli Punkt l\u00f6sche</td></tr></table>
 dialog.keys.normalmodifier=Strg
 dialog.keys.macmodifier=Kommando
 dialog.saveconfig.desc=Die folgendi Iinstellige k\u00f6nne gspeicheret werde :
@@ -525,13 +532,34 @@ dialog.diskcache.tileset.multiple=mehreri
 dialog.diskcache.deleteold=Uualti Kachle l\u00f6sche
 dialog.diskcache.maximumage=Maximali Alter (Tag)
 dialog.diskcache.deleteall=Alli Kachle l\u00f6sche
-dialog.diskcache.deleted1=Es sin
-dialog.diskcache.deleted2=Files uusem Ordner gl\u00f6scht worde
+dialog.diskcache.deleted=Es sin %d Files uusem Ordner gl\u00f6scht worde
 dialog.deletefieldvalues.intro=W\u00e4hlet Sie s F\u00e4ld uus zum l\u00f6sche
 dialog.deletefieldvalues.nofields=Es sin kei F\u00e4lder z'l\u00f6sche f\u00fcr dere Beriich
 dialog.setlinewidth.text=G\u00e4bet Sie die Dicke vonen Linien ii (1-4)
 dialog.downloadosm.desc=Best\u00e4tige um rohi OSM Date f\u00fcrn Gebiet aba zlade:
 dialog.searchwikipedianames.search=Sueche na:
+dialog.weather.location=Ort
+dialog.weather.update=Prognose aktualisiert
+dialog.weather.sunrise=Sonnenufgang
+dialog.weather.sunset=Sonnenuntergang
+dialog.weather.temperatureunits=Temperature
+dialog.weather.currentforecast=Aktuell
+dialog.weather.dailyforecast=T\u00e4glichi Prognose
+dialog.weather.3hourlyforecast=Dr\u00fc\u00fc-st\u00fcndlichi Prognose
+dialog.weather.day.now=Jetz\u00e4
+dialog.weather.day.today=H\u00fc\u00fct
+dialog.weather.day.tomorrow=Morn
+dialog.weather.day.monday=M\u00e4ntig
+dialog.weather.day.tuesday=Ziischtig
+dialog.weather.day.wednesday=Mittwuch
+dialog.weather.day.thursday=Duunschtig
+dialog.weather.day.friday=Friitig
+dialog.weather.day.saturday=Samschtig
+dialog.weather.day.sunday=Sunntig
+dialog.weather.wind=Wind
+dialog.weather.temp=Temp
+dialog.weather.humidity=R.L.
+dialog.weather.creditnotice=Diese Date sin vo openweathermap.org zur Verf\u00fcegig gestellt worde. Uf d Websiite h\u00e4ts no meh Infos.
 
 # 3d window
 dialog.3d.title=GpsPrune Dr\u00fc\u00fc-d Aasicht
@@ -550,11 +578,12 @@ confirm.addtimeoffset=Ziitverschiebig zutue
 confirm.addaltitudeoffset=H\u00f6chiverschiebig zutue
 confirm.rearrangewaypoints=Waypoints umorganisiert
 confirm.rearrangephotos=Fotos umorganisiert
+confirm.splitsegments=Es sin %d Schnitte gmacht worde
+confirm.sewsegments=Es sin %d Verbindige gemacht worde
 confirm.cutandmove=Beriich gmoved
 confirm.interpolate=P\u00fcnkte iigf\u00fcgt worde
 confirm.convertnamestotimes=Waypointname verwondlet
-confirm.saveexif.ok1=Es sin
-confirm.saveexif.ok2=F\u00f6telis gschriebe worde
+confirm.saveexif.ok=Es sin %d F\u00f6telis gschriebe worde
 confirm.undo.single=Operation r\u00fcckg\u00e4ngig gmacht worde.
 confirm.undo.multi=Operatione r\u00fcckg\u00e4ngig gmacht worde.
 confirm.jpegload.single=F\u00f6teli isch glade worde
@@ -568,21 +597,31 @@ confirm.correlatephotos.multi=F\u00f6telis sin korreliert worde
 confirm.createpoint=Punkt kreiert worde
 confirm.rotatephoto=F\u00f6teli umgedr\u00e4it worde
 confirm.running=Am Laufe ...
-confirm.lookupsrtm1=Es sin
-confirm.lookupsrtm2=H\u00f6henwerte gfunde
+confirm.lookupsrtm=Es sin %d H\u00f6henwerte gfunde
+confirm.downloadsrtm=Es sin %d Files abeglade
+confirm.downloadsrtm.1=Sisch %d File abeglade
+confirm.downloadsrtm.none=Kei Files abeglade, die sin alli scho da gsi.
 confirm.deletefieldvalues=Feldw\u00e4rte gl\u00f6scht worde
 confirm.audioload=Audiofiles glade worde
 confirm.media.removed=entf\u00e4rnt
 confirm.correlateaudios.single=Audiofile isch korreliert worde
 confirm.correlateaudios.multi=Audiofiles sin korreliert worde
 
+# Tips
+tip.title=Tipp
+tip.useamapcache=Mit lokali Kartekachle (Iistellige -> Karten uufem Disk speichere)\nk\u00f6nnet Sie d Darstellig bschleunige und Netzwerkverkehr reduziere.
+tip.learntimeparams=Wenn Sie Track -> Ziitparameter erlerne mit Ihren Tracks benutze\ndann werdet d ber\u00e4chneti Werte gnauer.
+tip.downloadsrtm=Sie k\u00f6nnet d Funktion beschleunige indem Sie\nOnline -> SRTM Files abalade uufrufe\num d Date lokal z'speichere.
+tip.usesrtmfor3d=Dere Track h\u00e4t kei H\u00f6chiinformation.\nSie k\u00f6nnet d SRTM Funktione verw\u00e4nde, um\nH\u00f6chiwerte abz'sch\u00e4tze.
+tip.manuallycorrelateone=Mit mindeschtens einem verbundenen Elem\u00e4nt kann die Ziitdiffer\u00e4nz automatisch ber\u00e4chnet werd\u00e4.
+
 # Buttons
 button.ok=OK
 button.back=Zrugg
 button.next=N\u00f6chst\u00e4
 button.finish=Fertig
 button.cancel=Abbr\u00e4ch\u00e4
-button.overwrite=�berschriib\u00e4
+button.overwrite=\u00dcberschriib\u00e4
 button.moveup=Uuf\u00e4 schieb\u00e4
 button.movedown=Aba schieb\u00e4
 button.edit=Editier\u00e4
@@ -593,6 +632,7 @@ button.yes=Ja
 button.no=Nei
 button.yestoall=Ja f\u00fcr alli
 button.notoall=Nei f\u00fcr alli
+button.always=Ja, immer
 button.select=Uusw\u00e4hle
 button.selectall=Alli uusw\u00e4hle
 button.selectnone=N\u00fc\u00fct uusw\u00e4hle
@@ -680,7 +720,6 @@ fieldname.newsegment=Segm\u00e4nt
 fieldname.custom=Custom
 fieldname.prefix=F\u00e4ld
 fieldname.distance=L\u00e4ngi
-fieldname.movingdistance=Wegl\u00e4ngi
 fieldname.duration=Ziitl\u00e4ngi
 fieldname.speed=Gschwindikeit
 fieldname.verticalspeed=Uf/Ab Gschwindikeit
@@ -721,6 +760,7 @@ logic.or=oder
 # External urls
 url.googlemaps=maps.google.ch
 wikipedia.lang=als
+openweathermap.lang=de
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -742,6 +782,8 @@ undo.deletemarked=P\u00fcnkte l\u00f6sch\u00e4
 undo.insert=P\u00fcnkte inn\u00e4tu\u00e4
 undo.reverse=Beriich umdr\u00e4hie
 undo.mergetracksegments=Tracksegm\u00e4nte merge
+undo.splitsegments=in Tracksegm\u00e4nte schniide
+undo.sewsegments=Tracksegm\u00e4nte z\u00e4mef\u00fcge
 undo.addtimeoffset=Ziitverschiebig zutue
 undo.addaltitudeoffset=H\u00f6chiverschiebig zutue
 undo.rearrangewaypoints=Waypoints reorganisier\u00e4
@@ -764,10 +806,8 @@ error.save.failed=Speichere vom File fehlgschlage
 error.saveexif.filenotfound=F\u00f6teli File n\u00f6d gfunde
 error.saveexif.cannotoverwrite1=F\u00f6teli File
 error.saveexif.cannotoverwrite2=isch n\u00f6d schriibbar. Speichere na einer Kopie?
-error.saveexif.failed1=
-error.saveexif.failed2=von d Bilder han i n\u00f6d k\u00f6nne speichere
-error.saveexif.forced1=
-error.saveexif.forced2=von d Bilder han i m\u00fcsse forziere
+error.saveexif.failed=%d von d Bilder han i n\u00f6d k\u00f6nne speichere
+error.saveexif.forced=%d von d Bilder han i m\u00fcsse forziere
 error.load.dialogtitle=F\u00e4hle bim Lade
 error.load.noread=File cha n\u00f6d glase werde
 error.load.nopoints=Kei g\u00fcltigi Information inem File gfunde
@@ -778,7 +818,7 @@ error.jpegload.dialogtitle=F\u00e4hle bim Lade von F\u00f6telis
 error.jpegload.nofilesfound=Kei Files gfunde
 error.jpegload.nojpegsfound=Kei Jpegs gfunde
 error.jpegload.nogpsfound=Kei GPS Information gfunde
-error.jpegload.exifreadfailed=EXIF Uufruef isch fehlgschlage. Kei EXIF Infos k\u00f6nnet gl\u00e4se werde\nohni nen interni oder ext\u00e4rni Bibliothek.
+error.jpegload.exifreadfailed=Exif Uufruef isch fehlgschlage. Kei Exif Infos k\u00f6nnet gl\u00e4se werde\nohni nen interni oder ext\u00e4rni Bibliothek.
 error.audioload.nofilesfound=Kei Audiofiles gfunde
 error.gpsload.unknown=Unbekannts F\u00e4hler
 error.undofailed.title=Undo isch fehlgschlage worde
@@ -804,3 +844,6 @@ 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.
+error.tracksplit.nosplit=Es isch n\u00f6d m\u00f6glech gsi, den Track uufz'schniide.
+error.downloadsrtm.nocache=Die Files k\u00f6nnet n\u00f6d gspeicheret werde.\nBitte pr\u00fcefet Sie den Kartenordner na.
+error.sewsegments.nothingdone=Es sin kei Verbindige gmacht worde.\nEs h\u00e4t jetzt %d Tracksegm\u00e4nte.
diff --git a/tim/prune/lang/prune-texts_en.properties b/tim/prune/lang/prune-texts_en.properties
index 0102075..0e8976c 100644
--- a/tim/prune/lang/prune-texts_en.properties
+++ b/tim/prune/lang/prune-texts_en.properties
@@ -7,6 +7,7 @@ menu.file.addphotos=Add photos
 menu.file.recentfiles=Recent files
 menu.file.save=Save as text
 menu.file.exit=Exit
+menu.online=Online
 menu.track=Track
 menu.track.undo=Undo
 menu.track.clearundo=Clear undo list
@@ -57,6 +58,7 @@ menu.map.editmode=Edit mode
 
 # Alt keys for menus
 altkey.menu.file=F
+altkey.menu.online=N
 altkey.menu.range=R
 altkey.menu.track=T
 altkey.menu.point=P
@@ -102,9 +104,12 @@ function.distances=Distances
 function.fullrangedetails=Full range details
 function.estimatetime=Estimate time
 function.learnestimationparams=Learn time estimation parameters
+function.splitsegments=Split track into segments
+function.sewsegments=Sew track segments together
 function.getgpsies=Get Gpsies tracks
 function.uploadgpsies=Upload track to Gpsies
 function.lookupsrtm=Get altitudes from SRTM
+function.downloadsrtm=Download SRTM tiles
 function.getwikipedia=Get nearby Wikipedia articles
 function.searchwikipedianames=Search Wikipedia by name
 function.downloadosm=Download OSM data for area
@@ -135,6 +140,7 @@ function.checkversion=Check for new version
 function.saveconfig=Save settings
 function.diskcache=Save maps to disk
 function.managetilecache=Manage tile cache
+function.getweatherforecast=Get weather forecast
 
 # Dialogs
 dialog.exit.confirm.title=Exit GpsPrune
@@ -242,6 +248,8 @@ dialog.exportpov.modelstyle=Model style
 dialog.exportpov.ballsandsticks=Balls and sticks
 dialog.exportpov.tubesandwalls=Tubes and walls
 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.3d.useterrain=Show terrain
+dialog.3d.terraingridsize=Grid size
 dialog.exportpov.baseimage=Base image
 dialog.exportpov.cannotmakebaseimage=Cannot write base image
 dialog.baseimage.title=Map image
@@ -257,6 +265,7 @@ 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.drawtrackpoints=Draw track points
 dialog.exportimage.textscalepercent=Text scale factor (%)
 dialog.pointtype.desc=Save the following point types:
 dialog.pointtype.track=Track points
@@ -378,7 +387,6 @@ dialog.correlate.photoselect.intro=Select one of these correlated photos to use
 dialog.correlate.select.photoname=Photo name
 dialog.correlate.select.timediff=Time difference
 dialog.correlate.select.photolater=Photo later
-dialog.correlate.options.tip=Tip: By manually connecting at least one item, the time offset can be calculated for you.
 dialog.correlate.options.intro=Select the options for automatic correlation
 dialog.correlate.options.offsetpanel=Time offset
 dialog.correlate.options.offset=Offset
@@ -421,8 +429,7 @@ dialog.compress.singletons.paramdesc=Distance factor
 dialog.compress.douglaspeucker.title=Douglas-Peucker compression
 dialog.compress.douglaspeucker.paramdesc=Span factor
 dialog.compress.summarylabel=Points to delete
-dialog.compress.confirm1=
-dialog.compress.confirm2=points have been marked.\nUse Track->Delete marked points to delete them
+dialog.compress.confirm=%d points have been marked.\nDelete these marked points now?
 dialog.compress.confirmnone=no points have been marked
 dialog.deletemarked.nonefound=No data points could be removed
 dialog.pastecoordinates.desc=Enter or paste the coordinates here
@@ -530,13 +537,34 @@ dialog.diskcache.tileset.multiple=multiple
 dialog.diskcache.deleteold=Delete old tiles
 dialog.diskcache.maximumage=Maximum age (days)
 dialog.diskcache.deleteall=Delete all tiles
-dialog.diskcache.deleted1=Deleted
-dialog.diskcache.deleted2=files from the cache
+dialog.diskcache.deleted=Deleted %d files from the cache
 dialog.deletefieldvalues.intro=Select the field to delete for the current range
 dialog.deletefieldvalues.nofields=There are no fields to delete for this range
 dialog.setlinewidth.text=Enter the thickness of lines to draw for the tracks (1-4)
 dialog.downloadosm.desc=Confirm to download the raw OSM data for the specified area:
 dialog.searchwikipedianames.search=Search for:
+dialog.weather.location=Location
+dialog.weather.update=Forecast updated
+dialog.weather.sunrise=Sunrise
+dialog.weather.sunset=Sunset
+dialog.weather.temperatureunits=Temperatures
+dialog.weather.currentforecast=Current weather
+dialog.weather.dailyforecast=Daily forecast
+dialog.weather.3hourlyforecast=Three-hourly forecast
+dialog.weather.day.now=Current weather
+dialog.weather.day.today=Today
+dialog.weather.day.tomorrow=Tomorrow
+dialog.weather.day.monday=Monday
+dialog.weather.day.tuesday=Tuesday
+dialog.weather.day.wednesday=Wednesday
+dialog.weather.day.thursday=Thursday
+dialog.weather.day.friday=Friday
+dialog.weather.day.saturday=Saturday
+dialog.weather.day.sunday=Sunday
+dialog.weather.wind=Wind
+dialog.weather.temp=Temp
+dialog.weather.humidity=Humidity
+dialog.weather.creditnotice=This data is made available by openweathermap.org. Their website has more details.
 
 # 3d window
 dialog.3d.title=GpsPrune Three-d view
@@ -555,11 +583,12 @@ confirm.addtimeoffset=Time offset added
 confirm.addaltitudeoffset=Altitude offset added
 confirm.rearrangewaypoints=Waypoints rearranged
 confirm.rearrangephotos=Photos rearranged
+confirm.splitsegments=%d segment splits were made
+confirm.sewsegments=%d segment joins were made
 confirm.cutandmove=Selection moved
 confirm.interpolate=Points added
 confirm.convertnamestotimes=Waypoint names converted
-confirm.saveexif.ok1=Saved
-confirm.saveexif.ok2=photo files
+confirm.saveexif.ok=Saved %d photo files
 confirm.undo.single=operation undone
 confirm.undo.multi=operations undone
 confirm.jpegload.single=photo was added
@@ -573,13 +602,23 @@ confirm.correlatephotos.multi=photos were correlated
 confirm.rotatephoto=photo rotated
 confirm.createpoint=point created
 confirm.running=Running ...
-confirm.lookupsrtm1=Found
-confirm.lookupsrtm2=altitude values
+confirm.lookupsrtm=Found %d altitude values
+confirm.downloadsrtm=Downloaded %d files to the cache
+confirm.downloadsrtm.1=Downloaded %d file to the cache
+confirm.downloadsrtm.none=No files downloaded, they were already in the cache
 confirm.deletefieldvalues=Field values deleted
 confirm.audioload=Audio files added
 confirm.correlateaudios.single=audio was correlated
 confirm.correlateaudios.multi=audios were correlated
 
+# Tips, shown just once when appropriate
+tip.title=Tip
+tip.useamapcache=By setting up a disk cache (Settings -> Save maps to disk)\nyou can speed up the display and reduce network traffic.
+tip.learntimeparams=The results will be more accurate if you use\nTrack -> Learn time estimation parameters\non your recorded tracks.
+tip.downloadsrtm=You can speed this up by calling\nOnline -> Download SRTM tiles\nto save the data in your map cache.
+tip.usesrtmfor3d=This track doesn't have altitudes.\nYou can use the SRTM functions to get approximate\naltitudes for the 3d view.
+tip.manuallycorrelateone=By manually connecting at least one item, the time offset can be calculated for you.
+
 # Buttons
 button.ok=OK
 button.back=Back
@@ -597,6 +636,7 @@ button.yes=Yes
 button.no=No
 button.yestoall=Yes to all
 button.notoall=No to all
+button.always=Always
 button.select=Select
 button.selectall=Select all
 button.selectnone=Select none
@@ -685,7 +725,6 @@ fieldname.newsegment=Segment
 fieldname.custom=Custom
 fieldname.prefix=Field
 fieldname.distance=Distance
-fieldname.movingdistance=Moving distance
 fieldname.duration=Duration
 fieldname.speed=Speed
 fieldname.verticalspeed=Vertical speed
@@ -720,6 +759,10 @@ units.degminsec=Deg-min-sec
 units.degmin=Deg-min
 units.deg=Degrees
 units.iso8601=ISO 8601
+units.degreescelsius=Celsius
+units.degreescelsius.short=\u00b0C
+units.degreesfahrenheit=Fahrenheit
+units.degreesfahrenheit.short=\u00b0F
 
 # How to combine conditions, such as filters
 logic.and=and
@@ -728,6 +771,7 @@ logic.or=or
 # External urls
 url.googlemaps=maps.google.co.uk
 wikipedia.lang=en
+openweathermap.lang=en
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -749,6 +793,8 @@ undo.deletemarked=delete points
 undo.insert=insert points
 undo.reverse=reverse range
 undo.mergetracksegments=merge track segments
+undo.splitsegments=split track segments
+undo.sewsegments=sew track segments
 undo.addtimeoffset=add time offset
 undo.addaltitudeoffset=add altitude offset
 undo.rearrangewaypoints=rearrange waypoints
@@ -771,10 +817,8 @@ error.save.failed=Failed to save the data to file
 error.saveexif.filenotfound=Failed to find photo file
 error.saveexif.cannotoverwrite1=Photo file
 error.saveexif.cannotoverwrite2=is read-only and can't be overwritten. Write to copy?
-error.saveexif.failed1=Failed to save
-error.saveexif.failed2=of the images
-error.saveexif.forced1=
-error.saveexif.forced2=of the images required forcing
+error.saveexif.failed=Failed to save %d of the images
+error.saveexif.forced=%d of the images required forcing
 error.load.dialogtitle=Error loading data
 error.load.noread=Cannot read file
 error.load.nopoints=No coordinate information found in the file
@@ -785,7 +829,7 @@ error.jpegload.dialogtitle=Error loading photos
 error.jpegload.nofilesfound=No files found
 error.jpegload.nojpegsfound=No jpeg files found
 error.jpegload.nogpsfound=No GPS information found
-error.jpegload.exifreadfailed=Failed to read EXIF information. No EXIF information can be read\nwithout either an internal or external library.
+error.jpegload.exifreadfailed=Failed to read Exif information. No Exif information can be read\nwithout either an internal or external library.
 error.audioload.nofilesfound=No audio clips found
 error.gpsload.unknown=Unknown error
 error.undofailed.title=Undo failed
@@ -811,3 +855,6 @@ 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.
+error.tracksplit.nosplit=The track could not be split
+error.downloadsrtm.nocache=The files could not be saved.\nPlease check the disk cache.
+error.sewsegments.nothingdone=No segments could be sewn together.\nThere are now %d segments in the track.
diff --git a/tim/prune/lang/prune-texts_en_US.properties b/tim/prune/lang/prune-texts_en_US.properties
index 928d027..a28dd50 100644
--- a/tim/prune/lang/prune-texts_en_US.properties
+++ b/tim/prune/lang/prune-texts_en_US.properties
@@ -8,6 +8,7 @@ function.setcolours=Set colors
 dialog.exportkml.trackcolour=Track color
 dialog.saveconfig.prune.languagecode=Language code (EN_US)
 dialog.setcolours.intro=Click on a color patch to change the color
+dialog.colourchooser.title=Choose color
 
 # Measurement units
 units.metres=Meters
diff --git a/tim/prune/lang/prune-texts_es.properties b/tim/prune/lang/prune-texts_es.properties
index ffe8f1f..491134d 100644
--- a/tim/prune/lang/prune-texts_es.properties
+++ b/tim/prune/lang/prune-texts_es.properties
@@ -7,6 +7,7 @@ menu.file.addphotos=Cargar fotos
 menu.file.recentfiles=Archivos recientes
 menu.file.save=Guardar
 menu.file.exit=Salir
+menu.online=Online
 menu.track=Track
 menu.track.undo=Deshacer
 menu.track.clearundo=Despejar la lista de deshacer
@@ -23,7 +24,7 @@ menu.range.start=Fijar comienzo
 menu.range.end=Fijar final
 menu.range.average=Crear punto a la media del rango
 menu.range.reverse=Invertir rango
-menu.range.mergetracksegments=Unir los segmentos de track
+menu.range.mergetracksegments=Unir los segmentos del track
 menu.range.cutandmove=Cortar y mover selecci\u00f3n
 menu.point=Punto
 menu.point.editpoint=Editar punto
@@ -49,7 +50,7 @@ menu.map.zoomout=Reducir zoom
 menu.map.zoomfull=Mostrar todo
 menu.map.newpoint=Crear un punto nuevo
 menu.map.drawpoints=Crear series de puntos
-menu.map.connect=Conectar puntos de track
+menu.map.connect=Conectar puntos del track
 menu.map.autopan=Posicionar autom\u00e1ticamente
 menu.map.showmap=Mostrar el mapa
 menu.map.showscalebar=Mostrar barra de escala
@@ -57,6 +58,7 @@ menu.map.editmode=Editar puntos
 
 # Alt keys for menus
 altkey.menu.file=A
+altkey.menu.online=O
 altkey.menu.track=T
 altkey.menu.range=R
 altkey.menu.point=U
@@ -88,6 +90,7 @@ function.exportimage=Exportar imagen
 function.editwaypointname=Editar nombre de waypoint
 function.compress=Comprimir track
 function.deleterange=Eliminar rango
+function.croptrack=Truncar track
 function.interpolate=Interpolar puntos
 function.addtimeoffset=A\u00f1adir compensar tiempo
 function.addaltitudeoffset=A\u00f1adir compensar altitud
@@ -99,11 +102,13 @@ function.charts=Diagramas
 function.show3d=Mostrar en 3-D
 function.distances=Distancias
 function.fullrangedetails=Detalles adicionales de rango
+function.estimatetime=Estimar duraci\u00f3n
 function.setmapbg=Configurar fondo de mapa
 function.setpaths=Configurar rutas del programas
 function.getgpsies=Bajar ruta de Gpsies
 function.uploadgpsies=Subir recorrido a Gpsies
 function.lookupsrtm=Obtener altitudes de SRTM
+function.downloadsrtm=Descargar datos de SRTM
 function.getwikipedia=Obtener art\u00edculos de Wikipedia cercanos
 function.searchwikipedianames=Buscar en Wikipedia por nombre
 function.downloadosm=Descargar datos OSM del \u00e1rea
@@ -132,6 +137,7 @@ function.checkversion=Buscar una nueva versi\u00f3n
 function.saveconfig=Guardar preferencias
 function.diskcache=Guardar mapas en disco
 function.managetilecache=Administrar cache de mapas
+function.getweatherforecast=Obtener pron\u00f3stico del tiempo
 
 # Dialogs
 dialog.exit.confirm.title=Salir de GpsPrune
@@ -179,10 +185,19 @@ dialog.gpssend.sendtracks=Enviar tracks
 dialog.gpssend.trackname=Nombre del track
 dialog.gpsbabel.filters=Filtros
 dialog.addfilter.title=A\u00f1adir filtro
+dialog.gpsbabel.filter.discard=Desechar
 dialog.gpsbabel.filter.simplify=Simplificar
 dialog.gpsbabel.filter.distance=Distancia
 dialog.gpsbabel.filter.interpolate=Interpolar
+dialog.gpsbabel.filter.discard.intro=Desechar puntos si
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
 dialog.gpsbabel.filter.discard.numsats=N\u00famero de sat\u00e9lites <
+dialog.gpsbabel.filter.simplify.maxpoints=Numero de puntos <
+dialog.gpsbabel.filter.distance.distance=Si distancia <
+dialog.gpsbabel.filter.distance.time=y differencia horaria <
+dialog.gpsbabel.filter.interpolate.distance=Si distancia >
+dialog.gpsbabel.filter.interpolate.time=o differencia horaria >
 dialog.saveoptions.title=Guardar archivo
 dialog.save.fieldstosave=Campos a guardar
 dialog.save.table.field=Campo
@@ -210,7 +225,8 @@ dialog.exportgpx.copysource=Copiar la fuente
 dialog.exportgpx.encoding=Codificaci\u00f3n
 dialog.exportgpx.encoding.system=Sistema
 dialog.exportgpx.encoding.utf8=UTF-8
-dialog.exportpov.text=Introdzca los Parametros para exportar
+dialog.3d.useterrain=Terreno
+dialog.exportpov.text=Introduca los par\u00e1metros para exportar
 dialog.exportpov.font=Fuente
 dialog.exportpov.camerax=C\u00e1mara X
 dialog.exportpov.cameray=C\u00e1mara Y
@@ -219,15 +235,21 @@ dialog.exportpov.modelstyle=Estilo
 dialog.exportpov.ballsandsticks=Balas en palos
 dialog.exportpov.tubesandwalls=Tubos y paredes
 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.baseimage.title=Imagen de mapa
+dialog.baseimage.mapsource=Proveedor de mapas
+dialog.baseimage.useimage=Usar imagen
 dialog.baseimage.zoom=Zoom
+dialog.baseimage.incomplete=Imagen incompleta
 dialog.baseimage.tiles=Recuadros
 dialog.baseimage.size=Tama\u00f1o de la imagen
 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
 dialog.exportsvg.gradients=Usar degradado para sombras
+dialog.exportimage.drawtrack=Dibujar track
+dialog.exportimage.drawtrackpoints=Dibujar puntos del track
 dialog.pointtype.desc=Salvar los siguientes tipos de puntos:
-dialog.pointtype.track=Puntos de track
+dialog.pointtype.track=Puntos del track
 dialog.pointtype.waypoint=Waypoints
 dialog.pointtype.photo=Puntos de foto
 dialog.pointtype.audio=Puntos de audio
@@ -242,10 +264,11 @@ dialog.undo.pretext=Por favor, seleccione la operaci\u00f3n(es) a deshacer
 dialog.undo.none.title=No se puede deshacer
 dialog.undo.none.text=Ninguna operaci\u00f3n a deshacer
 dialog.clearundo.title=Despejar la lista de deshacer
-dialog.clearundo.text=\u00bfEsta seguro que desea despejar la lista de deshacer?, �se perder\u00e1 toda la informaci\u00f3n!
+dialog.clearundo.text=\u00bfEsta seguro que desea despejar la lista de deshacer?, \u00a1se perder\u00e1 toda la informaci\u00f3n!
 dialog.pointedit.title=Editar punto
 dialog.pointedit.intro=Seleccione cada campo para modificar el valor
 dialog.pointedit.table.field=Campo
+dialog.pointedit.nofield=Ning\u00fan campo seleccionado
 dialog.pointedit.table.value=Valor
 dialog.pointnameedit.name=Nombre de waypoint
 dialog.pointnameedit.uppercase=May\u00fasculas
@@ -289,7 +312,10 @@ dialog.fullrangedetails.intro=Aqui estan los detalles para la selecci\u00f3n de
 dialog.estimatetime.details=Detalles
 dialog.estimatetime.climb=Ascenso
 dialog.estimatetime.descent=Descenso
+dialog.estimatetime.parameters=Par\u00e1metros
 dialog.estimatetime.results=Resultados
+dialog.estimatetime.results.estimatedtime=Duraci\u00f3n estimada
+dialog.estimatetime.results.actualtime=Duraci\u00f3n real
 dialog.setmapbg.intro=Seleccione un proveedor de mapas o a\u00f1ada uno nuevo
 dialog.addmapsource.title=A\u00f1adir un proveedor de mapas
 dialog.addmapsource.sourcename=Nombre del proveedor
@@ -325,7 +351,6 @@ dialog.correlate.photoselect.intro=Seleccione una de estas fotos correlacionadas
 dialog.correlate.select.photoname=Nombre de la foto
 dialog.correlate.select.timediff=Diferencia de tiempo
 dialog.correlate.select.photolater=Foto m\u00e1s adelante
-dialog.correlate.options.tip=Sugerencia: Correlacionando al menos una foto manualmente, el margen de tiempo se calcula autom\u00e1ticamente.
 dialog.correlate.options.intro=Seleccionar las opciones para correlaci\u00f3n autom\u00e1tica
 dialog.correlate.options.offsetpanel=Margen de tiempo
 dialog.correlate.options.offset=Margen
@@ -368,6 +393,7 @@ dialog.compress.duplicates.title=Eliminar duplicados
 dialog.compress.douglaspeucker.title=Compresion Douglas-Peucker
 dialog.compress.douglaspeucker.paramdesc=Factor de extensi\u00f3n
 dialog.compress.summarylabel=Puntos para eliminar
+dialog.compress.confirm=%d puntos marcados. \u00bfDesea eliminar los puntos?
 dialog.compress.confirmnone=Ning\u00fan punto marcado
 dialog.deletemarked.nonefound=Ning\u00fan punto eliminado
 dialog.pastecoordinates.desc=Ingresar o pegar las coordenadas aqu\u00ed
@@ -408,7 +434,7 @@ dialog.about.credits.thanks=Gracias a
 dialog.about.readme=Readme
 dialog.checkversion.error=El numero de versi\u00f3n no pudo ser verificada.\n Por favor verificar la conexi\u00f3n de Internet
 dialog.checkversion.uptodate=Esta usted utilizando la \u00faltima versi\u00f3n de GpsPrune
-dialog.checkversion.newversion1=�Una nueva versi\u00f3n de GpsPrune est\u00e1 disponible! La \u00faltima es ahora la versi\u00f3n
+dialog.checkversion.newversion1=\u00a1Una nueva versi\u00f3n de GpsPrune est\u00e1 disponible! La \u00faltima es ahora la versi\u00f3n
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=La nueva versi\u00f3n fue lanzada en
 dialog.checkversion.releasedate2=.
@@ -474,12 +500,25 @@ dialog.diskcache.tileset.multiple=varios
 dialog.diskcache.deleteold=Borrar recuadros antiguos
 dialog.diskcache.maximumage=Edad m\u00e1xima (dias)
 dialog.diskcache.deleteall=Borrar todos los recuadros
-dialog.diskcache.deleted1=Borrado
-dialog.diskcache.deleted2=Archivos del cache
+dialog.diskcache.deleted=Borrado %d archivos del cache
 dialog.deletefieldvalues.intro=Seleccionar el campo a eliminar para el rango actual
+dialog.deletefieldvalues.nofields=No hay campos a eliminar para el rango actual
 dialog.setlinewidth.text=Introduzca la anchura de las l\u00edneas a dibujar para los recorridos (1-4)
 dialog.downloadosm.desc=Confirmar la descarga de datos en bruto de OSM para el \u00e1rea especificada.
 dialog.searchwikipedianames.search=Buscar:
+dialog.weather.day.now=Tiempo actual
+dialog.weather.day.today=Hoy
+dialog.weather.day.tomorrow=Ma\u00f1ana
+dialog.weather.day.monday=Lunes
+dialog.weather.day.tuesday=Martes
+dialog.weather.day.wednesday=Mi\u00e9rcoles
+dialog.weather.day.thursday=Jueves
+dialog.weather.day.friday=Viernes
+dialog.weather.day.saturday=S\u00e1bado
+dialog.weather.day.sunday=Domingo
+dialog.weather.wind=Viento
+dialog.weather.temp=Temp
+dialog.weather.humidity=Humedad
 
 # 3d window
 dialog.3d.title=GpsPrune vista 3-D
@@ -501,8 +540,7 @@ confirm.rearrangephotos=Fotos reacomodadas
 confirm.cutandmove=Mover Selecci\u00f3n
 confirm.interpolate=Puntos insertados
 confirm.convertnamestotimes=Nombres de "waypoint" convertidos
-confirm.saveexif.ok1=Guardado
-confirm.saveexif.ok2=fotos
+confirm.saveexif.ok=Guardado %d fotos
 confirm.undo.single=operaci\u00f3n deshecha
 confirm.undo.multi=operaci\u00f3n(es) deshechas(s)
 confirm.jpegload.single=Foto incluida
@@ -516,13 +554,16 @@ confirm.correlatephotos.multi=fotos fueron correlacionadas
 confirm.createpoint=punto creado
 confirm.rotatephoto=foto rotada
 confirm.running=Trabajando ...
-confirm.lookupsrtm1=Encontrados
-confirm.lookupsrtm2=valor de altitud para la funci\u00f3n de b\u00fasqueda SRTM
+confirm.lookupsrtm=Encontrados %d valor de altitud para la funci\u00f3n de b\u00fasqueda SRTM
 confirm.deletefieldvalues=Valores del campo eliminados
 confirm.audioload=A\u00f1adidos archivos de audio
 confirm.correlateaudios.single=El audio fue correlacionado
 confirm.correlateaudios.multi=Los audios fueron correlacionados
 
+# Tips
+tip.title=Sugerencia
+tip.manuallycorrelateone=Correlacionando al menos una foto manualmente, el margen de tiempo se calcula autom\u00e1ticamente.
+
 # Buttons
 button.ok=Aceptar
 button.back=Anterior
@@ -572,6 +613,7 @@ filetype.audio=Archivos MP3, OGG, WAV
 display.nodata=Ning\u00fan dato cargado
 display.noaltitudes=Los datos del track no incluyen altitudes
 display.notimestamps=Los datos de recorrido no incluyen marcas de tiempo
+display.novalues=Los datos de recorrido no incluyen valores para este campo
 details.trackdetails=Detalles del track
 details.notrack=Ning\u00fan track cargado
 details.track.points=Puntos
@@ -627,7 +669,6 @@ fieldname.newsegment=Segmento
 fieldname.custom=Personalizado
 fieldname.prefix=Campo
 fieldname.distance=Distancia
-fieldname.movingdistance=Distancia en movimiento
 fieldname.duration=Duraci\u00f3n
 fieldname.speed=Velocidad
 fieldname.verticalspeed=Velocidad vertical
@@ -662,6 +703,10 @@ units.degminsec=Gra-min-seg
 units.degmin=Gra-min
 units.deg=Grados
 units.iso8601=ISO 8601
+units.degreescelsius=Celsius
+units.degreescelsius.short=\u00b0C
+units.degreesfahrenheit=Fahrenheit
+units.degreesfahrenheit.short=\u00b0F
 
 # How to combine conditions, such as filters
 logic.and=y
@@ -670,6 +715,7 @@ logic.or=o
 # External urls
 url.googlemaps=maps.google.es
 wikipedia.lang=es
+openweathermap.lang=sp
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -686,10 +732,11 @@ undo.deletepoint=eliminar punto
 undo.removephoto=eliminar foto
 undo.removeaudio=eliminar archivos de audio
 undo.deleterange=eliminar rango
+undo.croptrack=truncar track
 undo.deletemarked=eliminar puntos
 undo.insert=insertar puntos
 undo.reverse=invertir rango
-undo.mergetracksegments=unir los segmentos de track
+undo.mergetracksegments=unir los segmentos del track
 undo.addtimeoffset=a\u00f1adir margen de tiempo
 undo.addaltitudeoffset=a\u00f1adir margen de altitud
 undo.rearrangewaypoints=reordenar waypoints
@@ -712,10 +759,8 @@ error.save.failed=Fallo al guardar datos al archivo
 error.saveexif.filenotfound=Archivo no encontrado
 error.saveexif.cannotoverwrite1=No se puede guardar
 error.saveexif.cannotoverwrite2=es s\u00f3lo-lectura y no se puede sobreescribir. Guardar a una copia?
-error.saveexif.failed1=Fall\u00f3 al guardar
-error.saveexif.failed2=de las im\u00e1genes
-error.saveexif.forced1=
-error.saveexif.forced2=de las im\u00e1genes requiere forzar
+error.saveexif.failed=Fall\u00f3 al guardar %d de las im\u00e1genes
+error.saveexif.forced=%d de las im\u00e1genes requiere forzar
 error.load.dialogtitle=Fallo al cargar datos
 error.load.noread=No se puede leer el fichero
 error.load.nopoints=No se encuentra ninguna informaci\u00f3n de coordenadas en el archivo
@@ -726,7 +771,7 @@ error.jpegload.dialogtitle=Error cargando fotos
 error.jpegload.nofilesfound=No se encuentra ning\u00fan archivo
 error.jpegload.nojpegsfound=No se encuentra ning\u00fan archivo jpeg
 error.jpegload.nogpsfound=No se encuentra informaci\u00f3n GPS
-error.jpegload.exifreadfailed=Fallo al leer la informaci\u00f3n EXIF. No se puede leer ninguna informaci\u00f3n EXIF\ncon las librer\u00edas internas ni externas.
+error.jpegload.exifreadfailed=Fallo al leer la informaci\u00f3n Exif. No se puede leer ninguna informaci\u00f3n Exif\ncon las librer\u00edas internas ni externas.
 error.audioload.nofilesfound=No se encontraron archivos de audio
 error.gpsload.unknown=Error desconocido
 error.undofailed.title=Fallo al deshacer
diff --git a/tim/prune/lang/prune-texts_fa.properties b/tim/prune/lang/prune-texts_fa.properties
new file mode 100644
index 0000000..960330d
--- /dev/null
+++ b/tim/prune/lang/prune-texts_fa.properties
@@ -0,0 +1,73 @@
+# Text entries for the Prune application
+# Persian (Farsi) entries as extra
+
+# Menu entries
+menu.file=\u067e\u0648\u0634\u0647
+menu.file.addphotos=\u0627\u0636\u0627\u0641\u0647 \u06a9\u0631\u062f\u0646 \u0639\u06a9\u0633
+menu.file.save=\u0630\u062e\u064a\u0631\u0647
+menu.file.exit=\u062e\u0631\u0648\u062c
+menu.track=\u0645\u0633\u064a\u0631
+menu.track.undo=\u0628\u0627\u0637\u0644 \u06a9\u0631\u062f\u0646 \u06a9\u0627\u0631 \u0642\u0628\u0644\u064a
+menu.track.clearundo=\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0644\u064a\u0633\u062a \u06a9\u0627\u0631\u0647\u0627\u06cc \u0627\u0646\u062c\u0627\u0645 \u0634\u062f\u0647
+menu.track.deletemarked=\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0646\u0642\u0627\u0637 \u0627\u0646\u062a\u062e\u0627\u0628 \u0634\u062f\u0647
+menu.track.rearrange=\u0628\u0627\u0632 \u0686\u064a\u0646\u06cc \u0646\u0642\u0627\u0637 \u0645\u0633\u064a\u0631
+menu.track.rearrange.start=\u0647\u0645\u0647 \u0646\u0642\u0627\u0637 \u0628\u0647 \u0627\u0628\u062a\u062f\u0627\u06cc \u0645\u0633\u064a\u0631
+menu.track.rearrange.end=\u0647\u0645\u0647 \u0646\u0642\u0627\u0637 \u0628\u0647 \u0627\u0646\u062a\u0647\u0627\u06cc \u0645\u0633\u064a\u0631
+menu.track.rearrange.nearest=\u0647\u0631 \u064a\u06a9 \u0628\u0647 \u0646\u0632\u062f\u064a\u06a9 \u0646\u0642\u0637\u0647 \u0645\u0633\u064a\u0631
+menu.range=\u0686\u064a\u0646\u0634
+menu.range.all=\u0627\u0646\u062a\u062e\u0627\u0628 \u0647\u0645\u0647 \u0646\u0642\u0627\u0637
+menu.range.none=\u0627\u0646\u062a\u062e\u0627\u0628 \u0647\u064a\u0686 \u064a\u06a9 \u0627\u0632 \u0646\u0642\u0627\u0637
+menu.range.start=\u062a\u0646\u0638\u064a\u0645 \u0634\u0631\u0648\u0639 \u0686\u064a\u0646\u0634
+menu.range.end=\u062a\u0646\u0638\u064a\u0645 \u0627\u0646\u062a\u0647\u0627\u06cc \u0686\u064a\u0646\u0634
+function.deleterange=\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0686\u064a\u0646\u0634
+function.interpolate=\u062f\u0631\u0648\u0646\u064a\u0627\u0628\u06cc
+menu.range.average=\u0645\u06cc\u0627\u0646\u06af\u064a\u0646 \u0627\u0646\u062a\u062e\u0627\u0628 \u0634\u062f\u0647 \u0647\u0627
+menu.range.reverse=\u0686\u064a\u0646\u0634 \u0645\u0639\u06a9\u0648\u0633
+menu.range.mergetracksegments=\u0627\u062a\u0635\u0627\u0644 \u0642\u0633\u0645\u062a \u0647\u0627\u06cc \u0645\u0633\u064a\u0631
+menu.range.cutandmove=\u062c\u062f\u0627 \u06a9\u0631\u062f\u0646 \u0642\u0633\u0645\u062a \u0627\u0646\u062a\u062e\u0627\u0628 \u0634\u062f\u0647
+menu.point=\u0646\u0642\u0637\u0647
+menu.point.editpoint=\u062a\u0646\u0638\u064a\u0645\u0627\u062a \u0646\u0642\u0637\u0647
+menu.point.deletepoint=\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0646\u0642\u0637\u0647
+menu.photo=\u0639\u06a9\u0633
+menu.photo.saveexif=\u0630\u062e\u064a\u0631\u0647 \u062f\u0631 \u0641\u0627\u064a\u0644 \u0636\u0645\u064a\u0645\u0647 \u0639\u06a9\u0633
+function.connecttopoint=\u0627\u062a\u0635\u0627\u0644 \u0628\u0647 \u0646\u0642\u0637\u0647
+function.disconnectfrompoint=\u0642\u0637\u0639 \u0627\u062a\u0635\u0627\u0644 \u0627\u0632 \u0646\u0642\u0637\u0647
+function.removephoto=\u0628\u0631\u062f\u0627\u0634\u062a\u0646 \u0639\u06a9\u0633
+menu.view=\u062f\u064a\u062f
+menu.view.browser=\u0646\u0642\u0634\u0647 \u062f\u0631\u062c\u0633\u062a\u062c\u0648\u06af\u0631
+menu.view.browser.google=Google Maps
+menu.view.browser.openstreetmap=OpenStreetMap
+menu.view.browser.mapquest=MapQuest
+menu.view.browser.yahoo=Yahoo Maps
+menu.view.browser.bing=Bing Maps
+menu.settings=\u062a\u0646\u0638\u06cc\u0645\u0627\u062a
+menu.settings.onlinemode=\u062f\u0627\u0646\u0644\u0648\u062f \u0646\u0642\u0634\u0647 \u0627\u0632 \u0627\u064a\u0646\u062a\u0631\u0646\u062a
+menu.help=\u0631\u0627\u0647\u0646\u0645\u0627
+# Popup menu for map
+menu.map.zoomin=\u0628\u0632\u0631\u06af \u0646\u0645\u0627\u064a\u06cc
+menu.map.zoomout=\u06a9\u0648\u0686\u06a9 \u0646\u0645\u0627\u064a\u06cc
+menu.map.zoomfull=\u0646\u0645\u0627\u064a\u0634 \u062f\u0631 \u0627\u0646\u062f\u0627\u0632\u0647 \u06a9\u0627\u0645\u0644
+menu.map.newpoint=\u0627\u064a\u062c\u0627\u062f \u0646\u0642\u0637\u0647 \u062c\u062f\u064a\u062f
+menu.map.connect=\u0627\u062a\u0635\u0627\u0644 \u0646\u0642\u0627\u0637 \u0645\u0633\u064a\u0631
+menu.map.autopan=\u0646\u0645\u0627\u064a\u0634 \u062f\u0627\u0626\u0645 \u0646\u0642\u0637\u0647
+menu.map.showmap=\u0646\u0645\u0627\u064a\u0634 \u0646\u0642\u0637\u0647
+menu.map.showscalebar=\u0646\u0645\u0627\u064a\u0634 \u062a\u0646\u0638\u064a\u0645\u0627\u062a \u0645\u0642\u064a\u0627\u0633
+
+# Alt keys for menus
+altkey.menu.file=\u067e
+altkey.menu.track=\u0645
+altkey.menu.range=\u0686
+altkey.menu.point=\u0646
+altkey.menu.view=\u062f
+altkey.menu.photo=\u0639
+altkey.menu.settings=\u062a
+altkey.menu.help=\u0631
+
+# Ctrl shortcuts for menu items
+shortcut.menu.file.open=\u0628
+shortcut.menu.file.load=\u062f
+shortcut.menu.file.save=\u0630
+shortcut.menu.track.undo=\u0638
+shortcut.menu.edit.compress=\u062a
+shortcut.menu.range.all=\u0686
+shortcut.menu.help.help=\u0631
diff --git a/tim/prune/lang/prune-texts_fr.properties b/tim/prune/lang/prune-texts_fr.properties
index 1cfdf1e..477b832 100644
--- a/tim/prune/lang/prune-texts_fr.properties
+++ b/tim/prune/lang/prune-texts_fr.properties
@@ -7,6 +7,7 @@ menu.file.addphotos=Ajouter photos
 menu.file.recentfiles=Fichiers r\u00e9cents
 menu.file.save=Enregistrer
 menu.file.exit=Quitter
+menu.online=En ligne
 menu.track=Trace
 menu.track.undo=Annuler
 menu.track.clearundo=Purger la liste d'annulation
@@ -57,6 +58,7 @@ menu.map.editmode=Mode \u00e9dition
 
 # Alt keys for menus
 altkey.menu.file=F
+altkey.menu.online=L
 altkey.menu.track=T
 altkey.menu.range=E
 altkey.menu.point=P
@@ -103,9 +105,12 @@ function.fullrangedetails=Montrer tous les d\u00e9tails
 function.estimatetime=Temps estim\u00e9
 function.setmapbg=D\u00e9finir le fond de carte
 function.setpaths=D\u00e9finir les chemins des programmes
+function.splitsegments=S\u00e9pare les segments
+function.sewsegments=R\u00e9unis les segments
 function.getgpsies=R\u00e9cup\u00e9rer les traces Gpsies
 function.uploadgpsies=T\u00e9l\u00e9charger la trace sur Gpsies
 function.lookupsrtm=R\u00e9cup\u00e9rer les altitudes depuis SRTM
+function.downloadsrtm=T\u00e9l\u00e9charger les donn\u00e9es SRTM
 function.getwikipedia=Obtenir les articles de Wikip\u00e9dia \u00e0 proximit\u00e9
 function.searchwikipedianames=Rechercher dans Wikip\u00e9dia par nom
 function.downloadosm=T\u00e9l\u00e9charger les donn\u00e9es OSM de la zone
@@ -134,6 +139,7 @@ function.checkversion=Chercher une mise \u00e0 jour
 function.saveconfig=Enregistrer les pr\u00e9f\u00e9rences
 function.diskcache=Enregistrer les cartes sur le disque
 function.managetilecache=Gestion du cache des dalles de cartes
+function.getweatherforecast=Obtenir une pr\u00e9vision m\u00e9t\u00e9orologique
 
 # Dialogs
 dialog.exit.confirm.title=Quitter GpsPrune
@@ -162,6 +168,8 @@ dialog.openoptions.deliminfo.norecords=Pas d'enregistrements
 dialog.openoptions.altitudeunits=Unit\u00e9s d'altitude
 dialog.openoptions.speedunits=Unit\u00e9s de vitesse
 dialog.openoptions.vertspeedunits=Unit\u00e9s de vitesse verticale
+dialog.openoptions.vspeed.positiveup=Vitesse positive en montant
+dialog.openoptions.vspeed.positivedown=Vitesse positive en bas
 dialog.open.contentsdoubled=Ce fichier contient deux copies de chaque point,\nune fois comme waypoint, une autre comme point de trace.
 dialog.selecttracks.intro=S\u00e9lectionner la ou les traces \u00e0 charger
 dialog.selecttracks.noname=Sans titre
@@ -179,6 +187,19 @@ dialog.gpsload.save=Enregistrer dans un fichier
 dialog.gpssend.sendwaypoints=Envoyer les waypoints
 dialog.gpssend.sendtracks=Envoyer les traces
 dialog.gpssend.trackname=Nom de la trace
+dialog.gpsbabel.filters=Filtres
+dialog.addfilter.title=Ajouter un filtre
+dialog.gpsbabel.filter.discard=Jeter
+dialog.gpsbabel.filter.simplify=Simplifier
+dialog.gpsbabel.filter.interpolate=Interpoler
+dialog.gpsbabel.filter.discard.intro=Jeter les points si
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.distance=Distance
+dialog.gpsbabel.filter.distance.distance=Si la distance <
+dialog.gpsbabel.filter.distance.time=et difference de temps <
+dialog.gpsbabel.filter.interpolate.distance=Si la distance >
+dialog.gpsbabel.filter.interpolate.time=ou difference de temps >
 dialog.saveoptions.title=Enregistrer le fichier
 dialog.save.fieldstosave=Champs \u00e0 enregistrer
 dialog.save.table.field=Champ
@@ -197,6 +218,8 @@ 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.exportkml.standardkml=KML standard
+dialog.exportkml.extendedkml=KML enrichi avec l'heure
 dialog.exportgpx.name=Nom
 dialog.exportgpx.desc=L\u00e9gende
 dialog.exportgpx.includetimestamps=Inclure l'heure pour chaque point
@@ -243,8 +266,9 @@ dialog.undo.none.text=Pas d'op\u00e9ration \u00e0 annuler !
 dialog.clearundo.title=Purger la liste d'annulation
 dialog.clearundo.text=\u00cates-vous s\u00fbr de vouloir effacer la liste d'annulation ?\nToutes les informations d'annulation seront perdues !
 dialog.pointedit.title=\u00c9diter le point
-dialog.pointedit.intro=S\u00e9lectionner chaque champ pour changer la valeur
+dialog.pointedit.intro=S\u00e9lectionner chaque champ pour voire et changer la valeur
 dialog.pointedit.table.field=Champ
+dialog.pointedit.nofield=Aucun champ choisi
 dialog.pointedit.table.value=Valeur
 dialog.pointnameedit.name=Nom de waypoint
 dialog.pointnameedit.uppercase=CASSE MAJUSCULES
@@ -324,7 +348,6 @@ dialog.correlate.photoselect.intro=S\u00e9lectionner une de ces photos corr\u00e
 dialog.correlate.select.photoname=Nom de la photo
 dialog.correlate.select.timediff=Diff\u00e9rence de temps
 dialog.correlate.select.photolater=Photo prise plus tard
-dialog.correlate.options.tip=Astuce : En corr\u00e9lant manuellement au moins une photo, le d\u00e9calage de temps peut \u00eatre calcul\u00e9 pour vous.
 dialog.correlate.options.intro=S\u00e9lectionner les options pour la corr\u00e9lation automatique
 dialog.correlate.options.offsetpanel=D\u00e9calage de temps
 dialog.correlate.options.offset=D\u00e9calage
@@ -367,8 +390,7 @@ dialog.compress.duplicates.title=Suppression des doublons
 dialog.compress.douglaspeucker.title=Compression Douglas-Peucker
 dialog.compress.douglaspeucker.paramdesc=Taille du voisinage
 dialog.compress.summarylabel=Points \u00e0 supprimer
-dialog.compress.confirm1=
-dialog.compress.confirm2=point(s) marqu\u00e9(s).\nTrace->Supprimer les points marqu\u00e9s pour les supprimer
+dialog.compress.confirm=%d point(s) marqu\u00e9(s).\nSupprimer les points?
 dialog.compress.confirmnone=Pas de points marqu\u00e9s
 dialog.deletemarked.nonefound=Pas de donn\u00e9es \u00e0 effacer
 dialog.pastecoordinates.desc=Entrez ou collez les coordonn\u00e9es ici
@@ -415,7 +437,7 @@ dialog.checkversion.releasedate1=La nouvelle version est sortie le
 dialog.checkversion.releasedate2=.
 dialog.checkversion.download=Pour t\u00e9l\u00e9charger la nouvelle version, aller \u00e0 http://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=Vous pouvez utiliser ces raccourcis clavier \u00e0 la place de la souris
-dialog.keys.keylist=<table><tr><td>Touches-fl\u00e8ches</td><td>Faire d\u00e9filer la carte horizontalement et verticalement</td></tr><tr><td>Ctrl + gauche, Ctrl + droite</td><td>Choisir le point pr\u00e9c\u00e9dent ou suivant</td></tr><tr><td>Ctrl + haut, Ctrl + bas</td><td>Zoomer, s'\u00e9loigner</td></tr><tr><td>Suppr</td><td>Effacer le point courant</td></tr></table>
+dialog.keys.keylist=<table><tr><td>Touches-fl\u00e8ches</td><td>Faire d\u00e9filer la carte horizontalement et verticalement</td></tr><tr><td>Ctrl + gauche, Ctrl + droite</td><td>Choisir le point pr\u00e9c\u00e9dent ou suivant</td></tr><tr><td>Ctrl + haut, Ctrl + bas</td><td>Zoomer, s'\u00e9loigner</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>Choisir le segment pr\u00e9c\u00e9dent ou suivant</td></tr><tr><td>Ctrl + Home, End</td><td>Choisir le point premier, dernier</td></tr><tr><td>Supp [...]
 dialog.keys.normalmodifier=Ctrl
 dialog.keys.macmodifier=Command
 dialog.saveconfig.desc=Les param\u00e8tres suivants peuvent \u00eatre sauvegard\u00e9s dans un fichier de configuration:
@@ -476,13 +498,34 @@ dialog.diskcache.tileset.multiple=multiple
 dialog.diskcache.deleteold=Efface vieilles dalles
 dialog.diskcache.maximumage=\u00e2ge maxi (jours)
 dialog.diskcache.deleteall=Efface toutes les dalles
-dialog.diskcache.deleted1=Effac\u00e9
-dialog.diskcache.deleted2=dalles en cache
+dialog.diskcache.deleted=Effac\u00e9 %d dalles en 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 :
+dialog.weather.location=Location
+dialog.weather.update=Mise \u00e0 jour
+dialog.weather.sunrise=Lever du soleil
+dialog.weather.sunset=Coucher du soleil
+dialog.weather.temperatureunits=Temp\u00e9ratures
+dialog.weather.currentforecast=Temps actuel
+dialog.weather.dailyforecast=Pr\u00e9vision par jour
+dialog.weather.3hourlyforecast=Pr\u00e9vision par 3 heures
+dialog.weather.day.now=Temps actuel
+dialog.weather.day.today=Aujourd'hui
+dialog.weather.day.tomorrow=Demain
+dialog.weather.day.monday=Lundi
+dialog.weather.day.tuesday=Mardi
+dialog.weather.day.wednesday=Mercredi
+dialog.weather.day.thursday=Jeudi
+dialog.weather.day.friday=Vendredi
+dialog.weather.day.saturday=Samedi
+dialog.weather.day.sunday=Dimanche
+dialog.weather.wind=Vent
+dialog.weather.temp=Temp
+dialog.weather.humidity=Humidit\u00e9
+dialog.weather.creditnotice=Ces donn\u00e9es sont fournies par openweathermap.org. Consultez la page pour plus de d\u00e9tails.
 
 # 3d window
 dialog.3d.title=Vue 3D de GpsPrune
@@ -504,8 +547,7 @@ confirm.rearrangephotos=Photos r\u00e9arrang\u00e9es
 confirm.cutandmove=S\u00e9lection d\u00e9plac\u00e9e
 confirm.interpolate=Points ajout\u00e9s
 confirm.convertnamestotimes=Noms de waypoints convertis
-confirm.saveexif.ok1=Enregistr\u00e9
-confirm.saveexif.ok2=fichiers photo
+confirm.saveexif.ok=Enregistr\u00e9 %d fichiers photo
 confirm.undo.single=op\u00e9ration annul\u00e9e
 confirm.undo.multi=op\u00e9rations annul\u00e9es
 confirm.jpegload.single=la photo a \u00e9t\u00e9 ajout\u00e9e
@@ -519,13 +561,23 @@ confirm.correlatephotos.multi=photos ont \u00e9t\u00e9 corr\u00e9l\u00e9es
 confirm.createpoint=Point cr\u00e9\u00e9
 confirm.rotatephoto=Photo tourn\u00e9e
 confirm.running=En cours...
-confirm.lookupsrtm1=Trouv\u00e9
-confirm.lookupsrtm2=valeurs d'altitude
+confirm.lookupsrtm=Trouv\u00e9 %d valeurs d'altitude
+confirm.downloadsrtm=%d fichiers ont \u00e9t\u00e9 t\u00e9l\u00e9charg\u00e9s
+confirm.downloadsrtm.1=%d fichier a \u00e9t\u00e9 t\u00e9l\u00e9charg\u00e9
+confirm.downloadsrtm.none=Pas de fichiers ont \u00e9t\u00e9 t\u00e9l\u00e9charg\u00e9s, ils sont d\u00e9j\u00e0 l\u00e0
 confirm.deletefieldvalues=Valeurs effac\u00e9es
 confirm.audioload=Fichiers audio ajout\u00e9s
 confirm.correlateaudios.single=fichier audio a \u00e9t\u00e9 corr\u00e9l\u00e9
 confirm.correlateaudios.multi=fichiers audio ont \u00e9t\u00e9 corr\u00e9l\u00e9s
 
+# Tips
+tip.title=Astuce
+tip.useamapcache=By setting up a disk cache (Pr\u00e9f\u00e9rences -> Enregistrer les cartes sur le disque)\nyou can speed up the display and reduce network traffic.
+tip.learntimeparams=The results will be more accurate if you use\nTrace -> Learn time estimation parameters\non your recorded tracks.
+tip.downloadsrtm=You can speed this up by calling\nEn ligne -> T\u00e9l\u00e9charger les donn\u00e9es SRTM\nto save the data in your map cache.
+tip.usesrtmfor3d=This track doesn't have altitudes.\nYou can use the SRTM functions to get approximate\naltitudes for the 3d view.
+tip.manuallycorrelateone=En corr\u00e9lant manuellement au moins une photo, le d\u00e9calage de temps peut \u00eatre calcul\u00e9 pour vous.
+
 # Buttons
 button.ok=OK
 button.back=Retour
@@ -543,6 +595,7 @@ button.yes=Oui
 button.no=Non
 button.yestoall=Oui pour tous
 button.notoall=Non pour tous
+button.always=Oui, toujours
 button.select=S\u00e9lectionner
 button.selectall=Tout s\u00e9lectionner
 button.selectnone=Ne rien s\u00e9lectionner
@@ -557,6 +610,7 @@ button.browse=Naviguer...
 button.addnew=Ajouter nouveau...
 button.delete=Supprimer
 button.manage=G\u00e9rer
+button.combine=Combiner
 
 # File types
 filetype.txt=Fichiers TXT
@@ -574,6 +628,7 @@ filetype.audio=Fichiers MP3, OGG, WAV
 display.nodata=Pas de donn\u00e9es charg\u00e9es
 display.noaltitudes=La trace ne comporte pas d'information d'altitude
 display.notimestamps=La trace ne comporte pas d'information de temps
+display.novalues=La trace ne comporte pas d'information pour ce champ
 details.trackdetails=D\u00e9tails de la trace
 details.notrack=Pas de trace charg\u00e9e
 details.track.points=Points
@@ -629,7 +684,6 @@ fieldname.newsegment=Segment
 fieldname.custom=Personnalis\u00e9
 fieldname.prefix=Champ
 fieldname.distance=Distance
-fieldname.movingdistance=Distance continue
 fieldname.duration=Dur\u00e9e
 fieldname.speed=Vitesse
 fieldname.verticalspeed=Vitesse verticale
@@ -672,6 +726,7 @@ logic.or=ou
 # External urls
 url.googlemaps=maps.google.fr
 wikipedia.lang=fr
+openweathermap.lang=fr
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -693,6 +748,8 @@ undo.deletemarked=effacer les points
 undo.insert=ins\u00e9rer les points
 undo.reverse=inverser l'\u00e9tendue
 undo.mergetracksegments=fusionner les segments de trace
+undo.splitsegments=s\u00e9parer les segments de trace
+undo.sewsegments=r\u00e9unir les segments de trace
 undo.addtimeoffset=ajouter d\u00e9calage d'heure
 undo.addaltitudeoffset=ajouter d\u00e9calage d'altitude
 undo.rearrangewaypoints=r\u00e9arranger les waypoints
@@ -715,10 +772,8 @@ error.save.failed=\u00c9chec de l'enregistrement des donn\u00e9es dans le fichie
 error.saveexif.filenotfound=Fichier photo introuvable
 error.saveexif.cannotoverwrite1=Le fichier photo
 error.saveexif.cannotoverwrite2=est en lecture seule et ne peut pas \u00eatre \u00e9craser. Enregistrer sur une copie ?
-error.saveexif.failed1=\u00c9chec de la sauvegarde de
-error.saveexif.failed2=images
-error.saveexif.forced1=Enregistrement forc\u00e9 pour
-error.saveexif.forced2=images
+error.saveexif.failed=\u00c9chec de la sauvegarde de %d images
+error.saveexif.forced=Enregistrement forc\u00e9 pour %d images
 error.load.dialogtitle=Erreur au chargement des donn\u00e9es
 error.load.noread=Fichier illisible
 error.load.nopoints=Aucune coordonn\u00e9e trouv\u00e9e dans le fichier
@@ -729,7 +784,7 @@ error.jpegload.dialogtitle=Erreur au chargement des photos
 error.jpegload.nofilesfound=Aucun fichier trouv\u00e9
 error.jpegload.nojpegsfound=Aucun fichier jpeg trouv\u00e9
 error.jpegload.nogpsfound=Aucune information GPS trouv\u00e9e
-error.jpegload.exifreadfailed=Information EXIF illisible. Aucune information EXIF ne peut \u00eatre lue\nsans une librairie interne ou externe.
+error.jpegload.exifreadfailed=Information Exif illisible. Aucune information Exif ne peut \u00eatre lue\nsans une librairie interne ou externe.
 error.audioload.nofilesfound=Aucun fichier audio trouv\u00e9
 error.gpsload.unknown=Erreur inconnue
 error.undofailed.title=\u00c9chec de l'annulation
@@ -754,3 +809,4 @@ error.cache.notthere=Le dossier du cache n'a pas \u00e9t\u00e9 trouv\u00e9
 error.cache.empty=Le dossier du cache est vide
 error.cache.cannotdelete=Effacement des dalles impossible
 error.interpolate.invalidparameter=Le nombre de points doit \u00eatre compris entre 1 et 1000
+error.tracksplit.nosplit=Impossible de s\u00e9parer les segments
diff --git a/tim/prune/lang/prune-texts_hu.properties b/tim/prune/lang/prune-texts_hu.properties
index ea53142..58359f8 100644
--- a/tim/prune/lang/prune-texts_hu.properties
+++ b/tim/prune/lang/prune-texts_hu.properties
@@ -1,5 +1,5 @@
 # Text entries for the GpsPrune application
-# Hungarian entries thanks to Gy\u00f6rgy Ball\u00f3
+# Hungarian entries thanks to Gy\u00f6rgy Ball\u00f3 and Peter Bathory
 
 # Menu entries
 menu.file=F\u00e1jl
@@ -7,9 +7,11 @@ menu.file.addphotos=F\u00e9nyk\u00e9pek hozz\u00e1ad\u00e1sa
 menu.file.recentfiles=Legut\u00f3bbi f\u00e1jlok
 menu.file.save=Ment\u00e9s sz\u00f6vegk\u00e9nt
 menu.file.exit=Kil\u00e9p\u00e9s
+menu.online=Online
 menu.track=Nyomvonal
 menu.track.undo=Visszavon\u00e1s
 menu.track.clearundo=Visszavon\u00e1si lista t\u00f6rl\u00e9se
+menu.track.markrectangle=N\u00e9gyzeten bel\u00fcli pontok megjel\u00f6l\u00e9se
 menu.track.deletemarked=Jel\u00f6lt pontok t\u00f6rl\u00e9se
 menu.track.rearrange=\u00datpontok \u00fajrarendez\u00e9se
 menu.track.rearrange.start=\u00d6sszes a f\u00e1jl elej\u00e9re
@@ -20,8 +22,6 @@ menu.range.all=Mindet kijel\u00f6l
 menu.range.none=Kijel\u00f6l\u00e9s megsz\u00fcntet\u00e9se
 menu.range.start=Tartom\u00e1ny kezdet\u00e9nek be\u00e1ll\u00edt\u00e1sa
 menu.range.end=Tartom\u00e1ny v\u00e9g\u00e9nek be\u00e1ll\u00edt\u00e1sa
-function.deleterange=Tartom\u00e1ny t\u00f6rl\u00e9se
-function.interpolate=Pontok interpol\u00e1l\u00e1sa
 menu.range.average=Kijel\u00f6l\u00e9s \u00e1tlaga
 menu.range.reverse=Tartom\u00e1ny megford\u00edt\u00e1sa
 menu.range.mergetracksegments=Nyomvonalszakaszok egyes\u00edt\u00e9se
@@ -41,7 +41,7 @@ menu.view.browser.mapquest=Mapquest
 menu.view.browser.yahoo=Yahoo! Maps
 menu.view.browser.bing=Bing Maps
 menu.settings=Be\u00e1ll\u00edt\u00e1sok
-menu.settings.onlinemode=T\u00e9rk\u00e9pek bet\u00f6lt\u00e9se az internetr\u0151l
+menu.settings.onlinemode=T\u00e9rk\u00e9pek bet\u00f6lt\u00e9se internetr\u0151l
 menu.settings.autosave=Be\u00e1ll\u00edt\u00e1sok automatikus ment\u00e9se kil\u00e9p\u00e9skor
 menu.help=S\u00fag\u00f3
 # Popup menu for map
@@ -54,9 +54,11 @@ menu.map.connect=Nyompontok \u00f6sszek\u00f6t\u00e9se
 menu.map.autopan=Automatikus mozgat\u00e1s
 menu.map.showmap=T\u00e9rk\u00e9p megjelen\u00edt\u00e9se
 menu.map.showscalebar=M\u00e9retar\u00e1ny megjelen\u00edt\u00e9se
+menu.map.editmode=Szerkeszt\u00e9s m\u00f3d
 
 # Alt keys for menus
 altkey.menu.file=F
+altkey.menu.online=O
 altkey.menu.track=V
 altkey.menu.range=T
 altkey.menu.point=P
@@ -84,8 +86,12 @@ function.exportkml=Export\u00e1l\u00e1s KML-be
 function.exportgpx=Export\u00e1l\u00e1s GPX-be
 function.exportpov=Export\u00e1l\u00e1s POV-ba
 function.exportsvg=Export\u00e1l\u00e1s SVG-be
+function.exportimage=Export\u00e1l\u00e1s k\u00e9pbe
 function.editwaypointname=\u00datpont nev\u00e9nek szerkeszt\u00e9se
 function.compress=Nyomvonal t\u00f6m\u00f6r\u00edt\u00e9se
+function.deleterange=Tartom\u00e1ny t\u00f6rl\u00e9se
+function.croptrack=Nyomvonal k\u00f6rbev\u00e1g\u00e1sa
+function.interpolate=Pontok interpol\u00e1l\u00e1sa
 function.addtimeoffset=Id\u0151eltol\u00e1s hozz\u00e1ad\u00e1sa
 function.addaltitudeoffset=Magass\u00e1geltol\u00e1s hozz\u00e1ad\u00e1sa
 function.convertnamestotimes=\u00datpontok neveinek konvert\u00e1l\u00e1sa id\u0151pontokk\u00e1
@@ -96,11 +102,16 @@ function.charts=Diagramok
 function.show3d=3D n\u00e9zet
 function.distances=T\u00e1vols\u00e1gok
 function.fullrangedetails=Teljes tartom\u00e1ny r\u00e9szletei
+function.estimatetime=Becs\u00fclt id\u0151
+function.learnestimationparams=Id\u0151becsl\u00e9s tanul\u00e1s\u00e1nak param\u00e9terei
 function.setmapbg=H\u00e1tt\u00e9rk\u00e9p be\u00e1ll\u00edt\u00e1sa
 function.setpaths=Program\u00fatvonalak be\u00e1ll\u00edt\u00e1sa
+function.splitsegments=Nyomvonal kett\u00e9v\u00e1g\u00e1sa szakaszokk\u00e1
+function.sewsegments=Nyomvonalszakaszok \u00f6sszevon\u00e1sa
 function.getgpsies=Gpsies nyomvonalak let\u00f6lt\u00e9se
 function.uploadgpsies=Nyomvonal felt\u00f6lt\u00e9se Gpsiesra
 function.lookupsrtm=Magass\u00e1gok let\u00f6lt\u00e9se SRTM-r\u0151l
+function.downloadsrtm=SRTM csemp\u00e9k let\u00f6lt\u00e9se
 function.getwikipedia=K\u00f6zeli Wikip\u00e9dia sz\u00f3cikkek let\u00f6lt\u00e9se
 function.searchwikipedianames=Keres\u00e9s a Wikip\u00e9di\u00e1ban n\u00e9v szerint
 function.downloadosm=OSM adatok let\u00f6lt\u00e9se a ter\u00fcletr\u0151l
@@ -129,6 +140,7 @@ function.checkversion=\u00daj verzi\u00f3 keres\u00e9se
 function.saveconfig=Be\u00e1ll\u00edt\u00e1sok ment\u00e9se
 function.diskcache=T\u00e9rk\u00e9pek ment\u00e9se lemezre
 function.managetilecache=Csempegyors\u00edt\u00f3t\u00e1r kezel\u00e9se
+function.getweatherforecast=Id\u0151j\u00e1r\u00e1s el\u0151rejelz\u00e9s
 
 # Dialogs
 dialog.exit.confirm.title=Kil\u00e9p\u00e9s a GpsPrune-b\u00f3l
@@ -136,9 +148,10 @@ dialog.exit.confirm.text=Az adatok nincsenek elmentve. Biztos benne, hogy kil\u0
 dialog.openappend.title=Hozz\u00e1f\u0171z\u00e9s a megl\u00e9v\u0151 adatokhoz
 dialog.openappend.text=Hozz\u00e1f\u0171zi ezeket az adatokat a m\u00e1r bet\u00f6lt\u00f6tt adatokhoz?
 dialog.deletepoint.title=Pont t\u00f6rl\u00e9se
-dialog.deletepoint.deletephoto=T\u00f6rli a f\u00e9nyk\u00e9pet, amely ehhez a ponthoz tartozik?
+dialog.deletepoint.deletephoto=T\u00f6rli a ponthoz tartoz\u00f3 f\u00e9nyk\u00e9pet?
 dialog.deletephoto.title=F\u00e9nyk\u00e9p t\u00f6rl\u00e9se
-dialog.deletephoto.deletepoint=T\u00f6rli a pontot, amely ehhez a f\u00e9nyk\u00e9phez tartozik?
+dialog.deletephoto.deletepoint=T\u00f6rli a f\u00e9nyk\u00e9phez tartoz\u00f3 pontot?
+dialog.deleteaudio.deletepoint=T\u00f6rli a hangf\u00e1jlhoz tartoz\u00f3 pontot?
 dialog.openoptions.title=Be\u00e1ll\u00edt\u00e1sok megnyit\u00e1sa
 dialog.openoptions.filesnippet=F\u00e1jl kivonata
 dialog.load.table.field=Mez\u0151
@@ -153,7 +166,11 @@ dialog.delimiter.other=Egy\u00e9b
 dialog.openoptions.deliminfo.records=rekord
 dialog.openoptions.deliminfo.fields=mez\u0151vel
 dialog.openoptions.deliminfo.norecords=Nincsenek rekordok
-dialog.openoptions.altitudeunits=Magass\u00e1g egys\u00e9ge
+dialog.openoptions.altitudeunits=Magass\u00e1g m\u00e9rt\u00e9kegys\u00e9ge
+dialog.openoptions.speedunits=Sebess\u00e9g m\u00e9rt\u00e9kegys\u00e9ge
+dialog.openoptions.vertspeedunits=F\u00fcgg\u0151leges sebess\u00e9g m\u00e9rt\u00e9kegys\u00e9ge
+dialog.openoptions.vspeed.positiveup=Pozit\u00edv sebess\u00e9g m\u00e9rt\u00e9kegys\u00e9ge
+dialog.openoptions.vspeed.positivedown=Pozit\u00edv sebess\u00e9g lefel\u00e9
 dialog.open.contentsdoubled=Ez a f\u00e1jl minden egyes pont k\u00e9t p\u00e9ld\u00e1ny\u00e1t tartalmazza,\negyszer mint \u00fatpont, m\u00e1sodszor mint nyompont.
 dialog.selecttracks.intro=Nyomvonal vagy nyomvonalak kiv\u00e1laszt\u00e1sa bet\u00f6lt\u00e9shez
 dialog.selecttracks.noname=N\u00e9vtelen
@@ -171,14 +188,38 @@ dialog.gpsload.save=Ment\u00e9s f\u00e1jlba
 dialog.gpssend.sendwaypoints=\u00datpontok k\u00fcld\u00e9se
 dialog.gpssend.sendtracks=Nyomvonalak k\u00fcld\u00e9se
 dialog.gpssend.trackname=Nyomvonal neve
+dialog.gpsbabel.filters=Sz\u0171r\u0151k
+dialog.addfilter.title=Sz\u0171r\u0151 hozz\u00e1ad\u00e1sa
+dialog.gpsbabel.filter.discard=Mell\u0151z\u00e9s
+dialog.gpsbabel.filter.simplify=Egyszer\u0171s\u00edt\u00e9s
+dialog.gpsbabel.filter.distance=T\u00e1vols\u00e1g
+dialog.gpsbabel.filter.interpolate=Interpol\u00e1l\u00e1s
+dialog.gpsbabel.filter.discard.intro=Pontok kihagy\u00e1sa, ha
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Hdop >
+dialog.gpsbabel.filter.discard.numsats=M\u0171holdak sz\u00e1ma <
+dialog.gpsbabel.filter.discard.nofix=Fix n\u00e9lk\u00fcli pontok
+dialog.gpsbabel.filter.discard.unknownfix=Ismeretlen fix-es pontok
+dialog.gpsbabel.filter.simplify.intro=Pontok elt\u00e1vol\u00edt\u00e1sa am\u00edg
+dialog.gpsbabel.filter.simplify.maxpoints=Pontok sz\u00e1ma <
+dialog.gpsbabel.filter.simplify.maxerror=vagy hiba m\u00e9rete <
+dialog.gpsbabel.filter.simplify.crosstrack=lesodr\u00f3d\u00e1s
+dialog.gpsbabel.filter.simplify.length=t\u00e1vols\u00e1gk\u00fcl\u00f6nbs\u00e9g
+dialog.gpsbabel.filter.simplify.relative=relat\u00edv hdop
+dialog.gpsbabel.filter.distance.intro=Pont elt\u00e1vol\u00edt\u00e1sa, ha k\u00f6zel van egy kor\u00e1bbi ponthoz
+dialog.gpsbabel.filter.distance.distance=Ha a t\u00e1vols\u00e1g <
+dialog.gpsbabel.filter.distance.time=\u00e9s az id\u0151elt\u00e9r\u00e9s <
+dialog.gpsbabel.filter.interpolate.intro=Extra pontok beilleszt\u00e9se az \u00fatpontok k\u00f6z\u00e9
+dialog.gpsbabel.filter.interpolate.distance=Ha a t\u00e1vols\u00e1g >
+dialog.gpsbabel.filter.interpolate.time=vagy az id\u0151elt\u00e9r\u00e9s >
 dialog.saveoptions.title=F\u00e1jl ment\u00e9se
 dialog.save.fieldstosave=Mentend\u0151 mez\u0151k
 dialog.save.table.field=Mez\u0151
 dialog.save.table.hasdata=Tartalmaz adatot
 dialog.save.table.save=Ment\u00e9s
 dialog.save.headerrow=Fejl\u00e9csor a kimenetbe
-dialog.save.coordinateunits=Koordin\u00e1ta egys\u00e9ge
-dialog.save.altitudeunits=Magass\u00e1g egys\u00e9ge
+dialog.save.coordinateunits=Koordin\u00e1ta form\u00e1tuma
+dialog.save.altitudeunits=Magass\u00e1g m\u00e9rt\u00e9kegys\u00e9ge
 dialog.save.timestampformat=Id\u0151b\u00e9lyeg form\u00e1tuma
 dialog.save.overwrite.title=A f\u00e1jl m\u00e1r l\u00e9tezik
 dialog.save.overwrite.text=Ez a f\u00e1jl m\u00e1r l\u00e9tezik. Biztos benne, hogy fel\u00fcl\u00edrja a f\u00e1jlt?
@@ -187,7 +228,10 @@ dialog.exportkml.text=C\u00edm az adatokhoz
 dialog.exportkml.altitude=Abszol\u00fat magass\u00e1gok (rep\u00fcl\u00e9shez)
 dialog.exportkml.kmz=T\u00f6m\u00f6r\u00edt\u00e9s kmz f\u00e1jl k\u00e9sz\u00edt\u00e9s\u00e9hez
 dialog.exportkml.exportimages=K\u00e9pminiat\u0171r\u00f6k export\u00e1l\u00e1sa kmz-be
+dialog.exportkml.imagesize=K\u00e9pm\u00e9ret
 dialog.exportkml.trackcolour=Nyomvonal sz\u00edne
+dialog.exportkml.standardkml=Szabv\u00e1nyos KML
+dialog.exportkml.extendedkml=KML kib\u0151v\u00edt\u00e9se id\u0151b\u00e9lyegekkel
 dialog.exportgpx.name=N\u00e9v
 dialog.exportgpx.desc=Le\u00edr\u00e1s
 dialog.exportgpx.includetimestamps=Id\u0151b\u00e9lyegek is
@@ -204,10 +248,25 @@ dialog.exportpov.modelstyle=Modell st\u00edlusa
 dialog.exportpov.ballsandsticks=Goly\u00f3k \u00e9s botok
 dialog.exportpov.tubesandwalls=Cs\u00f6vek \u00e9s falak
 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.3d.useterrain=Terep megjelen\u00edt\u00e9se
+dialog.3d.terraingridsize=R\u00e1csm\u00e9ret
+dialog.exportpov.baseimage=Alapk\u00e9p
+dialog.exportpov.cannotmakebaseimage=Az alapk\u00e9p nem \u00edrhat\u00f3
+dialog.baseimage.title=T\u00e9rk\u00e9p k\u00e9p
+dialog.baseimage.useimage=K\u00e9p haszn\u00e1lata
+dialog.baseimage.mapsource=T\u00e9rk\u00e9pforr\u00e1s
+dialog.baseimage.zoom=Zoom szint
+dialog.baseimage.incomplete=Hi\u00e1nyos k\u00e9p
+dialog.baseimage.tiles=Csemp\u00e9k
+dialog.baseimage.size=K\u00e9pm\u00e9ret
 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
 dialog.exportsvg.gradients=\u00c1tmenetek haszn\u00e1lata az \u00e1rny\u00e9kol\u00e1shoz
+dialog.exportimage.noimagepossible=A t\u00e9rk\u00e9p k\u00e9peit az export\u00e1l\u00e1shoz el\u0151bb lemezre kell menteni.
+dialog.exportimage.drawtrack=Nyomvonal rajzol\u00e1sa a t\u00e9rk\u00e9pen
+dialog.exportimage.drawtrackpoints=A nyomvonal pontjainak kirajzol\u00e1sa
+dialog.exportimage.textscalepercent=Sz\u00f6vegnagy\u00edt\u00e1si faktor (%)
 dialog.pointtype.desc=A k\u00f6vetkez\u0151 pontt\u00edpusok ment\u00e9se:
 dialog.pointtype.track=Nyompontok
 dialog.pointtype.waypoint=\u00datpontok
@@ -219,6 +278,7 @@ dialog.confirmreversetrack.text=Ez a nyomvonal id\u0151b\u00e9lyeg-inform\u00e1c
 dialog.confirmcutandmove.title=Kiv\u00e1g\u00e1s \u00e9s mozgat\u00e1s meger\u0151s\u00edt\u00e9se
 dialog.confirmcutandmove.text=Ez a nyomvonal id\u0151b\u00e9lyeg-inform\u00e1ci\u00f3t tartalmaz, amely sorrendje mozgat\u00e1s ut\u00e1n megv\u00e1ltozik.\n Biztos benne, hogy mozgatja a kijel\u00f6l\u00e9st?
 dialog.interpolate.parameter.text=Pontok sz\u00e1ma, amely a k\u00e9t kiv\u00e1lasztott pont k\u00f6z\u00e9 besz\u00farand\u00f3
+dialog.interpolate.betweenwaypoints=Interpol\u00e1l az \u00fatpontok k\u00f6z\u00f6tt?
 dialog.undo.title=M\u0171velet(ek) visszavon\u00e1sa
 dialog.undo.pretext=V\u00e1lassza ki a visszavonand\u00f3 m\u0171velet(ek)et
 dialog.undo.none.title=Nem vonhat\u00f3 vissza
@@ -226,8 +286,9 @@ dialog.undo.none.text=Nincs visszavonhat\u00f3 m\u0171velet!
 dialog.clearundo.title=Visszavon\u00e1si lista t\u00f6rl\u00e9se
 dialog.clearundo.text=Biztos benne, hogy t\u00f6r\u00f6lni szeretn\u00e9 a visszavon\u00e1si list\u00e1t?\nMinden visszavon\u00e1si inform\u00e1ci\u00f3 el fog veszni!
 dialog.pointedit.title=Pont szerkeszt\u00e9se
-dialog.pointedit.text=V\u00e1lassza ki egyenk\u00e9nt a mez\u0151ket, amelyeket szerkeszteni szeretne, majd az \u00e9rt\u00e9k m\u00f3dos\u00edt\u00e1s\u00e1hoz haszn\u00e1lja a "Szerkeszt\u00e9s" gombot
+dialog.pointedit.intro=V\u00e1lassz ki egy mez\u0151t, hogy megn\u00e9zd \u00e9s szerkeszd az \u00e9rt\u00e9k\u00e9t
 dialog.pointedit.table.field=Mez\u0151
+dialog.pointedit.nofield=Nincs kiv\u00e1lasztott mez\u0151
 dialog.pointedit.table.value=\u00c9rt\u00e9k
 dialog.pointnameedit.name=\u00datpont neve
 dialog.pointnameedit.uppercase=NAGYBET\u0170S
@@ -268,6 +329,29 @@ dialog.distances.column.to=V\u00e9gpont
 dialog.distances.currentpoint=Jelenlegi pont
 dialog.distances.toofewpoints=Ehhez a funkci\u00f3hoz \u00fatpontok kellenek, amelyek k\u00f6z\u00f6tt a t\u00e1vols\u00e1g sz\u00e1m\u00edt\u00e1sra ker\u00fcl
 dialog.fullrangedetails.intro=Itt vannak a r\u00e9szletei a kiv\u00e1lasztott tartom\u00e1nynak
+dialog.fullrangedetails.coltotal=R\u00e9sekkel egy\u00fctt
+dialog.fullrangedetails.colsegments=R\u00e9sek n\u00e9lk\u00fcl
+dialog.estimatetime.details=R\u00e9szletek
+dialog.estimatetime.gentle=Lank\u00e1s
+dialog.estimatetime.steep=Meredek
+dialog.estimatetime.climb=M\u00e1sz\u00e1s
+dialog.estimatetime.descent=Ereszked\u00e9s
+dialog.estimatetime.parameters=Param\u00e9terek
+dialog.estimatetime.parameters.timefor=Sz\u00fcks\u00e9ges id\u0151:
+dialog.estimatetime.results=Eredm\u00e9nyek
+dialog.estimatetime.results.estimatedtime=Becs\u00fclt id\u0151
+dialog.estimatetime.results.actualtime=Aktu\u00e1lis id\u0151
+dialog.estimatetime.error.nodistance=Az id\u0151becsl\u00e9shez \u00f6sszek\u00f6t\u00f6tt nyomvonalpontokra van sz\u00fcks\u00e9g a t\u00e1vols\u00e1g meghat\u00e1roz\u00e1s\u00e1hoz
+dialog.estimatetime.error.noaltitudes=A kijel\u00f6l\u00e9s nem tartalmaz magass\u00e1g inform\u00e1ci\u00f3t
+dialog.learnestimationparams.intro=A nyomvonalb\u00f3l sz\u00e1m\u00edtott param\u00e9terek
+dialog.learnestimationparams.averageerror=\u00c1tlagos hiba
+dialog.learnestimationparams.combine=A param\u00e9terek kombin\u00e1lhat\u00f3k a jelenlegi \u00e9rt\u00e9kekkel
+dialog.learnestimationparams.combinedresults=Kombin\u00e1lt eredm\u00e9nyek
+dialog.learnestimationparams.weight.100pccurrent=Jelenlegi \u00e9rt\u00e9kek megtart\u00e1sa
+dialog.learnestimationparams.weight.current=jelenlegi
+dialog.learnestimationparams.weight.calculated=sz\u00e1m\u00edtott
+dialog.learnestimationparams.weight.50pc=A jelenlegi \u00e9s a sz\u00e1m\u00edtott \u00e9rt\u00e9kek \u00e1tlaga
+dialog.learnestimationparams.weight.100pccalculated=Az \u00faj sz\u00e1m\u00edtott \u00e9rt\u00e9kek haszn\u00e1lata
 dialog.setmapbg.intro=V\u00e1lassza ki az egyik t\u00e9rk\u00e9pforr\u00e1st, vagy adjon hozz\u00e1 egy \u00fajat
 dialog.addmapsource.title=\u00daj t\u00e9rk\u00e9pforr\u00e1s hozz\u00e1ad\u00e1sa
 dialog.addmapsource.sourcename=Forr\u00e1s neve
@@ -298,11 +382,11 @@ dialog.wikipedia.column.name=Sz\u00f3cikk neve
 dialog.wikipedia.column.distance=T\u00e1vols\u00e1g
 dialog.correlate.notimestamps=Nincsenek id\u0151b\u00e9lyegek az adatpontokon, \u00edgy nem feleltethet\u0151 meg semmi a f\u00e9nyk\u00e9pekkel.
 dialog.correlate.nouncorrelatedphotos=Nincsenek megfeleltetlen f\u00e9nyk\u00e9pek.\nBiztos benne, hogy folytatja?
+dialog.correlate.nouncorrelatedaudios=Nincsenek megfeleltetlen hangok.\nBiztos benne, hogy folytatja?
 dialog.correlate.photoselect.intro=V\u00e1lasszon egyet ezek k\u00f6z\u00fcl a megfeleltetett f\u00e9nyk\u00e9pek k\u00f6z\u00fcl az id\u0151eltol\u00e1s haszn\u00e1lat\u00e1hoz
 dialog.correlate.select.photoname=F\u00e9nyk\u00e9p neve
 dialog.correlate.select.timediff=Id\u0151k\u00fcl\u00f6nbs\u00e9g
 dialog.correlate.select.photolater=K\u00e9s\u0151bbi f\u00e9nyk\u00e9p
-dialog.correlate.options.tip=Tipp: legal\u00e1bb egy elem k\u00e9zzel t\u00f6rt\u00e9n\u0151 \u00f6sszekapcsol\u00e1s\u00e1val az id\u0151eltol\u00e1s kisz\u00e1m\u00edthat\u00f3.
 dialog.correlate.options.intro=V\u00e1lassza ki az opci\u00f3kat az automatikus megfeleltet\u00e9shez
 dialog.correlate.options.offsetpanel=Id\u0151eltol\u00e1s
 dialog.correlate.options.offset=Eltol\u00e1s
@@ -335,7 +419,6 @@ dialog.rearrangephotos.toend=Mozgat\u00e1s a v\u00e9g\u00e9hez
 dialog.rearrangephotos.nosort=Ne rendezze
 dialog.rearrangephotos.sortbyfilename=Rendez\u00e9s f\u00e1jln\u00e9v szerint
 dialog.rearrangephotos.sortbytime=Rendez\u00e9s id\u0151 szerint
-dialog.deletemarked.nonefound=Nem t\u00e1vol\u00edthat\u00f3 el adatpont
 dialog.compress.closepoints.title=K\u00f6zeli pontok elt\u00e1vol\u00edt\u00e1sa
 dialog.compress.closepoints.paramdesc=Hat\u00f3t\u00e1vols\u00e1g
 dialog.compress.wackypoints.title=Kisz\u00e1m\u00edthatatlan pontok elt\u00e1vol\u00edt\u00e1sa
@@ -346,17 +429,20 @@ dialog.compress.duplicates.title=Kett\u0151z\u00f6tt pontok elt\u00e1vol\u00edt\
 dialog.compress.douglaspeucker.title=Douglas-Peucker t\u00f6m\u00f6r\u00edt\u00e9s
 dialog.compress.douglaspeucker.paramdesc=T\u00f6m\u00f6r\u00edt\u00e9si t\u00e9nyez\u0151
 dialog.compress.summarylabel=T\u00f6rlend\u0151 pontok
+dialog.compress.confirm=%d a pontok meg lettek jel\u00f6lve.\nJel\u00f6lt pontok t\u00f6rl\u00e9se?
+dialog.compress.confirmnone=egy pont sem lett megjel\u00f6lve
+dialog.deletemarked.nonefound=Nem t\u00e1vol\u00edthat\u00f3 el adatpont
 dialog.pastecoordinates.desc=Adja meg vagy illessze be a koordin\u00e1t\u00e1kat ide
 dialog.pastecoordinates.coords=Koordin\u00e1t\u00e1k
-dialog.pastecoordinates.nothingfound=Ellen\u0151rizze a koordin\u00e1t\u00e1kat, \u00e9s pr\u00f3b\u00e1lja \u00f3jra
-dialog.help.help=Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt \u00e9s kezel\u00e9si \u00fatmutat\u00f3\u00e9rt l\u00e1sd a \n http://activityworkshop.net/software/gpsprune/\nwebhelyet.
+dialog.pastecoordinates.nothingfound=Ellen\u0151rizze a koordin\u00e1t\u00e1kat, \u00e9s pr\u00f3b\u00e1lja \u00fajra
+dialog.help.help=Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt \u00e9s kezel\u00e9si \u00fatmutat\u00f3\u00e9rt l\u00e1sd a \n http://gpsprune.activityworkshop.net/\nwebhelyet.
 dialog.about.version=Verzi\u00f3
 dialog.about.build=Build
 dialog.about.summarytext1=A GpsPrune egy program GPS vev\u0151kr\u0151l sz\u00e1rmaz\u00f3 adatok bet\u00f6lt\u00e9s\u00e9re, megjelen\u00edt\u00e9s\u00e9re \u00e9s szerkeszt\u00e9s\u00e9re.
 dialog.about.summarytext2=Gnu GPL licenc alatt ker\u00fclt kiad\u00e1sra a szabad, ny\u00edlt, vil\u00e1gm\u00e9ret\u0171 haszn\u00e1lathoz \u00e9s fejleszt\u00e9shez.<br>M\u00e1sol\u00e1sa, terjeszt\u00e9se \u00e9s m\u00f3dos\u00edt\u00e1sa megengedett \u00e9s \u00f6szt\u00f6nz\u00f6tt<br>a mell\u00e9kelt <code>license.txt</code> f\u00e1jlban r\u00f6gz\u00edtett felt\u00e9telek szerint
 dialog.about.summarytext3=Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt \u00e9s kezel\u00e9si \u00fatmutat\u00f3\u00e9rt l\u00e1sd a <code style="font-weight:bold">http://activityworkshop.net/</code> webhelyet.
 dialog.about.languages=El\u00e9rhet\u0151 nyelvek
-dialog.about.translatedby=Magyar sz\u00f6veg: Ball\u00f3 Gy\u00f6rgy
+dialog.about.translatedby=Magyar sz\u00f6veg: Ball\u00f3 Gy\u00f6rgy \u00e9s B\u00e1thory P\u00e9ter
 dialog.about.systeminfo=Rendszerinform\u00e1ci\u00f3
 dialog.about.systeminfo.os=Oper\u00e1ci\u00f3s rendszer
 dialog.about.systeminfo.java=Java futtat\u00f3k\u00f6rnyezet
@@ -373,7 +459,7 @@ dialog.about.systeminfo.exiflib.external.failed=K\u00fcls\u0151 (nem tal\u00e1lh
 dialog.about.yes=Igen
 dialog.about.no=Nem
 dialog.about.credits=K\u00e9sz\u00edt\u0151k
-dialog.about.credits.code=GpsPrune k\u00f3dj\u00e1t \u00edrta:
+dialog.about.credits.code=A GpsPrune k\u00f3dj\u00e1t \u00edrta:
 dialog.about.credits.exifcode=Exif k\u00f3d:
 dialog.about.credits.icons=N\u00e9h\u00e1ny ikon sz\u00e1rmazik:
 dialog.about.credits.translators=Ford\u00edt\u00f3k
@@ -386,9 +472,9 @@ dialog.checkversion.error=A verzi\u00f3sz\u00e1m nem ellen\u0151rizhet\u0151.\nE
 dialog.checkversion.uptodate=A GpsPrune leg\u00fajabb verzi\u00f3j\u00e1t haszn\u00e1lja.
 dialog.checkversion.newversion1=El\u00e9rhet\u0151 a GpsPrune \u00faj verzi\u00f3ja! A leg\u00fajabb verzi\u00f3 most:
 dialog.checkversion.newversion2=.
-dialog.checkversion.releasedate1=Ez az \u00faj verzi\u00f3 kiad\u00e1sra ker\u00fclt:
+dialog.checkversion.releasedate1=Az \u00faj verzi\u00f3 ekkor lett kiadva:
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Az \u00faj verzi\u00f3 let\u00f6lt\u00e9s\u00e9hez keresse fel a http://activityworkshop.net/software/gpsprune/download.html webhelyet.
+dialog.checkversion.download=Az \u00faj verzi\u00f3 let\u00f6lt\u00e9s\u00e9hez keresse fel a http://gpsprune.activityworkshop.net/download.html webhelyet.
 dialog.keys.intro=A k\u00f6vetkez\u0151 gyorsbillenty\u0171k haszn\u00e1lhat\u00f3k az eg\u00e9r haszn\u00e1lata helyett
 dialog.keys.keylist=<table><tr><td>Ny\u00edlbillenty\u0171k</td><td>T\u00e9rk\u00e9p mozgat\u00e1sa balra, jobbra, fel, le</td></tr><tr><td>Ctrl + bal, jobb ny\u00edl</td><td>El\u0151z\u0151 vagy k\u00f6vetkez\u0151 pont kiv\u00e1laszt\u00e1sa</td></tr><tr><td>Ctrl + fel, le ny\u00edl</td><td>Nagy\u00edt\u00e1s vagy kicsiny\u00edt\u00e9s</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>El\u0151z\u0151, k\u00f6vetkez\u0151 szakasz kiv\u00e1laszt\u00e1sa</td></tr><tr><td>Ctrl + Home, End</td>< [...]
 dialog.keys.normalmodifier=Ctrl
@@ -415,7 +501,7 @@ dialog.saveconfig.prune.autosavesettings=Automatikus ment\u00e9s be\u00e1ll\u00e
 dialog.setpaths.intro=Ha sz\u00fcks\u00e9ges, kiv\u00e1laszthatja a k\u00fcls\u0151 alkalmaz\u00e1sok \u00fatvonalait:
 dialog.setpaths.found=\u00datvonal megtal\u00e1lhat\u00f3?
 dialog.addaltitude.noaltitudes=A kiv\u00e1lasztott tartom\u00e1ny nem tartalmaz magass\u00e1gi \u00e9rt\u00e9keket
-dialog.addaltitude.desc=Magass\u00e1ki eltol\u00e1s, amely hozz\u00e1adand\u00f3
+dialog.addaltitude.desc=Magass\u00e1gi eltol\u00e1s, amely hozz\u00e1adand\u00f3
 dialog.lookupsrtm.overwritezeros=Fel\u00fcl\u00edrja a nulla magass\u00e1g \u00e9rt\u00e9ket?
 dialog.setcolours.intro=A sz\u00edn m\u00f3dos\u00edt\u00e1s\u00e1hoz kattintson egy sz\u00ednfoltra
 dialog.setcolours.background=H\u00e1tt\u00e9r
@@ -440,6 +526,7 @@ dialog.diskcache.save=T\u00e9rk\u00e9pek ment\u00e9se a lemezre
 dialog.diskcache.dir=Gyors\u00edt\u00f3t\u00e1r k\u00f6nyvt\u00e1ra
 dialog.diskcache.createdir=K\u00f6nyvt\u00e1r l\u00e9trehoz\u00e1sa
 dialog.diskcache.nocreate=A gyors\u00edt\u00f3t\u00e1r k\u00f6nyvt\u00e1ra nem ker\u00fclt l\u00e9trehoz\u00e1sra
+dialog.diskcache.cannotwrite=A t\u00e9rk\u00e9pcsemp\u00e9k nem menthet\u0151ek a kiv\u00e1lasztott k\u00f6nyvt\u00e1rba
 dialog.diskcache.table.path=El\u00e9r\u00e9si \u00fat
 dialog.diskcache.table.usedby=Haszn\u00e1lja
 dialog.diskcache.table.zoom=Nagy\u00edt\u00e1s
@@ -450,12 +537,31 @@ dialog.diskcache.tileset.multiple=t\u00f6bb
 dialog.diskcache.deleteold=R\u00e9gi csemp\u00e9k t\u00f6rl\u00e9se
 dialog.diskcache.maximumage=Maxim\u00e1lis kor (nap)
 dialog.diskcache.deleteall=Az \u00f6sszes csempe t\u00f6rl\u00e9se
-dialog.diskcache.deleted1=
-dialog.diskcache.deleted2=f\u00e1jl t\u00f6r\u00f6lve a gyors\u00edt\u00f3t\u00e1rb\u00f3l
+dialog.diskcache.deleted=%d f\u00e1jl t\u00f6r\u00f6lve a gyors\u00edt\u00f3t\u00e1rb\u00f3l
 dialog.deletefieldvalues.intro=V\u00e1lassza ki a t\u00f6rlend\u0151 mez\u0151t a jelenlegi tartom\u00e1nyban
+dialog.deletefieldvalues.nofields=Nincs t\u00f6r\u00f6lhet\u0151 mez\u0151 a tartom\u00e1nyban
 dialog.setlinewidth.text=Adja meg a rajzoland\u00f3 vonalak vastags\u00e1g\u00e1t a nyomvonalak sz\u00e1m\u00e1ra (1-4)
 dialog.downloadosm.desc=Nyers OSM adatok let\u00f6lt\u00e9s\u00e9nek meger\u0151s\u00edt\u00e9se a megadott ter\u00fcletre:
 dialog.searchwikipedianames.search=Keres\u00e9s erre:
+dialog.weather.location=Helysz\u00edn
+dialog.weather.update=El\u0151rejelz\u00e9s friss\u00edtve
+dialog.weather.sunrise=Napkelte
+dialog.weather.sunset=Napnyugta
+dialog.weather.temperatureunits=H\u0151m\u00e9rs\u00e9klet
+dialog.weather.currentforecast=Jelenlegi id\u0151j\u00e1r\u00e1s
+dialog.weather.dailyforecast=Napi el\u0151rejelz\u00e9s
+dialog.weather.3hourlyforecast=3 \u00f3r\u00e1s el\u0151rejelz\u00e9s
+dialog.weather.day.now=Jelenlegi id\u0151j\u00e1r\u00e1s
+dialog.weather.day.today=Ma
+dialog.weather.day.tomorrow=Holnap
+dialog.weather.day.monday=H\u00e9tf\u0151
+dialog.weather.day.tuesday=Kedd
+dialog.weather.day.wednesday=Szerda
+dialog.weather.day.thursday=Cs\u00fct\u00f6rt\u00f6k
+dialog.weather.day.friday=P\u00e9ntek
+dialog.weather.day.saturday=Szombat
+dialog.weather.day.sunday=Vas\u00e1rnap
+dialog.weather.creditnotice=Az adatok az openweathermap.org-r\u00f3l sz\u00e1rmaznak. N\u00e1luk tov\u00e1bbi inform\u00e1ci\u00f3t tal\u00e1lsz.
 
 # 3d window
 dialog.3d.title=GpsPrune 3D n\u00e9zet
@@ -474,10 +580,12 @@ confirm.addtimeoffset=Id\u0151eltol\u00e1s hozz\u00e1adva
 confirm.addaltitudeoffset=Magass\u00e1geltol\u00e1s hozz\u00e1adva
 confirm.rearrangewaypoints=\u00datpontok \u00fajrarendezve
 confirm.rearrangephotos=F\u00e9nyk\u00e9pek \u00fajrarendezve
+confirm.splitsegments=%d szakasz v\u00e1g\u00e1s elv\u00e9gezve
+confirm.sewsegments=%d szakasz egyes\u00edt\u00e9se elv\u00e9gezve
 confirm.cutandmove=Kijel\u00f6l\u00e9s \u00e1thelyezve
+confirm.interpolate=Pontok hozz\u00e1adva
 confirm.convertnamestotimes=\u00datpont nevei konvert\u00e1lva
-confirm.saveexif.ok1=Mentve
-confirm.saveexif.ok2=k\u00e9pf\u00e1jl
+confirm.saveexif.ok=Mentve %d k\u00e9pf\u00e1jl
 confirm.undo.single=m\u0171velet visszavonva
 confirm.undo.multi=m\u0171velet visszavonva
 confirm.jpegload.single=f\u00e9nyk\u00e9p hozz\u00e1adva
@@ -491,13 +599,23 @@ confirm.correlatephotos.multi=f\u00e9nyk\u00e9p megfeleltetve
 confirm.createpoint=pont l\u00e9trehozva
 confirm.rotatephoto=f\u00e9nyk\u00e9p elforgatva
 confirm.running=Futtat\u00e1s...
-confirm.lookupsrtm1=
-confirm.lookupsrtm2=magass\u00e1gi \u00e9rt\u00e9k tal\u00e1lhat\u00f3
+confirm.lookupsrtm=%d magass\u00e1gi \u00e9rt\u00e9k tal\u00e1lhat\u00f3
+confirm.downloadsrtm=%d f\u00e1jl let\u00f6lt\u00e9se gyors\u00edt\u00f3t\u00e1rba
+confirm.downloadsrtm.1=%d f\u00e1jl let\u00f6lt\u00e9se gyors\u00edt\u00f3t\u00e1rba
+confirm.downloadsrtm.none=Nem kellett f\u00e1jlokat let\u00f6lteni, m\u00e1r gyors\u00edt\u00f3t\u00e1rban voltak.
 confirm.deletefieldvalues=Mez\u0151 \u00e9rt\u00e9kei t\u00f6r\u00f6lve
 confirm.audioload=Hangf\u00e1jl hozz\u00e1adva
 confirm.correlateaudios.single=hangf\u00e1jl megfeleltetve
 confirm.correlateaudios.multi=hangf\u00e1jl megfeleltetve
 
+# Tips, shown just once when appropriate
+tip.title=Tipp
+tip.useamapcache=Gyors\u00edt\u00f3t\u00e1r be\u00e1ll\u00edt\u00e1s\u00e1val (Be\u00e1ll\u00edt\u00e1sok -> T\u00e9rk\u00e9pek lemezre ment\u00e9se) gyors\u00edthatod a megjelen\u00edt\u00e9st \u00e9s cs\u00f6kkentheted az adatforgalmat.
+tip.learntimeparams=Az eredm\u00e9ny sokkal pontosabb lesz, ha be\u00e1ll\u00edtod a\n Nyomvonal -> Id\u0151becsl\u00e9s tanul\u00e1s\u00e1nak param\u00e9terei\n \u00e9rt\u00e9keit a r\u00f6gz\u00edtett nyomvonalhoz.
+tip.downloadsrtm=Gyors\u00edthatod a folyamatot, ha az adatokat lemezre mented:\n Online -> SRTM csemp\u00e9k let\u00f6lt\u00e9se
+tip.usesrtmfor3d=A nyomvonal nem tartalmaz magass\u00e1gadatokat.\nHaszn\u00e1lhatod az SRTM funkci\u00f3kat, hogy k\u00f6zel\u00edt\u0151 magass\u00e1gi \u00e9rt\u00e9keket kapj\na 3D n\u00e9zethez
+tip.manuallycorrelateone=Legal\u00e1bb egy elem k\u00e9zzel t\u00f6rt\u00e9n\u0151 \u00f6sszekapcsol\u00e1s\u00e1val az id\u0151eltol\u00e1s kisz\u00e1m\u00edthat\u00f3.
+
 # Buttons
 button.ok=OK
 button.back=El\u0151z\u0151
@@ -515,6 +633,7 @@ button.yes=Igen
 button.no=Nem
 button.yestoall=Igen, mindet
 button.notoall=Nem, egyiket se
+button.always=Mindig
 button.select=Kijel\u00f6l\u00e9s
 button.selectall=Mindent kijel\u00f6l
 button.selectnone=Kijel\u00f6l\u00e9s megsz\u00fcntet\u00e9se
@@ -529,6 +648,7 @@ button.browse=B\u00f6ng\u00e9sz\u00e9s...
 button.addnew=\u00daj hozz\u00e1ad\u00e1sa
 button.delete=T\u00f6rl\u00e9s
 button.manage=Kezel\u00e9s
+button.combine=Egyes\u00edt\u00e9s
 
 # File types
 filetype.txt=TXT f\u00e1jlok
@@ -546,6 +666,7 @@ filetype.audio=MP3, OGG, WAV f\u00e1jlok
 display.nodata=Nincs adat bet\u00f6ltve
 display.noaltitudes=A nyomvonal nem tartalmaz magass\u00e1gi adatot
 display.notimestamps=A nyomvonal nem tartalmaz id\u0151b\u00e9lyegeket
+display.novalues=A nyomvonal nem tartalmaz \u00e9rt\u00e9keket ehhez a mez\u0151h\u00f6z
 details.trackdetails=Nyomvonal r\u00e9szletei
 details.notrack=Nincs adat bet\u00f6ltve
 details.track.points=Pontok
@@ -582,6 +703,7 @@ details.nophoto=Nincs f\u00e9nyk\u00e9p kiv\u00e1lasztva
 details.photo.loading=Bet\u00f6lt\u00e9s
 details.photo.bearing=Ir\u00e1ny
 details.media.connected=\u00d6sszekapcsolva
+details.media.fullpath=Teljes el\u00e9r\u00e9si \u00fat
 details.audiodetails=Hang r\u00e9szletei
 details.noaudio=Nincs hangf\u00e1jl kiv\u00e1lasztva
 details.audio.file=Hangf\u00e1jl
@@ -592,7 +714,7 @@ map.overzoom=Nem \u00e9rhet\u0151 el t\u00e9rk\u00e9p ezen a nagy\u00edt\u00e1si
 fieldname.latitude=Sz\u00e9less\u00e9g
 fieldname.longitude=Hossz\u00fas\u00e1g
 fieldname.altitude=Magass\u00e1g
-fieldname.timestamp=Id\u00f5
+fieldname.timestamp=Id\u0151
 fieldname.time=Id\u0151
 fieldname.waypointname=N\u00e9v
 fieldname.waypointtype=T\u00edpus
@@ -600,7 +722,6 @@ fieldname.newsegment=Szakasz
 fieldname.custom=Egy\u00e9ni
 fieldname.prefix=Mez\u0151
 fieldname.distance=T\u00e1vols\u00e1g
-fieldname.movingdistance=Mozg\u00e1si t\u00e1vols\u00e1g
 fieldname.duration=Id\u0151tartam
 fieldname.speed=Sebess\u00e9g
 fieldname.verticalspeed=F\u00fcgg\u0151leges sebess\u00e9g
@@ -615,16 +736,34 @@ units.feet=l\u00e1b
 units.feet.short=ft
 units.kilometres=kilom\u00e9ter
 units.kilometres.short=km
+units.kilometresperhour=km / \u00f3ra
+units.kilometresperhour.short=km/h
 units.miles=m\u00e9rf\u00f6ld
 units.miles.short=mi
+units.milesperhour=m\u00e9rf\u00f6ld / \u00f3ra
 units.milesperhour.short=mph
+units.nauticalmiles=Tengeri m\u00e9rf\u00f6ld
+units.nauticalmiles.short=nmi
+units.nauticalmilesperhour.short=csom\u00f3
+units.metrespersec=m\u00e9ter / sec
 units.metrespersec.short=m/s
+units.feetpersec=l\u00e1b / sec
 units.feetpersec.short=ft/s
 units.hours=\u00f3ra
+units.minutes=perc
+units.seconds=m\u00e1sodperc
 units.degminsec=Sz\u00f6g-sz\u00f6gperc-sz\u00f6gm\u00e1sodperc
 units.degmin=Sz\u00f6g-sz\u00f6gperc
 units.deg=Sz\u00f6g
 units.iso8601=ISO 8601
+units.degreescelsius=Celsius
+units.degreescelsius.short=\u00b0C
+units.degreesfahrenheit=Fahrenheit
+units.degreesfahrenheit.short=\u00b0F
+
+# How to combine conditions, such as filters
+logic.and=\u00e9s
+logic.or=vagy
 
 # External urls
 url.googlemaps=maps.google.hu
@@ -645,10 +784,13 @@ undo.deletepoint=pont t\u00f6rl\u00e9se
 undo.removephoto=f\u00e9nyk\u00e9p elt\u00e1vol\u00edt\u00e1sa
 undo.removeaudio=hangf\u00e1jl elt\u00e1vol\u00edt\u00e1sa
 undo.deleterange=tartom\u00e1ny t\u00f6rl\u00e9se
+undo.croptrack=nyomvonal k\u00f6rbev\u00e1g\u00e1sa
 undo.deletemarked=nyomvonal t\u00f6m\u00f6r\u00edt\u00e9se
 undo.insert=pontok besz\u00far\u00e1sa
 undo.reverse=tartom\u00e1ny megford\u00edt\u00e1sa
 undo.mergetracksegments=nyomvonalszakaszok egyes\u00edt\u00e9se
+undo.splitsegments=nyomvonal szakaszokra v\u00e1g\u00e1sa
+undo.sewsegments=nyomvonalszakaszok egyes\u00edt\u00e9se
 undo.addtimeoffset=id\u0151eltol\u00e1s hozz\u00e1ad\u00e1sa
 undo.addaltitudeoffset=magass\u00e1geltol\u00e1s hozz\u00e1ad\u00e1sa
 undo.rearrangewaypoints=\u00fatpontok \u00fajrarendez\u00e9se
@@ -671,10 +813,8 @@ error.save.failed=Az adatok f\u00e1jlba ment\u00e9se nem siker\u00fclt
 error.saveexif.filenotfound=F\u00e9nyk\u00e9pf\u00e1jl keres\u00e9se nem siker\u00fclt
 error.saveexif.cannotoverwrite1=A(z)
 error.saveexif.cannotoverwrite2=f\u00e9nyk\u00e9pf\u00e1jl csak olvashat\u00f3, \u00e9s nem \u00edrhat\u00f3 fel\u00fcl. \u00cdrjuk m\u00e1solatba?
-error.saveexif.failed1=
-error.saveexif.failed2=k\u00e9p ment\u00e9se nem siker\u00fclt
-error.saveexif.forced1=
-error.saveexif.forced2=k\u00e9p er\u00f6ltet\u00e9st ig\u00e9nyelt
+error.saveexif.failed=%d k\u00e9p ment\u00e9se nem siker\u00fclt
+error.saveexif.forced=%d k\u00e9p er\u0151ltet\u00e9st ig\u00e9nyelt
 error.load.dialogtitle=Hiba az adatok bet\u00f6lt\u00e9sekor
 error.load.noread=A f\u00e1jl nem olvashat\u00f3
 error.load.nopoints=Nem tal\u00e1lhat\u00f3 koordin\u00e1tainform\u00e1ci\u00f3 a f\u00e1jlban
@@ -685,7 +825,7 @@ error.jpegload.dialogtitle=Hiba a k\u00e9pek bet\u00f6lt\u00e9sekor
 error.jpegload.nofilesfound=Nem tal\u00e1lhat\u00f3 f\u00e1jl
 error.jpegload.nojpegsfound=Nem tal\u00e1lhat\u00f3 jpeg f\u00e1jl
 error.jpegload.nogpsfound=Nem tal\u00e1lhat\u00f3 GPS inform\u00e1ci\u00f3
-error.jpegload.exifreadfailed=Az EXIF inform\u00e1ci\u00f3 olvas\u00e1sa nem siker\u00fclt. Nem olvasat\u00f3 EXIF inform\u00e1ci\u00f3\nbe\u00e9p\u00edtett vagy k\u00fcls\u0151 f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r n\u00e9lk\u00fcl.
+error.jpegload.exifreadfailed=Az Exif inform\u00e1ci\u00f3 olvas\u00e1sa nem siker\u00fclt. Nem olvashat\u00f3 Exif inform\u00e1ci\u00f3\nbe\u00e9p\u00edtett vagy k\u00fcls\u0151 f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r n\u00e9lk\u00fcl.
 error.audioload.nofilesfound=Nem tal\u00e1lhat\u00f3 hangf\u00e1jl
 error.gpsload.unknown=Ismeretlen hiba
 error.undofailed.title=A visszavon\u00e1s nem siker\u00fclt
@@ -704,7 +844,13 @@ error.lookupsrtm.nonefound=Nem \u00e9rhet\u0151 el magass\u00e1gi \u00e9rt\u00e9
 error.lookupsrtm.nonerequired=Az \u00f6sszes pont m\u00e1r rendelkezik magass\u00e1gadatokkal, \u00edgy nincs mit keresni
 error.gpsies.uploadnotok=A gpsies szerver a k\u00f6vetkez\u0151 \u00fczenetet adta vissza
 error.gpsies.uploadfailed=A felt\u00f6lt\u00e9s nem siker\u00fclt a k\u00f6vetkez\u0151 hib\u00e1val
+error.showphoto.failed=F\u00e9nyk\u00e9p bet\u00f6lt\u00e9se sikertelen
 error.playaudiofailed=A hangf\u00e1jl lej\u00e1tsz\u00e1sa nem siker\u00fclt
 error.cache.notthere=A csempegyors\u00edt\u00f3t\u00e1r k\u00f6nyvt\u00e1ra nem tal\u00e1lhat\u00f3
 error.cache.empty=A csempegyors\u00edt\u00f3t\u00e1r k\u00f6nyvt\u00e1ra \u00fcres
 error.cache.cannotdelete=Nincs t\u00f6r\u00f6lhet\u0151 csempe
+error.interpolate.invalidparameter=A pontok sz\u00e1ma 1 \u00e9s 1000 k\u00f6z\u00f6tt kell legyen
+error.learnestimationparams.failed=Nem lehet param\u00e9tereket tanulni ebb\u0151l a nyomvonalb\u00f3l.\nT\u00f6lts be t\u00f6bb nyomvonalat.
+error.tracksplit.nosplit=A nyomvonal nem elv\u00e1ghat\u00f3
+error.downloadsrtm.nocache=A f\u00e1jlokat nem siker\u00fclt meneni.\nK\u00e9rlek, ellen\u0151rizd a gyors\u00edt\u00f3t\u00e1rat.
+error.sewsegments.nothingdone=A szakaszokat nem lehet \u00f6sszef\u0171zni.\nA nyomvonal jelenleg %d szakaszt tartalmaz.
diff --git a/tim/prune/lang/prune-texts_in.properties b/tim/prune/lang/prune-texts_in.properties
new file mode 100644
index 0000000..c0b4246
--- /dev/null
+++ b/tim/prune/lang/prune-texts_in.properties
@@ -0,0 +1,111 @@
+# Text entries for the Prune application
+# Indonesian entries as extra
+
+# Menu entries
+menu.file=Berkas
+menu.file.addphotos=Muat foto
+menu.file.save=Simpan
+menu.file.exit=Keluar
+menu.track=Track
+menu.track.undo=Batal
+menu.point.editpoint=Perbaiki titik
+menu.point.deletepoint=Hapus titik
+function.deleterange=Hapus jarak
+menu.range=Jangkauan
+menu.point=Titik
+menu.range.all=Pilih semua
+menu.range.none=Tidak memilih
+menu.photo=Foto
+menu.photo.saveexif=Simpan ke Exif
+function.connecttopoint=Hubungkan ke titik
+function.disconnectfrompoint=Putuskan dari titik
+function.removephoto=Menghapus foto
+menu.view=Lihat
+menu.settings=Pengaturan
+menu.view.browser=Peta di browser
+menu.help=Bantuan
+# Popup menu for map
+menu.map.zoomin=Perbesar
+menu.map.zoomout=Perkecil
+menu.map.newpoint=Buat titik baru
+menu.map.connect=Hubungkan titik jalur
+menu.map.showmap=Tampilkan peta
+
+# Alt keys for menus
+altkey.menu.file=B
+altkey.menu.track=T
+altkey.menu.range=J
+altkey.menu.point=K
+altkey.menu.view=L
+altkey.menu.photo=F
+altkey.menu.settings=G
+altkey.menu.help=N
+
+# Ctrl shortcuts for menu items
+shortcut.menu.file.open=B
+shortcut.menu.file.load=M
+shortcut.menu.file.save=S
+shortcut.menu.track.undo=Z
+shortcut.menu.edit.compress=P
+shortcut.menu.range.all=-
+shortcut.menu.help.help=-
+
+# Functions
+function.open=Buka
+function.loadfromgps=Muat data dari GPS
+function.sendtogps=Kirim data ke GPS
+function.exportkml=Ekspor KML
+function.exportgpx=Ekspor GPX
+function.exportpov=Ekspor POV
+function.editwaypointname=Perbaiki Nama waypoint
+function.compress=Padatkan jalur
+function.findwaypoint=Menemukan waypoint
+function.charts=Grafik
+function.show3d=Lihat tiga-D
+function.distances=Jarak
+function.correlatephotos=Korelasikan foto
+function.help=Bantuan
+function.about=Tentang Prune
+function.saveconfig=Simpan pengaturan
+
+# Dialogs
+dialog.load.table.datatype=Jenis
+dialog.load.table.description=Keterangan
+dialog.pointnameedit.name=Nama
+dialog.distances.column.from=Awal
+dialog.distances.column.to=Akhir
+dialog.about.languages=Bahasa
+dialog.about.yes=Ya
+dialog.about.no=Tidak
+
+# Buttons
+button.back=Sebelumnya
+button.next=Lanjut
+button.close=Tutup
+button.yes=Ya
+button.no=Tidak
+button.select=Pilih
+button.guessfields=Deteksi otomatis
+
+# File types
+filetype.txt=Berkas teks
+filetype.jpeg=Berkas JPG
+filetype.kmlkmz=Berkas KML, KMZ
+filetype.kml=Berkas KML
+filetype.kmz=Berkas KMZ
+filetype.gpx=Berkas GPX
+filetype.pov=Berkas POV
+filetype.svg=Berkas SVG
+
+# Display components
+display.nodata=Tidak ada data
+details.trackdetails=Rincian track
+details.notrack=Tidak ada track
+details.pointdetails=Rincian titik
+details.nopointselection=Tidak ada titik
+details.norangeselection=Tidak ada jangkauan
+details.rangedetails=Rincian jangkauan
+details.lists.photos=Foto
+details.photodetails=Rincian foto
+details.nophoto=Tidak ada foto
+details.photo.loading=Membuka
diff --git a/tim/prune/lang/prune-texts_it.properties b/tim/prune/lang/prune-texts_it.properties
index 719b791..121e04d 100644
--- a/tim/prune/lang/prune-texts_it.properties
+++ b/tim/prune/lang/prune-texts_it.properties
@@ -7,6 +7,7 @@ menu.file.addphotos=Aggiungi foto
 menu.file.recentfiles=Files recenti
 menu.file.save=Salva
 menu.file.exit=Esci
+menu.online=Online
 menu.track=Traccia
 menu.track.undo=Annulla
 menu.track.clearundo=Cancella lista annulla
@@ -57,6 +58,7 @@ menu.map.editmode=Modifica
 
 # Alt keys for menus
 altkey.menu.file=F
+altkey.menu.online=N
 altkey.menu.track=T
 altkey.menu.range=S
 altkey.menu.point=P
@@ -101,12 +103,15 @@ function.show3d=Mostra in 3D
 function.distances=Mostra distanze
 function.fullrangedetails=Mostra dettagli
 function.estimatetime=Stima durata
-function.learnestimationparams=Apprendi paramet
+function.learnestimationparams=Apprendi parametri di stima
 function.setmapbg=Configura sfondo mappa
 function.setpaths=Configura percorsi programmi
+function.splitsegments=Dividi traccia in segmenti
+function.sewsegments=Riorganizza segmenti insieme
 function.getgpsies=Ottieni traccie da Gpsies
 function.uploadgpsies=Carica traccia su Gpsies
 function.lookupsrtm=Ottieni quote da SRTM
+function.downloadsrtm=Scarica file da SRTM
 function.getwikipedia=Ottieni informazioni da Wikipedia
 function.searchwikipedianames=Cerca il nome in Wikipedia
 function.downloadosm=Scarica dati OSM dell'area
@@ -135,6 +140,7 @@ function.checkversion=Controlla gli aggiornamenti
 function.saveconfig=Salva configurazione
 function.diskcache=Salva mappe su disco
 function.managetilecache=Gestione del cache di tasselli
+function.getweatherforecast=Ottieni previsioni del tempo
 
 # Dialogs
 dialog.exit.confirm.title=Esci da GpsPrune
@@ -233,6 +239,8 @@ dialog.exportgpx.copysource=Copia xml sorgente
 dialog.exportgpx.encoding=Codifica caratteri
 dialog.exportgpx.encoding.system=Impostazione di diffetto
 dialog.exportgpx.encoding.utf8=UTF-8
+dialog.3d.useterrain=Mostra terreno
+dialog.3d.terraingridsize=Dimensione della griglia
 dialog.exportpov.text=Per favore inserisci i parametri per l'esportazione in POV
 dialog.exportpov.font=Font
 dialog.exportpov.camerax=Camera X
@@ -257,6 +265,7 @@ dialog.exportsvg.theta=Angolo di elevazione \u03b8
 dialog.exportsvg.gradients=Usa il gradiente per le ombre
 dialog.exportimage.noimagepossible=Le mappe devono essere memorizzate su disco prima di poter essere esportate.
 dialog.exportimage.drawtrack=Disegna traccia sulla mappa
+dialog.exportimage.drawtrackpoints=Disegna punti
 dialog.exportimage.textscalepercent=Fattore di scala testo (%)
 dialog.pointtype.desc=Salva i tipi di punti seguenti:
 dialog.pointtype.track=Punti traccia
@@ -378,7 +387,6 @@ dialog.correlate.photoselect.intro=Selezione una delle foto correlate da usare c
 dialog.correlate.select.photoname=Nome della foto
 dialog.correlate.select.timediff=Differenza di orario
 dialog.correlate.select.photolater=Foto scattata dopo il punto
-dialog.correlate.options.tip=Consiglio: Con il collegamento manuale di almeno una foto, lo scarto di orario viene calcolato per te
 dialog.correlate.options.intro=Selezione le opzioni per la correlazione automatica
 dialog.correlate.options.offsetpanel=Scarto di orario
 dialog.correlate.options.offset=Scarto
@@ -421,8 +429,7 @@ dialog.compress.duplicates.title=Cancella duplicati
 dialog.compress.douglaspeucker.title=Compressione con algoritmo Douglas-Peucker
 dialog.compress.douglaspeucker.paramdesc=Fattore di apertura
 dialog.compress.summarylabel=Punti da cancellare
-dialog.compress.confirm1=
-dialog.compress.confirm2=punti sono stati contrassegnati.\nUsa traccia-> Elimina i punti segnati per eliminarle
+dialog.compress.confirm=%d punti sono stati contrassegnati.\nElimina i punti?
 dialog.compress.confirmnone=I punti non sono stati marcati
 dialog.deletemarked.nonefound=Nessun punto rimosso
 dialog.pastecoordinates.desc=Inserisci o incolla qui le coordinate
@@ -530,13 +537,34 @@ dialog.diskcache.tileset.multiple=molteplici
 dialog.diskcache.deleteold=Cancellare tasselli vecchi
 dialog.diskcache.maximumage=Et\u00e0 massima (giorni)
 dialog.diskcache.deleteall=Cancellare tutti tasselli
-dialog.diskcache.deleted1=Cancellati
-dialog.diskcache.deleted2=files dal cache
+dialog.diskcache.deleted=Cancellati %d files dal cache
 dialog.deletefieldvalues.intro=Selezione il campo da cancellare dall'intervallo corrente
 dialog.deletefieldvalues.nofields=Non ci sono campi da cancellare nell'intervallo corrente
 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:
+dialog.weather.location=Localit\u00e0
+dialog.weather.update=Aggiornata
+dialog.weather.sunrise=Levata del sole
+dialog.weather.sunset=Tramonto del sole
+dialog.weather.temperatureunits=Temperature
+dialog.weather.currentforecast=Tempo attuale
+dialog.weather.dailyforecast=Previsione quotidiano
+dialog.weather.3hourlyforecast=Previsione ogni 3 ore
+dialog.weather.day.now=Tempo attuale
+dialog.weather.day.today=Oggi
+dialog.weather.day.tomorrow=Domani
+dialog.weather.day.monday=Luned\u00ec
+dialog.weather.day.tuesday=Marted\u00ec
+dialog.weather.day.wednesday=Mercoled\u00ec
+dialog.weather.day.thursday=Gioved\u00ec
+dialog.weather.day.friday=Venerd\u00ec
+dialog.weather.day.saturday=Sabato
+dialog.weather.day.sunday=Domenica
+dialog.weather.wind=Vento
+dialog.weather.temp=Temp
+dialog.weather.humidity=Umidit\u00e0
+dialog.weather.creditnotice=Queste informazioni sono rese disponibili da openweathermap.org. Il loro sito web contiene ulteriori dettagli.
 
 # 3d window
 dialog.3d.title=Visione GpsPrune in 3D
@@ -549,17 +577,18 @@ confirm.save.ok2=punti nel file
 confirm.deletepoint.single=punto \u00e8 stato rimosso
 confirm.deletepoint.multi=punti sono stati rimossi
 confirm.point.edit=punto editato
-confirm.mergetracksegments=segmeni di traccia uniti
+confirm.mergetracksegments=segmenti di traccia uniti
 confirm.reverserange=Intervallo invertito
 confirm.addtimeoffset=Scarto temporale aggiunto
 confirm.addaltitudeoffset=Scarto altitudine aggiunto
 confirm.rearrangewaypoints=Waypoint riorganizzati
 confirm.rearrangephotos=Foto riorganizzate
+confirm.splitsegments=%d segmenti sono stati suddivisi
+confirm.sewsegments=%d segmenti sono stati raggruppati
 confirm.cutandmove=Selezione spostata
 confirm.interpolate=Aggiungi punto
 confirm.convertnamestotimes=Nome del waypoint convertito
-confirm.saveexif.ok1=Salvato
-confirm.saveexif.ok2=foto
+confirm.saveexif.ok=Salvato %d foto
 confirm.undo.single=operazione annullate
 confirm.undo.multi=operazioni annullate
 confirm.jpegload.single=foto \u00e8 stata aggiunta
@@ -573,13 +602,19 @@ confirm.correlatephotos.multi=foto erano correlate
 confirm.createpoint=punto creato
 confirm.rotatephoto=foto ruotata
 confirm.running=Operazione in corso...
-confirm.lookupsrtm1=Trovato
-confirm.lookupsrtm2=valori di quota
+confirm.lookupsrtm=Trovato %d valori di quota
+confirm.downloadsrtm=Scarica %d file nella cache
+confirm.downloadsrtm.1=Scarica %d file nella cache
+confirm.downloadsrtm.none=Nessun file scaricato, erano gi\u00e0 presenti nella cache
 confirm.deletefieldvalues=Valori del campo cancellati
 confirm.audioload=Ripresa audio aggiunta
 confirm.correlateaudios.single=la ripresa audio era correlata
 confirm.correlateaudios.multi=le riprese audio erano correlate
 
+# Tips
+tip.title=Consiglio
+tip.manuallycorrelateone=Con il collegamento manuale di almeno una foto, lo scarto di orario viene calcolato per te
+
 # Buttons
 button.ok=OK
 button.back=Precedente
@@ -597,6 +632,7 @@ button.yes=S\u00ec
 button.no=No
 button.yestoall=Si a tutto
 button.notoall=No a tutto
+button.always=Sempre
 button.select=Seleziona
 button.selectall=Seleziona tutto
 button.selectnone=Deseleziona tutto
@@ -685,7 +721,6 @@ fieldname.newsegment=Segmento
 fieldname.custom=Custom
 fieldname.prefix=Campo
 fieldname.distance=Distanza
-fieldname.movingdistance=Distanza in movimento
 fieldname.duration=Durata
 fieldname.speed=Velocit\u00e0
 fieldname.verticalspeed=Velocit\u00e0 verticale
@@ -720,6 +755,10 @@ units.degminsec=Deg-min-sec
 units.degmin=Deg-min
 units.deg=Degrees
 units.iso8601=ISO 8601
+units.degreescelsius=Celsius
+units.degreescelsius.short=\u00b0C
+units.degreesfahrenheit=Fahrenheit
+units.degreesfahrenheit.short=\u00b0F
 
 # How to combine conditions, such as filters
 logic.and=e
@@ -728,6 +767,7 @@ logic.or=o
 # External urls
 url.googlemaps=maps.google.it
 wikipedia.lang=it
+openweathermap.lang=it
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -749,6 +789,8 @@ undo.deletemarked=elimina punti
 undo.insert=inserisci punti
 undo.reverse=inverti l'intervallo
 undo.mergetracksegments=unisci segmenti traccia
+undo.splitsegments=dividi traccia
+undo.sewsegments=riorganizza segmenti traccia
 undo.addtimeoffset=aggiungi scarto temporale
 undo.addaltitudeoffset=aggiungi scarto altitudine
 undo.rearrangewaypoints=riorganizza waypoint
@@ -771,10 +813,8 @@ error.save.failed=Fallito il tentativo di salvare i dati nel file
 error.saveexif.filenotfound=Non trovato un file di foto
 error.saveexif.cannotoverwrite1=File di foto
 error.saveexif.cannotoverwrite2=\u00e8 in sola lettura e non pu\u00f2 essere sovrascritto. Creo una copia?
-error.saveexif.failed1=Salvataggio fallito
-error.saveexif.failed2=delle immagini
-error.saveexif.forced1=
-error.saveexif.forced2=delle immagini richiede forzatura
+error.saveexif.failed=Salvataggio fallito%d delle immagini
+error.saveexif.forced=%d delle immagini richiede forzatura
 error.load.dialogtitle=Errore nel caricamento dati
 error.load.noread=Non posso leggere il file
 error.load.nopoints=Non ci sono coordinate nel file
@@ -785,7 +825,7 @@ error.jpegload.dialogtitle=Errore nel caricamento delle foto
 error.jpegload.nofilesfound=File non trovato
 error.jpegload.nojpegsfound=File jpeg non trovato
 error.jpegload.nogpsfound=Informazioni GPS non trovate
-error.jpegload.exifreadfailed=Lettera dei dati EXIF fallita. I dati EXIF non possono\n essere letti senza una libreria interna o esterna.
+error.jpegload.exifreadfailed=Lettera dei dati Exif fallita. I dati Exif non possono\n essere letti senza una libreria interna o esterna.
 error.audioload.nofilesfound=Riprese audio non trovate
 error.gpsload.unknown=Errore sconosciuto
 error.undofailed.title=Impossibile annullare
@@ -811,3 +851,6 @@ error.cache.empty=Directory del cache di tasselli \u00e8 vuoto
 error.cache.cannotdelete=Impossibile cancellare tasselli
 error.interpolate.invalidparameter=Il numero di punti deve essere tra 1 e 1000
 error.learnestimationparams.failed=Non \u00e8 possibile apprendere i parametri da questa traccia.\nProva a caricare pi\u00f9 tracce.
+error.tracksplit.nosplit=La traccia non pu\u00f2 essere divisa
+error.downloadsrtm.nocache=Non \u00e8 stato possibile salvare i file.\nControlla la cache del disco.
+error.sewsegments.nothingdone=Non \u00e8 stato possibile riorganizzare nessun segmento.\nCi sono %d segmenti nella traccia.
diff --git a/tim/prune/lang/prune-texts_ja.properties b/tim/prune/lang/prune-texts_ja.properties
index 142ae84..ad25d69 100644
--- a/tim/prune/lang/prune-texts_ja.properties
+++ b/tim/prune/lang/prune-texts_ja.properties
@@ -118,7 +118,7 @@ function.rearrangephotos=\u5199\u771f\u306e\u4e26\u3079\u76f4\u3057
 function.rotatephotoleft=\u5199\u771f\u3092\u5de6\u306b\u56de\u3059
 function.rotatephotoright=\u5199\u771f\u3092\u53f3\u306b\u56de\u3059
 function.photopopup=\u5199\u771f\u306e\u30dd\u30c3\u30d7\u30a2\u30c3\u30d7\u3092\u8868\u793a
-function.ignoreexifthumb=EXIF\u30b5\u30e0\u30cd\u30a4\u30eb\u3092\u7121\u8996
+function.ignoreexifthumb=Exif\u30b5\u30e0\u30cd\u30a4\u30eb\u3092\u7121\u8996
 function.loadaudio=\u30aa\u30fc\u30c7\u30a3\u30aa\u30d5\u30a1\u30a4\u30eb\u3092\u8ffd\u52a0
 function.removeaudio=\u4e00\u89a7\u304b\u3089\u73fe\u5728\u306e\u30aa\u30fc\u30c7\u30a3\u30aa\u30d5\u30a1\u30a4\u30eb\u3092\u524a\u9664
 function.correlateaudios=\u30aa\u30fc\u30c7\u30a3\u30aa\u3092\u95a2\u9023\u4ed8\u3051\u308b
@@ -225,7 +225,6 @@ dialog.undo.none.text=\u30a2\u30f3\u30c9\u30a5\u3067\u304d\u308b\u4f5c\u696d\u75
 dialog.clearundo.title=\u30a2\u30f3\u30c9\u30a5\u30ea\u30b9\u30c8\u3092\u7a7a\u306b\u3059\u308b
 dialog.clearundo.text=\u672c\u5f53\u306b\u30a2\u30f3\u30c9\u30a5\u30ea\u30b9\u30c8\u3092\u7a7a\u306b\u3057\u307e\u3059\u304b?\n\u5168\u3066\u306e\u30a2\u30f3\u30c9\u30a5\u60c5\u5831\u304c\u5931\u308f\u308c\u307e\u3059\u3002
 dialog.pointedit.title=\u70b9\u3092\u7de8\u96c6
-dialog.pointedit.text=\u7de8\u96c6\u3059\u308b\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u9078\u629e\u3057\u3001'\u7de8\u96c6' \u30dc\u30bf\u30f3\u3067\u5024\u3092\u7de8\u96c6\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.pointedit.table.field=\u30d5\u30a3\u30fc\u30eb\u30c9
 dialog.pointedit.table.value=\u503c
 dialog.pointnameedit.name=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u540d
@@ -240,7 +239,7 @@ dialog.addtimeoffset.minutes=\u5206
 dialog.addtimeoffset.notimestamps=\u3053\u306e\u9078\u629e\u7bc4\u56f2\u306f\u30bf\u30a4\u30e0\u30b9\u30bf\u30f3\u30d7\u3092\u6301\u3063\u3066\u306a\u3044\u306e\u3067\u3001\u504f\u4f4d\u3092\u8db3\u305b\u307e\u305b\u3093\u3002
 dialog.findwaypoint.intro=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u540d\u306e\u4e00\u90e8\u3092\u5165\u529b
 dialog.findwaypoint.search=\u691c\u7d22
-dialog.saveexif.title=EXIF\u3092\u4fdd\u5b58
+dialog.saveexif.title=Exif\u3092\u4fdd\u5b58
 dialog.saveexif.intro=\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u4f7f\u3063\u3066\u3001\u4fdd\u5b58\u3059\u308b\u5199\u771f\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.saveexif.nothingtosave=\u5ea7\u6a19\u60c5\u5831\u306f\u7121\u5909\u66f4\u3067\u3059\u3002\u4fdd\u5b58\u3059\u308b\u3082\u306e\u306f\u3042\u308a\u307e\u305b\u3093\u3002
 dialog.saveexif.noexiftool=exiftool \u30d7\u30ed\u30b0\u30e9\u30e0\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u7d9a\u3051\u307e\u3059\u304b\uff1f
@@ -296,7 +295,7 @@ dialog.correlate.photoselect.intro=\u6642\u9593\u504f\u4f4d\u3092\u4f5c\u308b\u3
 dialog.correlate.select.photoname=\u5199\u771f\u540d
 dialog.correlate.select.timediff=\u6642\u9593\u5dee
 dialog.correlate.select.photolater=\u5199\u771f\u304c\u5f8c
-dialog.correlate.options.tip=\u63d0\u793a\uff1a\u5c11\u306a\u304f\u3068\u3082\u4e00\u3064\u306e\u5199\u771f\u3092\u624b\u52d5\u3067\u95a2\u9023\u4ed8\u3051\u308b\u3068\u3001\u6642\u9593\u504f\u4f4d\u3092\u8a08\u7b97\u3059\u308b\u4e8b\u304c\u3067\u304d\u307e\u3059\u3002
+tip.manuallycorrelateone=\u63d0\u793a\uff1a\u5c11\u306a\u304f\u3068\u3082\u4e00\u3064\u306e\u5199\u771f\u3092\u624b\u52d5\u3067\u95a2\u9023\u4ed8\u3051\u308b\u3068\u3001\u6642\u9593\u504f\u4f4d\u3092\u8a08\u7b97\u3059\u308b\u4e8b\u304c\u3067\u304d\u307e\u3059\u3002
 dialog.correlate.options.intro=\u81ea\u52d5\u95a2\u9023\u4ed8\u3051\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.correlate.options.offsetpanel=\u6642\u9593\u504f\u4f4d
 dialog.correlate.options.offset=\u504f\u4f4d
@@ -336,7 +335,7 @@ dialog.deletemarked.nonefound=\u9664\u304f\u3053\u3068\u304c\u3067\u304d\u308b\u
 dialog.pastecoordinates.desc=\u5ea7\u6a19\u3092\u3053\u3053\u306b\u5165\u529b\u3059\u308b\u304b\u30da\u30fc\u30b9\u30c8\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.pastecoordinates.coords=\u5ea7\u6a19
 dialog.pastecoordinates.nothingfound=\u5ea7\u6a19\u306e\u78ba\u8a8d\u3092\u3057\u3066\u3001\u3082\u3046\u4e00\u5ea6\u8a66\u3057\u3066\u304f\u3060\u3055\u3044\u3002
-dialog.help.help=\u8a73\u3057\u3044\u60c5\u5831\u3084\u3001\u30e6\u30fc\u30b6\u30fc\u30ac\u30a4\u30c9\u306a\u3069\u306f\u3001\nhttp://activityworkshop.net/software/gpsprune/ \u3092\u898b\u3066\u304f\u3060\u3055\u3044\u3002
+dialog.help.help=\u8a73\u3057\u3044\u60c5\u5831\u3084\u3001\u30e6\u30fc\u30b6\u30fc\u30ac\u30a4\u30c9\u306a\u3069\u306f\u3001\nhttp://gpsprune.activityworkshop.net/ \u3092\u898b\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.about.version=\u30d0\u30fc\u30b8\u30e7\u30f3
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune \u306f\u3001GPS\u53d7\u4fe1\u6a5f\u304b\u3089\u306e\u30c7\u30fc\u30bf\u3092\u8aad\u307f\u8fbc\u307f\u3001\u8868\u793a\u3057\u3001\u7de8\u96c6\u3059\u308b\u305f\u3081\u306e\u30d7\u30ed\u30b0\u30e9\u30e0\u3067\u3059\u3002
@@ -371,7 +370,7 @@ dialog.checkversion.newversion1=\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u306e GpsPr
 dialog.checkversion.newversion2=\u3067\u3059\u3002
 dialog.checkversion.releasedate1=\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u306f\u3001
 dialog.checkversion.releasedate2=\u306b\u30ea\u30ea\u30fc\u30b9\u3057\u307e\u3057\u305f\u3002
-dialog.checkversion.download=\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3059\u308b\u306b\u306f\u3001 http://activityworkshop.net/software/gpsprune/download.html \u3078\u884c\u3063\u3066\u304f\u3060\u3055\u3044\u3002
+dialog.checkversion.download=\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3059\u308b\u306b\u306f\u3001 http://gpsprune.activityworkshop.net/download.html \u3078\u884c\u3063\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.keys.intro=\u30de\u30a6\u30b9\u306e\u4ee3\u308f\u308a\u306b\u6b21\u306e\u30b7\u30e7\u30fc\u30c8\u30ab\u30c3\u30c8\u30ad\u30fc\u3092\u4f7f\u3046\u4e8b\u304c\u3067\u304d\u307e\u3059\u3002
 dialog.keys.keylist=<table><tr><td>\u77e2\u5370\u30ad\u30fc</td><td>\u5730\u56f3\u3092\u4e0a\u4e0b\u5de6\u53f3\u306b\u79fb\u52d5</td></tr><tr><td>Ctrl + \u5de6\u30fb\u53f3\u77e2\u5370</td><td>\u524d\u30fb\u6b21\u306e\u70b9\u3092\u9078\u629e</td></tr><tr><td>Ctrl + \u4e0a\u30fb\u4e0b\u77e2\u5370</td><td>\u62e1\u5927\u30fb\u7e2e\u5c0f</td></tr><tr><td>Del</td><td>\u73fe\u5728\u306e\u70b9\u3092\u524a\u9664</td></tr></table>
 dialog.keys.normalmodifier=Ctrl\u30ad\u30fc
@@ -390,7 +389,7 @@ dialog.saveconfig.prune.exiftoolpath=exiftool\u3078\u306e\u30d1\u30b9
 dialog.saveconfig.prune.mapsource=\u30de\u30c3\u30d7\u30fb\u30bd\u30fc\u30b9\u3092\u9078\u629e
 dialog.saveconfig.prune.mapsourcelist=\u30de\u30c3\u30d7\u30fb\u30bd\u30fc\u30b9
 dialog.saveconfig.prune.diskcache=\u30de\u30c3\u30d7\u306e\u30ad\u30e3\u30c3\u30b7\u30e5
-dialog.saveconfig.prune.kmzimagewidth=KMZ \u753b\u50cf\u5e45
+dialog.saveconfig.prune.kmzimagewidth=KML \u753b\u50cf\u5e45
 dialog.saveconfig.prune.colourscheme=\u8272\u306e\u30b9\u30ad\u30fc\u30e0
 dialog.saveconfig.prune.linewidth=\u7dda\u306e\u5e45
 dialog.saveconfig.prune.kmltrackcolour=KML \u30c8\u30e9\u30c3\u30af\u306e\u8272
@@ -440,8 +439,7 @@ confirm.rearrangewaypoints=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u304c\u4e2
 confirm.rearrangephotos=\u5199\u771f\u304c\u4e26\u3079\u66ff\u3048\u3089\u308c\u305f
 confirm.cutandmove=\u9078\u629e\u304c\u52d5\u304b\u3055\u308c\u305f
 confirm.convertnamestotimes=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u306e\u540d\u524d\u304c\u5909\u63db\u3055\u308c\u305f
-confirm.saveexif.ok1=\u4fdd\u5b58\u3055\u308c\u305f
-confirm.saveexif.ok2=\u5199\u771f\u30d5\u30a1\u30a4\u30eb
+confirm.saveexif.ok=\u4fdd\u5b58\u3055\u308c\u305f %d \u5199\u771f\u30d5\u30a1\u30a4\u30eb
 confirm.undo.single=\u64cd\u4f5c\u306f\u30a2\u30f3\u30c9\u30a5\u3055\u308c\u305f
 confirm.undo.multi=\u64cd\u4f5c\u306f\u30a2\u30f3\u30c9\u30a5\u3055\u308c\u305f
 confirm.jpegload.single=\u5199\u771f\u304c\u52a0\u3048\u3089\u308c\u305f
@@ -455,8 +453,7 @@ confirm.correlatephotos.multi=\u5199\u771f\u304c\u95a2\u9023\u4ed8\u3051\u3089\u
 confirm.createpoint=\u70b9\u304c\u4f5c\u3089\u308c\u305f
 confirm.rotatephoto=\u5199\u771f\u3092\u56de\u8ee2\u3057\u305f
 confirm.running=\u5b9f\u884c\u4e2d...
-confirm.lookupsrtm1=
-confirm.lookupsrtm2=\u6a19\u9ad8\u5024
+confirm.lookupsrtm=%d \u6a19\u9ad8\u5024
 confirm.deletefieldvalues=\u30d5\u30a3\u30fc\u30eb\u30c9\u306e\u5024\u304c\u524a\u9664\u3055\u308c\u305f
 confirm.audioload=\u30aa\u30fc\u30c7\u30a3\u30aa\u30d5\u30a1\u30a4\u30eb\u304c\u8ffd\u52a0\u3055\u308c\u305f
 confirm.correlateaudios.single=\u30aa\u30fc\u30c7\u30a3\u30aa\u304c\u95a2\u9023\u4ed8\u3051\u3089\u308c\u305f
@@ -562,7 +559,6 @@ fieldname.newsegment=\u30bb\u30b0\u30e1\u30f3\u30c8
 fieldname.custom=\u30ab\u30b9\u30bf\u30e0
 fieldname.prefix=\u30d5\u30a3\u30fc\u30eb\u30c9
 fieldname.distance=\u8ddd\u96e2
-fieldname.movingdistance=\u79fb\u52d5\u8ddd\u96e2
 fieldname.duration=\u9593\u9694
 fieldname.speed=\u901f\u5ea6
 fieldname.verticalspeed=\u5782\u76f4\u901f\u5ea6
@@ -634,10 +630,8 @@ error.save.failed=\u30c7\u30fc\u30bf\u3092\u30d5\u30a1\u30a4\u30eb\u306b\u4fdd\u
 error.saveexif.filenotfound=\u5199\u771f\u30d5\u30a1\u30a4\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f
 error.saveexif.cannotoverwrite1=\u5199\u771f\u30d5\u30a1\u30a4\u30eb
 error.saveexif.cannotoverwrite2=\u8aad\u307f\u8fbc\u307f\u5c02\u7528\u3067\u4e0a\u66f8\u304d\u3067\u304d\u307e\u305b\u3093\u3002\u8907\u88fd\u3092\u4f5c\u308a\u307e\u3059\u304b\uff1f
-error.saveexif.failed1=
-error.saveexif.failed2=\u679a\u306e\u753b\u50cf\u306e\u4fdd\u5b58\u306b\u5931\u6557\u3057\u307e\u3057\u305f
-error.saveexif.forced1=
-error.saveexif.forced2=\u679a\u306e\u753b\u50cf\u304c\u5f37\u884c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002
+error.saveexif.failed=%d \u679a\u306e\u753b\u50cf\u306e\u4fdd\u5b58\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+error.saveexif.forced=%d \u679a\u306e\u753b\u50cf\u304c\u5f37\u884c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002
 error.load.dialogtitle=\u30c7\u30fc\u30bf\u306e\u8aad\u307f\u8fbc\u307f\u30a8\u30e9\u30fc
 error.load.noread=\u30d5\u30a1\u30a4\u30eb\u304c\u8aad\u3081\u307e\u305b\u3093
 error.load.nopoints=\u30d5\u30a1\u30a4\u30eb\u306e\u4e2d\u306b\u5ea7\u6a19\u60c5\u5831\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
diff --git a/tim/prune/lang/prune-texts_ko.properties b/tim/prune/lang/prune-texts_ko.properties
index 2904a27..ec5de7d 100644
--- a/tim/prune/lang/prune-texts_ko.properties
+++ b/tim/prune/lang/prune-texts_ko.properties
@@ -219,7 +219,6 @@ dialog.undo.none.text=\ub418\ub3cc\ub9b4 \uc791\uc5c5\uc774 \uc5c6\uc5b4\uc694!
 dialog.clearundo.title=\ub418\ub3cc\ub9ac\uae30 \ubaa9\ub85d \uc9c0\uc6b0\uae30
 dialog.clearundo.text=\uc815\ub9d0\ub85c \ub418\ub3cc\ub9ac\uae30 \ubaa9\ub85d \uc9c0\uc6b0\uc2e4\uac74\uac00\uc694? /n \ubaa8\ub4e0 \ub418\ub3cc\ub9ac\uae30 \uc815\ubcf4\uac00 \uc5c6\uc5b4\uc9c4\ub2e4\uad6c\uc694!
 dialog.pointedit.title=\uc9c0\uc810 \uc218\uc815\ud558\uae30
-dialog.pointedit.text=\uc218\uc815\ud560 \ud544\ub4dc\ub97c \uc120\ud0dd\ud558\uc2dc\uace0, \uc218\uc815\ud558\uae30 \ubc84\ud2bc\uc744 \uc0ac\uc6a9\ud558\uc138\uc694.
 dialog.pointedit.table.field=\ud544\ub4dc
 dialog.pointedit.table.value=\uac12
 dialog.pointnameedit.name=\uacbd\uc720\uc9c0 \uc774\ub984
@@ -295,7 +294,6 @@ dialog.correlate.photoselect.intro=\ub300\ud45c\uc0ac\uc9c4\uc73c\ub85c \uc0ac\u
 dialog.correlate.select.photoname=\uc0ac\uc9c4\uc774\ub984
 dialog.correlate.select.timediff=\uc0ac\uae34 \ucc28\uc774
 dialog.correlate.select.photolater=\uc0ac\uc9c4 \uc2dc\uac04\uc774 \uc9c0\uc810\uc2dc\uac04\ubcf4\ub2e4 \ub290\ub9bc
-dialog.correlate.options.tip=\ub3c4\uc6c0: \ucd5c\uc18c\ud55c \ud55c\uac1c\uc758 \uc544\uc774\ud0ec(\uc0ac\uc9c4,\uc18c\ub9ac)\uc744 \uc9c1\uc811 \uc5f0\uacb0\ud558\uba74, \ud0c0\uc784 \uc624\ud504\uc14b\uc744 \uacc4\uc0b0\ud560 \uc218 \uc788\uc5b4\uc694.
 dialog.correlate.options.intro=\uc790\ub3d9 \uc5f0\uacb0\uc744 \uc704\ud574 \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.
 dialog.correlate.options.offsetpanel=\uc2dc\uac04 \uc624\ud504\uc14b
 dialog.correlate.options.offset=\uc624\ud504\uc14b
@@ -340,7 +338,7 @@ dialog.compress.summarylabel=\uc0ad\uc81c\ud560 \uc9c0\uc810
 dialog.pastecoordinates.desc=\uc790\ud45c\ub97c \ub123\uc73c\uc138\uc694
 dialog.pastecoordinates.coords=\uc88c\ud45c
 dialog.pastecoordinates.nothingfound=\uc88c\ud45c\ub97c \ud655\uc778\ud558\uc2dc\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \ubcf4\uc138\uc694.
-dialog.help.help=\ub354 \uc790\uc138\ud55c \uc815\ubcf4\ub098 \uc0ac\uc6a9\uc790\uc124\uba85\uc740 \uc774\uacf3\uc744 \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n http://activityworkshop.net/software/gpsprune/
+dialog.help.help=\ub354 \uc790\uc138\ud55c \uc815\ubcf4\ub098 \uc0ac\uc6a9\uc790\uc124\uba85\uc740 \uc774\uacf3\uc744 \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n http://gpsprune.activityworkshop.net/
 dialog.about.version=\ubc84\uc804
 dialog.about.build=\ube4c\ub4dc
 dialog.about.summarytext1=GpsPrune\uc740 GPS\uc218\uc2e0\uae30\uc5d0\uc11c \uc704\uce58 \uc815\ubcf4\ub97c \ubc1b\uace0, \ud654\uba74\uc5d0 \ubcf4\uc5ec\uc8fc\uace0, \uc218\uc815\ud558\uac8c \ud574\uc8fc\ub294 \ud504\ub85c\uadf8\ub7a8\uc785\ub2c8\ub2e4.
@@ -379,7 +377,7 @@ dialog.checkversion.newversion1=GpsPrune\uc758 \uc0c8 \ubc84\uc804\uc744 \uc9c0\
 dialog.checkversion.newversion2=\ubc84\uc804\uc785\ub2c8\ub2e4.
 dialog.checkversion.releasedate1=\uc0c8 \ubc84\uc804\uc740
 dialog.checkversion.releasedate2=\uc5d0 \ubc30\ud3ec\ub418\uc5c8\uc2b5\ub2c8\ub2e4.
-dialog.checkversion.download=\uc0c8 \ubc84\uc804\uc744 \ub2e4\uc6b4\ubc1b\uace0 \uc2f6\uc73c\uc138\uc694? \uadf8\ub7fc \uc544\ub798 URL\ub85c \uc640\uc8fc\uc138\uc694. /n http://activityworkshop.net/software/gpsprune/download.html.
+dialog.checkversion.download=\uc0c8 \ubc84\uc804\uc744 \ub2e4\uc6b4\ubc1b\uace0 \uc2f6\uc73c\uc138\uc694? \uadf8\ub7fc \uc544\ub798 URL\ub85c \uc640\uc8fc\uc138\uc694. /n http://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=\ub9c8\uc6b0\uc2a4\ub97c \uc0ac\uc6a9\ud558\uc9c0 \ub9c8\uc2dc\uace0 \uc544\ub798 \ub2e8\ucd95\ud0a4\ub97c \uc0ac\uc6a9\ud574\ubcf4\uc138\uc694.
 dialog.keys.keylist=<table><tr><td>\ud654\uc0b4\ud45c \ud0a4</td><td>\uc88c,\uc6b0,\uc544\ub798,\uc704\ub85c \uc9c0\ub3c4 \uc774\ub3d9</td></tr><tr><td>Ctrl + \uc67c\ucabd, \uc624\ub978\ucabd \ud654\uc0b4\ud45c</td><td>\uc9c0\uc810 \uc120\ud0dd \uc774\ub3d9</td></tr><tr><td>Ctrl + \uc704, \uc544\ub798 \ud654\uc0b4\ud45c</td><td>\uc9c0\ub3c4 \ud655\ub300 \ucd95\uc18c</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>\uc774\uc804 \uc774\ud6c4 \ubd80\ubd84 \uc120\ud0dd</td></tr><tr><td>Ctrl + Ho [...]
 dialog.keys.normalmodifier=Ctrl
@@ -453,8 +451,7 @@ confirm.rearrangewaypoints=\uacbd\uc720\uc9c0\uac00 \uc7ac\uc815\ub82c\ub418\uc5
 confirm.rearrangephotos=\uc0ac\uc9c4\ub4e4\uc774 \uc7ac\uc815\ub82c\ub418\uc5c8\uc5b4\uc694.
 confirm.cutandmove=\uc62e\uaca8\uc84c\uc5b4\uc694.
 confirm.convertnamestotimes=\uacbd\uc720\uc9c0 \uc774\ub984\ub4e4\uc774 \ubcc0\ud658\ub418\uc5c8\uc5b4\uc694.
-confirm.saveexif.ok1=\uc800\uc7a5\ub428
-confirm.saveexif.ok2=\uc0ac\uc9c4 \ud30c\uc77c\ub4e4
+confirm.saveexif.ok=\uc800\uc7a5\ub428 %d \uc0ac\uc9c4 \ud30c\uc77c\ub4e4
 confirm.undo.single=\uac1c \uc2e4\ud589\ucde8\uc18c \ub3d9\uc791
 confirm.undo.multi=\uac1c \uc2e4\ud589\ucde8\uc18c \ub3d9\uc791
 confirm.jpegload.single=\uc0ac\uc9c4\uc774 \ucd94\uac00\ub428
@@ -468,13 +465,16 @@ confirm.correlatephotos.multi=\uc0ac\uc9c4\ub4e4 \uc5f0\uacb0\ub428
 confirm.createpoint=\uc9c0\uc810 \uc0dd\uc131\ub428
 confirm.rotatephoto=\uc0ac\uc9c4 \ub3cc\ub824\uc9d0
 confirm.running=\uc2e4\ud589\uc911
-confirm.lookupsrtm1=
-confirm.lookupsrtm2=\uace0\ub3c4\uac12 \ubc1c\uacac
+confirm.lookupsrtm=%d \uace0\ub3c4\uac12 \ubc1c\uacac
 confirm.deletefieldvalues=\ud544\ub4dc\uac12 \uc9c0\uc6cc\uc9d0
 confirm.audioload=\uc18c\ub9ac\ud30c\uc77c \ucd94\uac00\ub428
 confirm.correlateaudios.single=\uc18c\ub9ac \uc5f0\uacb0\ub428
 confirm.correlateaudios.multi=\uc18c\ub9ac\ub4e4 \uc5f0\uacb0\ub428
 
+# Tips
+tip.title=\ub3c4\uc6c0
+tip.manuallycorrelateone=\ucd5c\uc18c\ud55c \ud55c\uac1c\uc758 \uc544\uc774\ud0ec(\uc0ac\uc9c4,\uc18c\ub9ac)\uc744 \uc9c1\uc811 \uc5f0\uacb0\ud558\uba74, \ud0c0\uc784 \uc624\ud504\uc14b\uc744 \uacc4\uc0b0\ud560 \uc218 \uc788\uc5b4\uc694.
+
 # Buttons
 button.ok=\ud655\uc778
 button.back=\ub4a4\ub85c
@@ -574,7 +574,6 @@ fieldname.newsegment=\ubd80\ubd84
 fieldname.custom=\ucee4\uc2a4\ud140
 fieldname.prefix=\ud544\ub4dc
 fieldname.distance=\uac70\ub9ac
-fieldname.movingdistance=\uc6c0\uc9c0\uc778\uac70\ub9ac
 fieldname.duration=\uae30\uac04
 fieldname.speed=\uc18d\ub3c4
 fieldname.verticalspeed=\uc218\uc9c1\uc18d\ub3c4
@@ -645,10 +644,8 @@ error.save.failed=\ud30c\uc77c\uc5d0 \uc790\ub8cc\uc800\uc7a5 \uc2e4\ud328
 error.saveexif.filenotfound=\uc0ac\uc9c4\ud30c\uc77c \ucc3e\uae30 \uc2e4\ud328
 error.saveexif.cannotoverwrite1=\uc0ac\uc9c4\ud30c\uc77c
 error.saveexif.cannotoverwrite2=\uc774 \uc77d\uae30\uc804\uc6a9\uc774\ub124\uc694, \ub36e\uc5b4\uc4f8\uc218 \uc5c6\uc5b4\uc694 \ubcf5\uc0ac\ud574\uc11c \uc4f8\uae4c\uc694?
-error.saveexif.failed1=
-error.saveexif.failed2=\uc774\ubbf8\uc9c0 \uc800\uc7a5 \uc2e4\ud328
-error.saveexif.forced1=
-error.saveexif.forced2=\uc774\ubbf8\uc9c0\uac00 \uac15\uc81c\ub97c \uc694\uad6c\ud569\ub2c8\ub2e4.
+error.saveexif.failed=%d \uc774\ubbf8\uc9c0 \uc800\uc7a5 \uc2e4\ud328
+error.saveexif.forced=%d \uc774\ubbf8\uc9c0\uac00 \uac15\uc81c\ub97c \uc694\uad6c\ud569\ub2c8\ub2e4.
 error.load.dialogtitle=\uc790\ub8cc \ubd88\ub7ec\uc624\ub2e4\uac00 \uc5d0\ub7ec
 error.load.noread=\ud30c\uc77c\uc744 \uc77d\uc744 \uc218 \uc5c6\ub124\uc694.
 error.load.nopoints=\ud30c\uc77c\uc5d0 \uc88c\ud45c \uc815\ubcf4\uac00 \uc5c6\uc5b4\uc694.
@@ -659,7 +656,7 @@ error.jpegload.dialogtitle=\uc0ac\uc9c4 \ubd88\ub7ec\uc624\ub294 \uc911 \uc5d0\u
 error.jpegload.nofilesfound=\ucc3e\uc740 \ud30c\uc77c \uc5c6\uc74c.
 error.jpegload.nojpegsfound=\ucc3e\uc740 jpeg\ud30c\uc77c \uc5c6\uc74c.
 error.jpegload.nogpsfound=GPS \uc815\ubcf4\ub97c \ucc3e\uc9c0 \ubabb\ud568.
-error.jpegload.exifreadfailed=EXIF\uc815\ubcf4 \uc77d\uae30 \uc2e4\ud328./n\ub0b4\uc7a5\uc774\ub098 \uc678\uc7a5 \ub77c\uc774\ube0c\ub7ec\uc774\uac00 \uc5c6\uc73c\uba74/nEXIF\uc815\ubcf4\ub97c \uc77d\uc744 \uc218 \uc5c6\uc5b4\uc694.
+error.jpegload.exifreadfailed=Exif\uc815\ubcf4 \uc77d\uae30 \uc2e4\ud328./n\ub0b4\uc7a5\uc774\ub098 \uc678\uc7a5 \ub77c\uc774\ube0c\ub7ec\uc774\uac00 \uc5c6\uc73c\uba74/nExif\uc815\ubcf4\ub97c \uc77d\uc744 \uc218 \uc5c6\uc5b4\uc694.
 error.audioload.nofilesfound=\ucc3e\uc740 \uc18c\ub9ac\ud30c\uc77c \uc5c6\uc74c.
 error.gpsload.unknown=\uc54c\ub824\uc9c0\uc9c0 \uc54a\uc740 \uc624\ub958.
 error.undofailed.title=\ub418\ub3cc\ub9ac\uae30 \uc2e4\ud328.
diff --git a/tim/prune/lang/prune-texts_nl.properties b/tim/prune/lang/prune-texts_nl.properties
index 8915ccb..ac4b797 100644
--- a/tim/prune/lang/prune-texts_nl.properties
+++ b/tim/prune/lang/prune-texts_nl.properties
@@ -7,6 +7,7 @@ menu.file.addphotos=Foto's toevoegen
 menu.file.recentfiles=Onlangs geopend
 menu.file.save=Opslaan als tekst
 menu.file.exit=Afsluiten
+menu.online=Online
 menu.track=Route
 menu.track.undo=Ongedaan maken
 menu.track.clearundo=Ongedaan-maken lijst wissen
@@ -57,6 +58,7 @@ menu.map.editmode=Wijzigen
 
 # Alt keys for menus
 altkey.menu.file=F
+altkey.menu.online=O
 altkey.menu.track=R
 altkey.menu.range=E
 altkey.menu.point=P
@@ -104,9 +106,12 @@ function.estimatetime=Geschatte tijd
 function.learnestimationparams=Parameters voor geschatte tijd
 function.setmapbg=Instellen kaart achtergrond
 function.setpaths=Instellen programmapaden
+function.splitsegments=Splits route in segmenten
+function.sewsegments=Voeg segmenten samen
 function.getgpsies=Routes van Gpsies ophalen
 function.uploadgpsies=Upload routes naar Gpsies
 function.lookupsrtm=Hoogtes van SRTM ophalen
+function.downloadsrtm=Downloaden SRTM tegels
 function.getwikipedia=Wikipedia artikelen uit de buurt ophalen
 function.searchwikipedianames=Wikipedia zoeken op naam
 function.downloadosm=Downloaden OSM data voor gebied
@@ -135,6 +140,7 @@ function.checkversion=Controleer op nieuwe versie
 function.saveconfig=Instellingen opslaan
 function.diskcache=Kaart opslaan op schijf
 function.managetilecache=Beheer tegelcache
+function.getweatherforecast=Ophalen weersvoorspelling
 
 # Dialogs
 dialog.exit.confirm.title=GpsPrune afsluiten
@@ -242,6 +248,8 @@ dialog.exportpov.modelstyle=Model stijl
 dialog.exportpov.ballsandsticks=Balletjes en stokjes
 dialog.exportpov.tubesandwalls=Buizen en muren
 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.3d.useterrain=Toon reli\u00ebf
+dialog.3d.terraingridsize=Rasterformaat
 dialog.exportpov.baseimage=Basisafbeelding
 dialog.exportpov.cannotmakebaseimage=Kan basisafbeelding niet opslaan
 dialog.baseimage.title=Basisafbeelding
@@ -257,6 +265,7 @@ 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.drawtrackpoints=Teken routepunten
 dialog.exportimage.textscalepercent=Tekstschaal factor (%)
 dialog.pointtype.desc=Sla de volgende punttypen op:
 dialog.pointtype.track=Routepunten
@@ -378,7 +387,6 @@ dialog.correlate.photoselect.intro=Selecteer \u00e9\u00e9n van deze gekoppelde f
 dialog.correlate.select.photoname=Fotonaam
 dialog.correlate.select.timediff=Tijdsverschil
 dialog.correlate.select.photolater=Foto later
-dialog.correlate.options.tip=Tip: Door handmatig een foto te koppelen kan het tijdsverschil voor u berekend worden.
 dialog.correlate.options.intro=Selecteer de opties voor automatisch koppelen
 dialog.correlate.options.offsetpanel=Tijdverschil
 dialog.correlate.options.offset=Verschil
@@ -421,14 +429,13 @@ dialog.compress.duplicates.title=Verwijderen duplicaten
 dialog.compress.douglaspeucker.title=Douglas-Peucker compressie
 dialog.compress.douglaspeucker.paramdesc=Span factor
 dialog.compress.summarylabel=Te verwijderen punten
-dialog.compress.confirm1=Er zijn
-dialog.compress.confirm2=punten zijn gemarkeerd.\nGebruik Track - Verwijder gemarkeerde punten om ze te verwijderen
+dialog.compress.confirm=Er zijn %d punten zijn gemarkeerd.\nVerwijder gemarkeerde punten?
 dialog.compress.confirmnone=er zijn geen punten gemarkeerd
 dialog.deletemarked.nonefound=Er konden geen punten verwijderd worden
 dialog.pastecoordinates.desc=Geef co\u00f6rdinaten in
 dialog.pastecoordinates.coords=Co\u00f6rdinaten
 dialog.pastecoordinates.nothingfound=Controleer de co\u00f6rdinaten en probeer het nogmaals
-dialog.help.help=Ga naar\n http://activityworkshop.net/software/gpsprune/\nvoor meer informatie en handleidingen.
+dialog.help.help=Ga naar\n http://gpsprune.activityworkshop.net/\nvoor meer informatie en handleidingen.
 dialog.about.version=Versie
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune is een programma voor het laden, tonen en wijzigen van data uit GPS ontvangers.
@@ -467,7 +474,7 @@ dialog.checkversion.newversion1=Een nieuwe versie van GpsPrune is beschikbaar. D
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Deze nieuwe versie was vrijgegeven op
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Om de nieuwst versie te downloaden, ga naar http://activityworkshop.net/software/gpsprune/download.html.
+dialog.checkversion.download=Om de nieuwst versie te downloaden, ga naar http://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=De volgende sneltoetsen vervangen muisacties
 dialog.keys.keylist=<table><tr><td>Pijltjestoetsen</td><td>verschuif de kaart links, rechts, omhoog, omlaag</td></tr><tr><td>Ctrl + pijltje naar links, rechts</td><td>Selecteer volgende, vorige punt</td></tr><tr><td>Ctrl + pijltje omhoog, omlaag</td><td>Zoom in of uit</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>Selecteer vorig, volgend segment</td></tr><tr><td>Ctrl + Home, End</td><td>Select eerste, laatste punt</td></tr><tr><td>Del</td><td>Verwijder huidige punt</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -530,13 +537,32 @@ dialog.diskcache.tileset.multiple=meerdere
 dialog.diskcache.deleteold=Verwijder oude tegels
 dialog.diskcache.maximumage=Maximum leeftijd (dagen)
 dialog.diskcache.deleteall=Verwijder alle tegels
-dialog.diskcache.deleted1=
-dialog.diskcache.deleted2=bestanden uit de cache verwijderd
+dialog.diskcache.deleted=%d bestanden uit de cache verwijderd
 dialog.deletefieldvalues.intro=Selecteer het te verwijderen veld voor de huidige reeks
 dialog.deletefieldvalues.nofields=Er zijn geen velden in deze reeks om te verwijderen
 dialog.setlinewidth.text=Geef lijndikte voor routes (1-4)
 dialog.downloadosm.desc=Bevestig het downloaden van ruwe OSM data voor dit gebied:
 dialog.searchwikipedianames.search=Zoeken naar:
+dialog.weather.location=Locatie
+dialog.weather.update=Verwachting bijgewerkt
+dialog.weather.sunrise=Zonsopkomst
+dialog.weather.sunset=Zonsondergang
+dialog.weather.temperatureunits=Temperaturen
+dialog.weather.currentforecast=Huidig weer
+dialog.weather.dailyforecast=Dagelijkse voorspelling
+dialog.weather.3hourlyforecast=3-uurse voorspelling
+dialog.weather.day.now=Huidig weer
+dialog.weather.day.today=Vandaag
+dialog.weather.day.tomorrow=Morgen
+dialog.weather.day.monday=Maandag
+dialog.weather.day.tuesday=Dinsdag
+dialog.weather.day.wednesday=Woensdag
+dialog.weather.day.thursday=Donderdag
+dialog.weather.day.friday=Vrijdag
+dialog.weather.day.saturday=Zaterdag
+dialog.weather.day.sunday=Zondag
+dialog.weather.humidity=Luchtvocht.
+dialog.weather.creditnotice=Deze gegevens worden beschikbaar gesteld door openweathermap.org. Hun website heeft meer details.
 
 # 3d window
 dialog.3d.title=GpsPrune in 3D
@@ -555,11 +581,12 @@ confirm.addtimeoffset=Tijdsverschil toegevoegd
 confirm.addaltitudeoffset=Hoogteverschil toegevoegd
 confirm.rearrangewaypoints=Waypoints herschikt
 confirm.rearrangephotos=Foto herschikt
+confirm.splitsegments=Er zijn %d opdelingen gemaakt
+confirm.sewsegments=Er zijn %d samenvoegingen gemaakt
 confirm.cutandmove=Selectie verplaatst
 confirm.interpolate=Punten toegevoegd
 confirm.convertnamestotimes=Namen waypoint geconverteerd
-confirm.saveexif.ok1=Opgeslagen
-confirm.saveexif.ok2=foto bestanden
+confirm.saveexif.ok=Opgeslagen %d foto bestanden
 confirm.undo.single=Actie geannuleerd
 confirm.undo.multi=Acties geannuleerd
 confirm.jpegload.single=foto was toegevoegd
@@ -573,13 +600,23 @@ confirm.correlatephotos.multi=Foto's waren gecorreleerd
 confirm.createpoint=punt aangemaakt
 confirm.rotatephoto=foto geroteerd
 confirm.running=Bezig...
-confirm.lookupsrtm1=Gevonden
-confirm.lookupsrtm2=hoote waarden
+confirm.lookupsrtm=Gevonden %d hoote waarden
+confirm.downloadsrtm=Er zijn %d bestanden gedownload nar de cache
+confirm.downloadsrtm.1=Er zijn %d bestanden gedownload nar de cache
+confirm.downloadsrtm.none=Geen bestanden gedownload, waren al aanwezig in de cache.
 confirm.deletefieldvalues=Veldwaarden gewist
 confirm.audioload=Audiobestanden toegevoegd
 confirm.correlateaudios.single=audiobestand gecorreleerd
 confirm.correlateaudios.multi=audiobestanden gecorreleerd
 
+# Tips, shown just once when appropriate
+tip.title=Tip
+tip.useamapcache=Door het instellen van een schijfcache (Instellingen -> Kaart opslaan op schijf<span style="color: #ff0000;">)\nkan je de afbeeldsnelheid verbeteren en het netwerkverkeer verminderen.</span>
+tip.learntimeparams=De resultaten zullen nauwkeuriger zijn als je \nRoute -> Parameters voor geschatte tijd\ngebruikt op je opgenomen routes.
+tip.downloadsrtm=Je kan dit versnellen door hier\nOnline -> Download SRTM tegels\nde data in je kaartcache op te slaan.
+tip.usesrtmfor3d=Deze route heeft geen hoogten.\nJe kan de SRTM functies gebruiken om een geschatte hoogte\nop te halen voor het 3d beeld.
+tip.manuallycorrelateone=Door handmatig een foto te koppelen kan het tijdsverschil voor u berekend worden.
+
 # Buttons
 button.ok=OK
 button.back=Terug
@@ -597,6 +634,7 @@ button.yes=Ja
 button.no=Nee
 button.yestoall=Ja op alles
 button.notoall=Nee op alles
+button.always=Altijd
 button.select=Selecteer
 button.selectall=Selecteer alles
 button.selectnone=Selecteer niets
@@ -685,7 +723,6 @@ fieldname.newsegment=Segment
 fieldname.custom=Custom
 fieldname.prefix=Veld
 fieldname.distance=Afstand
-fieldname.movingdistance=Snelheid in beweging
 fieldname.duration=Duur
 fieldname.speed=Snelheid
 fieldname.verticalspeed=Verticale snelheid
@@ -720,6 +757,8 @@ units.degminsec=Grd-min-sec
 units.degmin=Grd-min
 units.deg=Graden
 units.iso8601=ISO 8601
+units.degreescelsius=Celsius
+units.degreesfahrenheit=Fahrenheit
 
 # How to combine conditions, such as filters
 logic.and=en
@@ -728,6 +767,7 @@ logic.or=of
 # External urls
 url.googlemaps=maps.google.nl
 wikipedia.lang=nl
+openweathermap.lang=nl
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -749,6 +789,8 @@ undo.deletemarked=verwijderen punten
 undo.insert=punten invoegen
 undo.reverse=reeks omkeren
 undo.mergetracksegments=samenvoegen route segmenten
+undo.splitsegments=opdelen routesegmenten
+undo.sewsegments=verbinden routesegmenten
 undo.addtimeoffset=tijdsverschil toevoegen
 undo.addaltitudeoffset=hoogteverschil toevoegen
 undo.rearrangewaypoints=herschikken waypoint
@@ -771,10 +813,8 @@ error.save.failed=Kon de gegevens niet naar bestand wegschrijven
 error.saveexif.filenotfound=Fotobestand niet gevonden
 error.saveexif.cannotoverwrite1=Fotobestand
 error.saveexif.cannotoverwrite2=is alleen-lezen en kan niet worden overschreven. Wegschrijven naar kopie?
-error.saveexif.failed1=Kon
-error.saveexif.failed2=van de foto's niet wegschrijven
-error.saveexif.forced1=
-error.saveexif.forced2=van de foto's hadden "forcing" nodig
+error.saveexif.failed=Kon %d van de foto's niet wegschrijven
+error.saveexif.forced=%d van de foto's hadden "forcing" nodig
 error.load.dialogtitle=Fout gegevens laden
 error.load.noread=Kan bestand niet lezen
 error.load.nopoints=Geen co\u00f6rdinaat informatie gevonden in bestand
@@ -785,7 +825,7 @@ error.jpegload.dialogtitle=Fout bij inlezen foto's
 error.jpegload.nofilesfound=Bestanden niet gevonden
 error.jpegload.nojpegsfound=Geen jpeg-bestanden gevonden
 error.jpegload.nogpsfound=Geen GPS informatie gevonden
-error.jpegload.exifreadfailed=Kon geen EXIF informatie inlezen. EXIF informatie kan niet worden gelezen\n zonder interne of externe bibliotheek.
+error.jpegload.exifreadfailed=Kon geen Exif informatie inlezen. Exif informatie kan niet worden gelezen\n zonder interne of externe bibliotheek.
 error.audioload.nofilesfound=Geen audiobestanden gevonden
 error.gpsload.unknown=Onbekende fout
 error.undofailed.title=Terugdraaien mislukt
@@ -811,3 +851,6 @@ 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.
+error.tracksplit.nosplit=Deze route kon niet opgedeeld worden
+error.downloadsrtm.nocache=De bestanden konden niet worden opgeslagen.\nControleer de schijfcache.
+error.sewsegments.nothingdone=Er konden geen segmenten worden samengevoegd.\nEr zijn nu %d segmenten in de route.
diff --git a/tim/prune/lang/prune-texts_pl.properties b/tim/prune/lang/prune-texts_pl.properties
index 339c395..212eb69 100644
--- a/tim/prune/lang/prune-texts_pl.properties
+++ b/tim/prune/lang/prune-texts_pl.properties
@@ -101,11 +101,13 @@ function.show3d=Poka\u017c model 3D
 function.distances=Odleg\u0142o\u015bci
 function.fullrangedetails=Wszystkie detale
 function.estimatetime=Przewidywany czas
+function.learnestimationparams=Skoryguj wsp\u00f3\u0142czynniki szacowania czasu
 function.setmapbg=Wybierz map\u0119 t\u0142a
 function.setpaths=Ustaw \u015bcie\u017cki do program\u00f3w
 function.getgpsies=Pobierz \u015bcie\u017cki z Gpsies
 function.uploadgpsies=Wy\u015blij \u015bcie\u017cki do Gpsies
 function.lookupsrtm=Pobierz wysoko\u015bci z SRTM
+function.downloadsrtm=Zapisz dane z SRTM
 function.getwikipedia=Szukaj w Wikipedii o okolicy
 function.searchwikipedianames=Szukaj nazwy w Wikipedii
 function.downloadosm=Za\u0142aduj dane obszaru z OSM
@@ -134,6 +136,7 @@ function.checkversion=Sprawd\u017a czy jest nowa wersja
 function.saveconfig=Zapisz ustawienia
 function.diskcache=Zapisz mapy na dysk
 function.managetilecache=Zarz\u0105dzaj keszem p\u0142ytek
+function.getweatherforecast=Pobierz prognoza pogody
 
 # Dialogs
 dialog.exit.confirm.title=Zako\u0144cz GpsPrune
@@ -162,6 +165,8 @@ 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.openoptions.vspeed.positiveup=Pr\u0119dko\u015b\u0107 wznoszenia
+dialog.openoptions.vspeed.positivedown=Pr\u0119dko\u015b\u0107 opadania
 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
@@ -197,7 +202,7 @@ 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.intro=Usu\u0144 punkty je\u015bli poprzednie punkty wystarczaj\u0105co blisko
 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
@@ -222,7 +227,7 @@ 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.exportkml.extendedkml=Standardowy KML ze znacznikami czasu
 dialog.exportgpx.name=Nazwa
 dialog.exportgpx.desc=Opis
 dialog.exportgpx.includetimestamps=Do\u0142\u0105cz znaczniki czasu
@@ -254,6 +259,7 @@ 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.drawtrackpoints=Rysuj punkty
 dialog.exportimage.textscalepercent=Wsp\u00f3\u0142czynnik skali tekstu (%)
 dialog.pointtype.desc=Zapisz punkty nast\u0119puj\u0105cych typ\u00f3w:
 dialog.pointtype.track=punkty \u015bcie\u017cki
@@ -322,14 +328,24 @@ dialog.fullrangedetails.colsegments=Bez luk
 dialog.estimatetime.details=Szczeg\u00f3\u0142y
 dialog.estimatetime.gentle=\u0141agodnie
 dialog.estimatetime.steep=Stromo
+dialog.estimatetime.climb=Wznoszenie
+dialog.estimatetime.descent=Opadanie
 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.estimatetime.error.nodistance=Aby obliczy\u0107 wsp\u00f3\u0142czynniki szacowania czasu punkty bie\u017c\u0105cej \u015bcie\u017cki musz\u0105 posiada\u0107 informacje o odleg\u0142o\u015bci
+dialog.estimatetime.error.noaltitudes=Bie\u017c\u0105ce zaznaczenie nie zawiera informacji o wysoko\u015bci
+dialog.learnestimationparams.intro=Wsp\u00f3\u0142czynniki obliczone na podstawie bie\u017c\u0105cej \u015bcie\u017cki
 dialog.learnestimationparams.averageerror=B\u0142\u0105d \u015bredni
+dialog.learnestimationparams.combine=Po\u0142\u0105cz powy\u017csze wsp\u00f3\u0142czynniki z ich warto\u015bciami bie\u017c\u0105cymi
+dialog.learnestimationparams.combinedresults=Nowe wsp\u00f3\u0142czynniki
+dialog.learnestimationparams.weight.100pccurrent=Zatrzymaj warto\u015bci bie\u017c\u0105ce
 dialog.learnestimationparams.weight.current=bie\u017c\u0105ce
 dialog.learnestimationparams.weight.calculated=obliczone
+dialog.learnestimationparams.weight.50pc=\u015arednia warto\u015bci bie\u017c\u0105cych i obliczonych
+dialog.learnestimationparams.weight.100pccalculated=U\u017cyj nowe warto\u015bci 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
@@ -365,7 +381,6 @@ dialog.correlate.photoselect.intro=Wybierz jedno z powi\u0105zanych zdj\u0119\u0
 dialog.correlate.select.photoname=Nazwa zdj\u0119cia
 dialog.correlate.select.timediff=R\u00f3\u017cnica czasowa
 dialog.correlate.select.photolater=P\u00f3\u017aniejsze zdj\u0119cie
-dialog.correlate.options.tip=Porada: Gdy powi\u0105\u017cesz r\u0119cznie przynajmniej jedno zdj\u0119cie, r\u00f3\u017cnica czasowa zostanie policzona automatycznie.
 dialog.correlate.options.intro=Wybierz opcje dla automatycznej korelacji
 dialog.correlate.options.offsetpanel=Przesuni\u0119cie czasowe
 dialog.correlate.options.offset=Przesuni\u0119cie
@@ -408,8 +423,7 @@ dialog.compress.duplicates.title=Usuwanie duplikat\u00f3w
 dialog.compress.douglaspeucker.title=kompresja Douglasa-Peuckera
 dialog.compress.douglaspeucker.paramdesc=wsp\u00f3\u0142czynnik rozpi\u0119to\u015bci (szeroko\u015bci korytarza)
 dialog.compress.summarylabel=Punkty do usuni\u0119cia
-dialog.compress.confirm1=
-dialog.compress.confirm2=punkt\u00f3w zosta\u0142o zaznaczonych\nU\u017cyj \u015acie\u017cka->Usu\u0144 zaznaczone punkty, by je usun\u0105\u0107
+dialog.compress.confirm=%d punkt\u00f3w zosta\u0142o zaznaczonych\nUsu\u0144 zaznaczone punkty?
 dialog.compress.confirmnone=\u017cadne punkty nie zosta\u0142y zaznaczone
 dialog.deletemarked.nonefound=Nie mo\u017cna usun\u0105\u0107 \u017cadnych punkt\u00f3w
 dialog.pastecoordinates.desc=Wprowad\u017a lub wklej wsp\u00f3\u0142rz\u0119dne
@@ -454,7 +468,7 @@ dialog.checkversion.newversion1=Dost\u0119pna jest nowa wersja GpsPrune! Najnows
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Nowa wersja zosta\u0142a opublikowana
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Aby \u015bci\u0105gn\u0105\u0107 now\u0105 wersj\u0119 przejd\u017a na http://activityworkshop.net/software/gpsprune/download.html.
+dialog.checkversion.download=Aby \u015bci\u0105gn\u0105\u0107 now\u0105 wersj\u0119 przejd\u017a na http://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=U\u017cuwaj nast\u0119puj\u0105cych klawiszy skr\u00f3t\u00f3w zamiast myszki
 dialog.keys.keylist=<table><tr><td>klawisze strza\u0142ek</td><td>Przesuwa map\u0119 w lewo, w prawo, w g\u00f3r\u0119, w d\u00f3\u0142</td></tr><tr><td>Ctrl + lewa, prawa strza\u0142ka</td><td>Wybierz punkt poprzedni lub nast\u0119pny</td></tr><tr><td>Ctrl + strza\u0142ka w g\u00f3r\u0119, w d\u00f3\u0142</td><td>Powi\u0119ksz, pomniejsz</td></tr><tr><td>Del</td><td>Usun bie\u017c\u0105cy punkt</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -517,13 +531,22 @@ dialog.diskcache.tileset.multiple=wiele
 dialog.diskcache.deleteold=Usu\u0144 stare p\u0142ytki
 dialog.diskcache.maximumage=Maksymalny wiek (w dniach)
 dialog.diskcache.deleteall=Usu\u0144 wszystkie p\u0142ytki
-dialog.diskcache.deleted1=Usuni\u0119to
-dialog.diskcache.deleted2=plik\u00f3w z kesza
+dialog.diskcache.deleted=Usuni\u0119to %d plik\u00f3w z kesza
 dialog.deletefieldvalues.intro=Wybierz pola do skasowania z wybranego zakresu
 dialog.deletefieldvalues.nofields=Brak p\u00f3l do skasowania dla tego zakresu
 dialog.setlinewidth.text=Wprowad\u017a grubo\u015b\u0107 linii do rysowania \u015bcie\u017cek
 dialog.downloadosm.desc=Potwierd\u017a \u015bci\u0105gni\u0119cie danych dla tego obszaru z OSM:
 dialog.searchwikipedianames.search=Szukaj
+dialog.weather.day.now=Aktualny
+dialog.weather.day.today=Dzisiaj
+dialog.weather.day.tomorrow=Jutro
+dialog.weather.day.monday=Poniedzia\u0142ek
+dialog.weather.day.tuesday=Wtorek
+dialog.weather.day.wednesday=\u015Aroda
+dialog.weather.day.thursday=Czwartek
+dialog.weather.day.friday=Pi\u0105tek
+dialog.weather.day.saturday=Sobota
+dialog.weather.day.sunday=Niedziela
 
 # 3d window
 dialog.3d.title=GpsPrune widok tr\u00f3jwymiarowy
@@ -545,8 +568,7 @@ confirm.rearrangephotos=Zmieniono kolejno\u015b\u0107 zdj\u0119\u0107
 confirm.cutandmove=Przesuni\u0119to zaznaczenie
 confirm.interpolate=Dodano punkty
 confirm.convertnamestotimes=Zmieniono nazwy punkt\u00f3w po\u015brednich
-confirm.saveexif.ok1=Zapisano
-confirm.saveexif.ok2=plik(\u00f3w) zdj\u0119\u0107
+confirm.saveexif.ok=Zapisano %d plik(\u00f3w) zdj\u0119\u0107
 confirm.undo.single=cofni\u0119to operacj\u0119
 confirm.undo.multi=operacje zosta\u0142y cofni\u0119te
 confirm.jpegload.single=dodano zdj\u0119cie
@@ -560,13 +582,16 @@ confirm.correlatephotos.multi=zdj\u0119cia zosta\u0142y po\u0142\u0105czone
 confirm.createpoint=stworzono punkt
 confirm.rotatephoto=obr\u00f3cono zdj\u0119cie
 confirm.running=Przetwarzam dane ...
-confirm.lookupsrtm1=Znaleziono
-confirm.lookupsrtm2=warto\u015bci wysoko\u015bci
+confirm.lookupsrtm=Znaleziono %d warto\u015bci wysoko\u015bci
 confirm.deletefieldvalues=Warto\u015bci p\u00f3l usuni\u0119to
 confirm.audioload=dodano pliki audio
 confirm.correlateaudios.single=audio zosta\u0142o po\u0142\u0105czone
 confirm.correlateaudios.multi=audio zosta\u0142y po\u0142\u0105czone
 
+# Tips
+tip.title=Porada
+tip.manuallycorrelateone=Gdy powi\u0105\u017cesz r\u0119cznie przynajmniej jedno zdj\u0119cie, r\u00f3\u017cnica czasowa zostanie policzona automatycznie.
+
 # Buttons
 button.ok=OK
 button.back=Poprzedni
@@ -584,6 +609,7 @@ button.yes=Tak
 button.no=Nie
 button.yestoall=Tak na wszystko
 button.notoall=Nie na wszystko
+button.always=Zawsze
 button.select=Zaznacz
 button.selectall=Zaznacz wszystko
 button.selectnone=Odznacz
@@ -672,7 +698,6 @@ fieldname.newsegment=Odcinek
 fieldname.custom=U\u017cytkownika
 fieldname.prefix=Pole
 fieldname.distance=Odleg\u0142o\u015b\u0107
-fieldname.movingdistance=Odleg\u0142o\u015b\u0107 przesuni\u0119cia
 fieldname.duration=Czas trwania
 fieldname.speed=Pr\u0119dko\u015b\u0107
 fieldname.verticalspeed=Pr\u0119dko\u015b\u0107 pionowa
@@ -707,6 +732,10 @@ units.degminsec=Sto-min-sek
 units.degmin=Sto-min
 units.deg=Stopnie
 units.iso8601=ISO 8601
+units.degreescelsius=Celsjusza
+units.degreescelsius.short=\u00b0C
+units.degreesfahrenheit=Fahrenheita
+units.degreesfahrenheit.short=\u00b0F
 
 # How to combine conditions, such as filters
 logic.and=i
@@ -715,6 +744,7 @@ logic.or=lub
 # External urls
 url.googlemaps=maps.google.pl
 wikipedia.lang=pl
+openweathermap.lang=pl
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -758,10 +788,8 @@ error.save.failed=Nie powi\u00f3d\u0142 si\u0119 zapis danych do pliku
 error.saveexif.filenotfound=Nie znaleziono pliku ze zdj\u0119ciem
 error.saveexif.cannotoverwrite1=Plik ze zdj\u0119ciem
 error.saveexif.cannotoverwrite2=jest w trybie tylko do odczytu i nie mo\u017ce zosta\u0107 nadpisany. Zapisa\u0107 kopi\u0119?
-error.saveexif.failed1=B\u0142\u0105d zapisu
-error.saveexif.failed2=zdj\u0119\u0107
-error.saveexif.forced1=
-error.saveexif.forced2=zdj\u0119\u0107 z wymuszonym zapisem
+error.saveexif.failed=B\u0142\u0105d zapisu %d zdj\u0119\u0107
+error.saveexif.forced=%d zdj\u0119\u0107 z wymuszonym zapisem
 error.load.dialogtitle=B\u0142\u0105d \u0142adowania danych
 error.load.noread=Nie mo\u017cna przeczyta\u0107 pliku
 error.load.nopoints=Nie znaleziono informacji o wsp\u00f3\u0142rz\u0119dnych w pliku
@@ -772,7 +800,7 @@ error.jpegload.dialogtitle=B\u0142\u0105d \u0142adowania zdj\u0119cia
 error.jpegload.nofilesfound=Nie znaleziono plik\u00f3w
 error.jpegload.nojpegsfound=Nie znaleziono plik\u00f3w jpeg
 error.jpegload.nogpsfound=Nie znaleziono informacji GPS
-error.jpegload.exifreadfailed=Nie powiod\u0142o si\u0119 odczytanie informacji EXIF\nInformacji tych nie mo\u017cna przeczyta\u0107 bez wewn\u0119trznej lub zewn\u0119trznej biblioteki.
+error.jpegload.exifreadfailed=Nie powiod\u0142o si\u0119 odczytanie informacji Exif\nInformacji tych nie mo\u017cna przeczyta\u0107 bez wewn\u0119trznej lub zewn\u0119trznej biblioteki.
 error.audioload.nofilesfound=Nie znaleziono plik\u00f3w audio
 error.gpsload.unknown=Nieznany b\u0142\u0105d
 error.undofailed.title=Cofnij nie powiod\u0142o si\u0119
@@ -797,3 +825,4 @@ error.cache.notthere=Nie znaleziono katalogu kesza
 error.cache.empty=Katalog kesza jest pusty
 error.cache.cannotdelete=\u017badne p\u0142ytki nie mog\u0142y zosta\u0107 usuni\u0119te
 error.interpolate.invalidparameter=Ilo\u015b\u0107 punkt\u00f3w musi zawiera\u0107 si\u0119 w zakresie od 1 do 1000
+error.learnestimationparams.failed=Oszacowanie wsp\u00f3\u0142czynnik\u00f3w dla danej scie\u017cki nie powiod\u0142o si\u0119.\nSpr\u00f3buj za\u0142adowa\u0107 wi\u0119cej \u015bcie\u017cek.
diff --git a/tim/prune/lang/prune-texts_pt.properties b/tim/prune/lang/prune-texts_pt.properties
index fe7bdc2..50c6844 100644
--- a/tim/prune/lang/prune-texts_pt.properties
+++ b/tim/prune/lang/prune-texts_pt.properties
@@ -7,21 +7,21 @@ menu.file.addphotos=Adicionar fotos
 menu.file.recentfiles=Arquivos recentes
 menu.file.save=Salvar
 menu.file.exit=Sair
+menu.online=Online
 menu.track=Rota
 menu.track.undo=Desfazer
 menu.track.clearundo=Limpar lista de desfazer
+menu.track.markrectangle=Marcar pontos no ret\u00e2ngulo
 menu.track.deletemarked=Remover pontos marcados
 menu.track.rearrange=Rearrumar pontos
 menu.track.rearrange.start=Tudo para o in\u00edcio do arquivo
 menu.track.rearrange.end=Tudo para o fim do arquivo
 menu.track.rearrange.nearest=Cada um para o ponto da rota mais pr\u00f3ximo
 menu.range=Intervalo
-menu.range.all=Selectionar tudo
-menu.range.none=N\u00e3o selecionar nenhuns
+menu.range.all=Selecionar tudo
+menu.range.none=Desmarcar todas as sele\u00e7\u00f5es
 menu.range.start=Definir in\u00edcio do intervalo
 menu.range.end=Definir fim do intervalo
-function.deleterange=Remover intervalo
-function.interpolate=Interpolar pontos
 menu.range.average=Sele\u00e7\u00e3o m\u00e9dia
 menu.range.reverse=Reverter intervalo
 menu.range.mergetracksegments=Mesclar trechos da rota
@@ -36,7 +36,7 @@ menu.view=Exibir
 menu.view.showsidebars=Mostrar barras laterais
 menu.view.browser=Mapear no navegador
 menu.view.browser.google=Mapas do Google
-menu.view.browser.openstreetmap=Mapas do Openstreetmap
+menu.view.browser.openstreetmap=Mapas do OpenStreetMap
 menu.view.browser.mapquest=Mapas do Mapquest
 menu.view.browser.yahoo=Mapas do Yahoo
 menu.view.browser.bing=Mapas do Bing
@@ -54,9 +54,11 @@ menu.map.connect=Conectar pontos da rota
 menu.map.autopan=Auto-ajustar
 menu.map.showmap=Mostrar o mapa
 menu.map.showscalebar=Mostrar barra de escala
+menu.map.editmode=Modo de edi\u00e7\u00e3o
 
 # Alt keys for menus
 altkey.menu.file=A
+altkey.menu.online=N
 altkey.menu.track=R
 altkey.menu.range=I
 altkey.menu.point=P
@@ -84,8 +86,12 @@ function.exportkml=Exportar para KML
 function.exportgpx=Exportar para GPX
 function.exportpov=Exportar para POV
 function.exportsvg=Exportar para SVG
+function.exportimage=Exportar imagem
 function.editwaypointname=Editar nome do ponto
 function.compress=Comprimir rota
+function.deleterange=Remover intervalo
+function.croptrack=Cortar rota
+function.interpolate=Interpolar pontos
 function.addtimeoffset=Adicionar diferen\u00e7a de tempo
 function.addaltitudeoffset=Adicionar diferen\u00e7a de altitude
 function.convertnamestotimes=Converter nomes dos pontos para tempos
@@ -96,13 +102,18 @@ function.charts=Gr\u00e1ficos
 function.show3d=Visualizar 3D
 function.distances=Dist\u00e2ncias
 function.fullrangedetails=Todos os detalhes
+function.estimatetime=Tempo estimado
+function.learnestimationparams=Aprender os par\u00e2metros para estimativa de tempo
 function.setmapbg=Definir como fundo do mapa
 function.setpaths=Definir caminhos do programa
+function.splitsegments=Dividir rota em segmentos
+function.sewsegments=Reunir segmentos em rota
 function.getgpsies=Obter rotas Gpsies
 function.uploadgpsies=Enviar rotas para o Gpsies
 function.lookupsrtm=Obter altitudes a partir do SRTM
-function.getwikipedia=Obter artigos da Wikipedia das redondezas
-function.searchwikipedianames=Procurar na Wikipedia por nome
+function.downloadsrtm=Baixar arquivos SRTM
+function.getwikipedia=Obter artigos da Wikip\u00e9dia das redondezas
+function.searchwikipedianames=Procurar na Wikip\u00e9dia por nome
 function.downloadosm=Baixar dados OSM para a \u00e1rea
 function.duplicatepoint=Duplicar ponto
 function.setcolours=Definir cores
@@ -116,7 +127,7 @@ function.rearrangephotos=Rearrumar fotos
 function.rotatephotoleft=Roda foto \u00e0 esquerda
 function.rotatephotoright=Roda foto \u00e0 direita
 function.photopopup=Mostrar janela da foto
-function.ignoreexifthumb=Ignorar miniatura do exif
+function.ignoreexifthumb=Ignorar miniatura do Exif
 function.loadaudio=Adicionar arquivos de \u00e1udio
 function.removeaudio=Remover arquivo de \u00e1udio
 function.correlateaudios=Correlacionar \u00e1udios
@@ -129,16 +140,18 @@ function.checkversion=Verificar novas vers\u00f5es
 function.saveconfig=Salvar configura\u00e7\u00f5es
 function.diskcache=Salvar mapas para o disco
 function.managetilecache=Gerenciar cache de fundos
+function.getweatherforecast=Baixar a previs\u00e3o do tempo para a \u00e1rea atual
 
 # Dialogs
 dialog.exit.confirm.title=Sair do GpsPrune
-dialog.exit.confirm.text=Seus dados n\u00e3o foram salvos. Voc\u00ea tem certeza que deseja sair?
+dialog.exit.confirm.text=Seus dados n\u00e3o foram salvos. Voc\u00ea tem certeza de que deseja sair?
 dialog.openappend.title=Adicionar aos dados existentes
 dialog.openappend.text=Adicionar estes dados aos dados j\u00e1 carregados?
 dialog.deletepoint.title=Remover Ponto
 dialog.deletepoint.deletephoto=Remover foto anexada a este ponto?
 dialog.deletephoto.title=Remover Foto
 dialog.deletephoto.deletepoint=Remover ponto anexado a esta foto?
+dialog.deleteaudio.deletepoint=Remover ponto anexado a este clipe de \u00e1udio?
 dialog.openoptions.title=Op\u00e7\u00f5es de abertura
 dialog.openoptions.filesnippet=Extrair do arquivo
 dialog.load.table.field=Campo
@@ -154,6 +167,10 @@ dialog.openoptions.deliminfo.records=registros, com
 dialog.openoptions.deliminfo.fields=campos
 dialog.openoptions.deliminfo.norecords=Sem registros
 dialog.openoptions.altitudeunits=Unidades de altitude
+dialog.openoptions.speedunits=Unidades de velocidade
+dialog.openoptions.vertspeedunits=Unidades de velocidade vertical
+dialog.openoptions.vspeed.positiveup=Velocidades de subida positivas
+dialog.openoptions.vspeed.positivedown=Velocidades de descida positivas
 dialog.open.contentsdoubled=Este arquivo cont\u00e9m duas c\u00f3pias de cada ponto,\nsendo uma como ponto normal e uma como ponto de rota.
 dialog.selecttracks.intro=Selecione a rota ou rotas para carregar
 dialog.selecttracks.noname=Sem nome
@@ -171,6 +188,30 @@ dialog.gpsload.save=Salvar para arquivo
 dialog.gpssend.sendwaypoints=Enviar pontos
 dialog.gpssend.sendtracks=Enviar rotas
 dialog.gpssend.trackname=Nome da rota
+dialog.gpsbabel.filters=Filtros
+dialog.addfilter.title=Adicionar filtro
+dialog.gpsbabel.filter.discard=Descartar
+dialog.gpsbabel.filter.simplify=Simplificar
+dialog.gpsbabel.filter.distance=Dist\u00e2ncia
+dialog.gpsbabel.filter.interpolate=Interpolar
+dialog.gpsbabel.filter.discard.intro=Descartar pontos se
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=N\u00famero de sat\u00e9lites <
+dialog.gpsbabel.filter.discard.nofix=Ponto n\u00e3o possui fixo
+dialog.gpsbabel.filter.discard.unknownfix=Ponto possui fixo desconhecido
+dialog.gpsbabel.filter.simplify.intro=Remover pontos at\u00e9
+dialog.gpsbabel.filter.simplify.maxpoints=N\u00famero de pontos <
+dialog.gpsbabel.filter.simplify.maxerror=ou erro de dist\u00e2ncia <
+dialog.gpsbabel.filter.simplify.crosstrack=cruzamento de rota
+dialog.gpsbabel.filter.simplify.length=diferen\u00e7a de comprimento
+dialog.gpsbabel.filter.simplify.relative=relativo ao hdop
+dialog.gpsbabel.filter.distance.intro=Remover pontos se pr\u00f3ximos a qualquer ponto anterior
+dialog.gpsbabel.filter.distance.distance=Se dist\u00e2ncia <
+dialog.gpsbabel.filter.distance.time=e diferen\u00e7a de tempo <
+dialog.gpsbabel.filter.interpolate.intro=Adicionar pontos entre os pontos da rota
+dialog.gpsbabel.filter.interpolate.distance=Se dist\u00e2ncia >
+dialog.gpsbabel.filter.interpolate.time=ou diferen\u00e7a de tempo >
 dialog.saveoptions.title=Salvar arquivo
 dialog.save.fieldstosave=Campos a salvar
 dialog.save.table.field=Campo
@@ -181,13 +222,16 @@ dialog.save.coordinateunits=Unidades das coordenadas
 dialog.save.altitudeunits=Unidades da altitude
 dialog.save.timestampformat=Formato da data-hora
 dialog.save.overwrite.title=Arquivo j\u00e1 existe
-dialog.save.overwrite.text=Este arquivo j\u00e1 existe. Voc\u00ea tem certeza que deseja sobrescrev\u00ea-lo?
+dialog.save.overwrite.text=Este arquivo j\u00e1 existe. Voc\u00ea tem certeza de que deseja sobrescrev\u00ea-lo?
 dialog.save.notypesselected=Nenhum tipo de ponto foi selecionado
 dialog.exportkml.text=T\u00edtulo para os dados
 dialog.exportkml.altitude=Incluir altitudes (para avia\u00e7\u00e3o)
-dialog.exportkml.kmz=Comprimir para criar arquivo kmz
-dialog.exportkml.exportimages=Exportar miniaturas de imagem para o kmz
+dialog.exportkml.kmz=Comprimir para criar arquivo KMZ
+dialog.exportkml.exportimages=Exportar miniaturas de imagem para o KMZ
+dialog.exportkml.imagesize=Tamanho da imagem
 dialog.exportkml.trackcolour=Cor da rota
+dialog.exportkml.standardkml=KML plano
+dialog.exportkml.extendedkml=KML extendido com estampa de tempo
 dialog.exportgpx.name=Nome
 dialog.exportgpx.desc=Descri\u00e7\u00e3o
 dialog.exportgpx.includetimestamps=Incluir data-hora
@@ -203,12 +247,26 @@ 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.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.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 de que deseja continuar?
+dialog.3d.useterrain=Mostrar terreno
+dialog.3d.terraingridsize=Tamanho da grade
+dialog.exportpov.baseimage=Imagem base
+dialog.exportpov.cannotmakebaseimage=N\u00e3o foi poss\u00edvel gravar imagem base
+dialog.baseimage.title=Imagem do mapa
+dialog.baseimage.useimage=Usar imagem
+dialog.baseimage.mapsource=Fonte do mapa
+dialog.baseimage.zoom=N\u00edvel de amplia\u00e7\u00e3o
+dialog.baseimage.incomplete=Imagem incompleta
+dialog.baseimage.tiles=Ladrilhos
+dialog.baseimage.size=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
 dialog.exportsvg.gradients=Usar gradientes para sombreamento
+dialog.exportimage.noimagepossible=Imagens de mapa precisam estar em cache no disco para serem usados por uma exporta\u00e7\u00e3o.
+dialog.exportimage.drawtrack=Desenhar rota no mapa
+dialog.exportimage.drawtrackpoints=Desenhar pontos da rota
+dialog.exportimage.textscalepercent=Fator de escala do texto (%)
 dialog.pointtype.desc=Salvar os seguintes tipos de ponto:
 dialog.pointtype.track=Pontos de rotas
 dialog.pointtype.waypoint=Pontos
@@ -216,19 +274,21 @@ dialog.pointtype.photo=Pontos de foto
 dialog.pointtype.audio=Pontos de \u00e1udio
 dialog.pointtype.selection=Apenas sele\u00e7\u00e3o
 dialog.confirmreversetrack.title=Confirmar invers\u00e3o
-dialog.confirmreversetrack.text=Esta rota possui informa\u00e7\u00f5es de data-hora, as quais estar\u00e3o fora de sequ\u00eancia ap\u00f3s a revers\u00e3o.\n Voc\u00ea tem certeza que deseja reverter esta se\u00e7\u00e3o?
+dialog.confirmreversetrack.text=Esta rota possui informa\u00e7\u00f5es de data-hora, as quais estar\u00e3o fora de sequ\u00eancia ap\u00f3s a revers\u00e3o.\n Voc\u00ea tem certeza de que deseja reverter esta se\u00e7\u00e3o?
 dialog.confirmcutandmove.title=Confirmar cortar e mover
-dialog.confirmcutandmove.text=A rota cont\u00e9m informa\u00e7\u00f5es de data-hora, as quais estar\u00e3o fora de sequ\u00eancia ap\u00f3s o movimento.\n Voc\u00ea tem certeza que deseja mover esta se\u00e7\u00e3o?
+dialog.confirmcutandmove.text=A rota cont\u00e9m informa\u00e7\u00f5es de data-hora, as quais estar\u00e3o fora de sequ\u00eancia ap\u00f3s o movimento.\n Voc\u00ea tem certeza de que deseja mover esta se\u00e7\u00e3o?
 dialog.interpolate.parameter.text=N\u00famero de pontos para inserir entre os pontos selecionados
+dialog.interpolate.betweenwaypoints=Interpolar entre os pontos do caminho?
 dialog.undo.title=A\u00e7\u00e3o(\u00f5es) de desfazer
 dialog.undo.pretext=Por favor, selecione a a\u00e7\u00e3o(\u00f5es) a desfazer
 dialog.undo.none.title=N\u00e3o foi poss\u00edvel desfazer
 dialog.undo.none.text=Nenhuma opera\u00e7\u00e3o a desfazer!
 dialog.clearundo.title=Limpar lista de desfazer
-dialog.clearundo.text=Voc\u00ea tem certeza que deseja limpar a lista de desfazer?\n Todas as informa\u00e7\u00f5es para desfazer ser\u00e3o perdidas!
+dialog.clearundo.text=Voc\u00ea tem certeza de que deseja limpar a lista de desfazer?\n Todas as informa\u00e7\u00f5es para desfazer ser\u00e3o perdidas!
 dialog.pointedit.title=Editar ponto
 dialog.pointedit.intro=Selecionar cada campo para mudar o valor
 dialog.pointedit.table.field=Campo
+dialog.pointedit.nofield=Nenhum campo selecionado
 dialog.pointedit.table.value=Valor
 dialog.pointnameedit.name=Nome do ponto
 dialog.pointnameedit.uppercase=MAI\u00daSCULAS
@@ -269,6 +329,29 @@ dialog.distances.column.to=Para o ponto
 dialog.distances.currentpoint=Ponto atual
 dialog.distances.toofewpoints=Esta fun\u00e7\u00e3o precisa de pontos para calcular a dist\u00e3ncia entre eles
 dialog.fullrangedetails.intro=Aqui est\u00e3o os detalhes para o intervalo selecionado
+dialog.fullrangedetails.coltotal=Incluindo intervalos
+dialog.fullrangedetails.colsegments=Sem intervalos
+dialog.estimatetime.details=Detalhes
+dialog.estimatetime.gentle=Suave
+dialog.estimatetime.steep=\u00cdngreme
+dialog.estimatetime.climb=Subida
+dialog.estimatetime.descent=Descida
+dialog.estimatetime.parameters=Par\u00e2metros
+dialog.estimatetime.parameters.timefor=Tempo para
+dialog.estimatetime.results=Resultados
+dialog.estimatetime.results.estimatedtime=Tempo estimado
+dialog.estimatetime.results.actualtime=Tempo real
+dialog.estimatetime.error.nodistance=Para estimar o tempo \u00e9 necess\u00e1rio conectar os pontos da rota, para obter uma dist\u00e2ncia
+dialog.estimatetime.error.noaltitudes=A sele\u00e7\u00e3o n\u00e3o inclui nenhuma informa\u00e7\u00e3o de altitude
+dialog.learnestimationparams.intro=Estes s\u00e3o os par\u00e2metros calculados desta rota
+dialog.learnestimationparams.averageerror=Erro m\u00e9dio
+dialog.learnestimationparams.combine=Estes par\u00e2metros podem ser combinados com os valores atuais
+dialog.learnestimationparams.combinedresults=Resultados combinados
+dialog.learnestimationparams.weight.100pccurrent=Manter os valores atuais
+dialog.learnestimationparams.weight.current=atual
+dialog.learnestimationparams.weight.calculated=calculado
+dialog.learnestimationparams.weight.50pc=M\u00e9dia dos valores atuais e calculados
+dialog.learnestimationparams.weight.100pccalculated=Usar novos valores calculados
 dialog.setmapbg.intro=Selecione uma das fontes de mapas ou adicione uma nova
 dialog.addmapsource.title=Adicionar nova fonte de mapas
 dialog.addmapsource.sourcename=Nome da fonte
@@ -298,12 +381,12 @@ dialog.gpsies.activity.skating=Patina\u00e7\u00e3o
 dialog.wikipedia.column.name=Nome do artigo
 dialog.wikipedia.column.distance=Dist\u00e2ncia
 dialog.correlate.notimestamps=N\u00e3o existem data-hora nos dados dos pontos, assim n\u00e3o h\u00e1 nada para correlacionar com as fotos
-dialog.correlate.nouncorrelatedphotos=Existem fotos n\u00e3o correlacionadas.\nVoc\u00ea tem certeza que deseja continuar?
+dialog.correlate.nouncorrelatedphotos=Existem fotos n\u00e3o correlacionadas.\nVoc\u00ea tem certeza de que deseja continuar?
+dialog.correlate.nouncorrelatedaudios=Existem \u00e1udios n\u00e3o correlacionados.\nVoc\u00ea tem certeza de que deseja continuar?
 dialog.correlate.photoselect.intro=Selecione uma destas fotos correlacionadas para usar como diferen\u00e7a de tempo
 dialog.correlate.select.photoname=Nome da foto
 dialog.correlate.select.timediff=Diferen\u00e7a de tempo
 dialog.correlate.select.photolater=Foto \u00e9 mais recente
-dialog.correlate.options.tip=Dica: Correlacionando pelo menos uma foto manualmente, a diferen\u00e7a de tempo pode ser calculada para voc\u00ea.
 dialog.correlate.options.intro=Selecione as op\u00e7\u00f5es para correla\u00e7\u00e3o autom\u00e1tica
 dialog.correlate.options.offsetpanel=Diferen\u00e7a de tempo
 dialog.correlate.options.offset=Diferen\u00e7a
@@ -336,7 +419,6 @@ dialog.rearrangephotos.toend=Mover para o fim
 dialog.rearrangephotos.nosort=N\u00e3o ordenar
 dialog.rearrangephotos.sortbyfilename=Ordenar pelo nome do arquivo
 dialog.rearrangephotos.sortbytime=Ordenar pela hora
-dialog.deletemarked.nonefound=Nenhum dado dos pontos pode ser removido
 dialog.compress.closepoints.title=Remo\u00e7\u00e3o de ponto pr\u00f3ximo
 dialog.compress.closepoints.paramdesc=Fator de deslocamento
 dialog.compress.wackypoints.title=Remo\u00e7\u00e3o de ponto exc\u00eantrica
@@ -347,10 +429,13 @@ dialog.compress.duplicates.title=Remo\u00e7\u00e3o de duplicado
 dialog.compress.douglaspeucker.title=Compress\u00e3o Douglas-Peucker
 dialog.compress.douglaspeucker.paramdesc=Fator de deslocamento
 dialog.compress.summarylabel=Pontos para remover
+dialog.compress.confirm=%d pontos foram marcados.\nRemover estes pontos marcados agora?
+dialog.compress.confirmnone=nenhum ponto foi marcado
+dialog.deletemarked.nonefound=Nenhum dado dos pontos pode ser removido
 dialog.pastecoordinates.desc=Insira ou cole as coordenadas aqui
 dialog.pastecoordinates.coords=Coordenadas
 dialog.pastecoordinates.nothingfound=Por favor, verifique as coordenadas novamente
-dialog.help.help=Por favor, veja\n http://activityworkshop.net/software/gpsprune/\npara mais informa\u00e7\u00f5es e guia do usu\u00e1rio.
+dialog.help.help=Por favor, veja\n http://gpsprune.activityworkshop.net/\npara mais informa\u00e7\u00f5es e guia do usu\u00e1rio.
 dialog.about.version=Vers\u00e3o
 dialog.about.build=Compila\u00e7\u00e3o
 dialog.about.summarytext1=GpsPrune \u00e9 um programa para carregar, exibir e editar dados de receptores de GPS.
@@ -389,7 +474,7 @@ dialog.checkversion.newversion1=Uma nova vers\u00e3o do GpsPrune est\u00e1 dispo
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Esta nova vers\u00e3o foi lan\u00e7ada em
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Para baixar a nova vers\u00e3o, v\u00e1 para http://activityworkshop.net/software/gpsprune/download.html.
+dialog.checkversion.download=Para baixar a nova vers\u00e3o, v\u00e1 para http://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=Voc\u00ea pode usar os seguintes atalhos de teclado ao inv\u00e9s de usar o mouse
 dialog.keys.keylist=<table><tr><td>Cursores</td><td>Move o mapa para esquerda, direita, acima e abaixo</td></tr><tr><td>Ctrl + cursores esquerdo e direito</td><td>Seleciona o pr\u00f3ximo ponto ou o anterior</td></tr><tr><td>Ctrl + cursores acima e abaixo</td><td>Amplia ou reduz</td></tr><tr><td>Del</td><td>Remove o ponto atual</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -441,6 +526,7 @@ dialog.diskcache.save=Salvar imagens do mapa para o disco
 dialog.diskcache.dir=Diret\u00f3rio da cache
 dialog.diskcache.createdir=Criar diret\u00f3rio
 dialog.diskcache.nocreate=Diret\u00f3rio da cache n\u00e3o foi criado
+dialog.diskcache.cannotwrite=Ladrilhos de mapa n\u00e3o puderam ser salvos na pasta selecionada
 dialog.diskcache.table.path=Caminho
 dialog.diskcache.table.usedby=Usado por
 dialog.diskcache.table.zoom=Zoom
@@ -451,12 +537,34 @@ dialog.diskcache.tileset.multiple=m\u00faltiplos
 dialog.diskcache.deleteold=Apagar fundos antigos
 dialog.diskcache.maximumage=Idade m\u00e1xima (dias)
 dialog.diskcache.deleteall=Apagar todos os fundos
-dialog.diskcache.deleted1=Removidos
-dialog.diskcache.deleted2=arquivos do cache
+dialog.diskcache.deleted=Removidos %d arquivos do cache
 dialog.deletefieldvalues.intro=Selecione o campo a remover para o intervalo atual
+dialog.deletefieldvalues.nofields=N\u00e3o existem campos a remover para este intervalo
 dialog.setlinewidth.text=Insira a espessura das linhas para desenhar as rotas (1-4)
 dialog.downloadosm.desc=Confirmar a transfer\u00eancia de dados OSM brutos para a \u00e1rea especificada:
 dialog.searchwikipedianames.search=Procurar por:
+dialog.weather.location=Localiza\u00e7\u00e3o
+dialog.weather.update=Previs\u00e3o atualizada
+dialog.weather.sunrise=Nascer do sol
+dialog.weather.sunset=P\u00f4r do sol
+dialog.weather.temperatureunits=Temperaturas
+dialog.weather.currentforecast=Clima atual
+dialog.weather.dailyforecast=Previs\u00e3o para o dia
+dialog.weather.3hourlyforecast=Previs\u00e3o para tr\u00eas horas
+dialog.weather.day.now=Clima atual
+dialog.weather.day.today=Hoje
+dialog.weather.day.tomorrow=Amanh\u00e3
+dialog.weather.day.monday=Segunda
+dialog.weather.day.tuesday=Ter\u00e7a
+dialog.weather.day.wednesday=Quarta
+dialog.weather.day.thursday=Quinta
+dialog.weather.day.friday=Sexta
+dialog.weather.day.saturday=S\u00e1bado
+dialog.weather.day.sunday=Domingo
+dialog.weather.wind=Vento
+dialog.weather.temp=Temp
+dialog.weather.humidity=Umidade
+dialog.weather.creditnotice=Estes dados foram disponibilizados por openweathermap.org. A p\u00e1gina Web possui mais detalhes.
 
 # 3d window
 dialog.3d.title=Vista 3D do GpsPrune
@@ -475,10 +583,12 @@ confirm.addtimeoffset=Diferen\u00e7a de tempo adicionada
 confirm.addaltitudeoffset=Diferen\u00e7a de altitude adicionadas
 confirm.rearrangewaypoints=Pontos rearrumados
 confirm.rearrangephotos=Fotos rearrumadas
+confirm.splitsegments=%d divis\u00f5es de segmentos feitas
+confirm.sewsegments=%d reuni\u00f5es de segmentos feitas
 confirm.cutandmove=Sele\u00e7\u00e3o movida
+confirm.interpolate=Pontos adicionados
 confirm.convertnamestotimes=Nomes dos pontos convertidos
-confirm.saveexif.ok1=Salvo
-confirm.saveexif.ok2=arquivos de foto
+confirm.saveexif.ok=Salvo %d arquivos de foto
 confirm.undo.single=opera\u00e7\u00e3o desfeita
 confirm.undo.multi=opera\u00e7\u00f5es desfeitas
 confirm.jpegload.single=foto foi adicionada
@@ -492,13 +602,23 @@ confirm.correlatephotos.multi=fotos foram correlacionadas
 confirm.createpoint=ponto criado
 confirm.rotatephoto=foto rotacionada
 confirm.running=Rodando...
-confirm.lookupsrtm1=Encontrado
-confirm.lookupsrtm2=valores de altitude
+confirm.lookupsrtm=Encontrado %d valores de altitude
+confirm.downloadsrtm=%d arquivos baixados para a cache
+confirm.downloadsrtm.1=%d arquivo baixados para a cache
+confirm.downloadsrtm.none=Nenhum arquivo baixado, pois j\u00e1 est\u00e3o na cache.
 confirm.deletefieldvalues=Valores do campo removidos
 confirm.audioload=Arquivos de \u00e1udio adicionados
 confirm.correlateaudios.single=\u00e1udio foi correlacionado
 confirm.correlateaudios.multi=\u00e1udios foram correlacionados
 
+# Tips, shown just once when appropriate
+tip.title=Dica
+tip.useamapcache=Configurando a cache de disco (Configura\u00e7\u00f5es -> Salvar mapas para disco)\nvoc\u00ea pode acelerar a exibi\u00e7\u00e3o e reduzir o tr\u00e1fego de rede.
+tip.learntimeparams=Os resultados ser\u00e3o mais precisos se voc\u00ea usar\nRota -> Aprender os par\u00e2metros para estimativa de tempo\nde suas rotas gravadas.
+tip.downloadsrtm=Voc\u00ea pode acelerar chamando\nOnline -> Baixar ladrilhos SRTM\npara obter as altitudes\naproximadas para a vis\u00e3o 3D.
+tip.usesrtmfor3d=Esta rota n\u00e3o possui altitudes.\nVoc\u00ea pode usar as fun\u00e7\u00f5es SRTM para obter as altitudes\naproximadas para a vis\u00e3o 3D.
+tip.manuallycorrelateone=Correlacionando pelo menos uma foto manualmente, a diferen\u00e7a de tempo pode ser calculada para voc\u00ea.
+
 # Buttons
 button.ok=Ok
 button.back=Voltar
@@ -516,6 +636,7 @@ button.yes=Sim
 button.no=N\u00e3o
 button.yestoall=Sim para todos
 button.notoall=N\u00e3o para todos
+button.always=Sempre
 button.select=Selecionar
 button.selectall=Selecionar todos
 button.selectnone=Selecionar nenhum
@@ -530,6 +651,7 @@ button.browse=Navegar...
 button.addnew=Adicionar novo
 button.delete=Remover
 button.manage=Gerenciar
+button.combine=Combinar
 
 # File types
 filetype.txt=Arquivos TXT
@@ -540,12 +662,14 @@ filetype.kmz=Arquivos KMZ
 filetype.gpx=Arquivos GPX
 filetype.pov=Arquivos POV
 filetype.svg=Arquivos SVG
+filetype.png=Arquivos PNG
 filetype.audio=Arquivos MP3, OGG, WAV
 
 # Display components
 display.nodata=Nenhum dado carregado
 display.noaltitudes=Dados da rota n\u00e3o incluem altitudes
 display.notimestamps=Dados da rota n\u00e3o incluem data-hora
+display.novalues=Dados da rota n\u00e3o incluem valores para este campo
 details.trackdetails=Detalhes da track
 details.notrack=Nenhuma rota carrgeada
 details.track.points=Pontos
@@ -582,6 +706,7 @@ details.nophoto=Nenhuma foto selecionada
 details.photo.loading=Carregando
 details.photo.bearing=Apontando
 details.media.connected=Conectada
+details.media.fullpath=Caminho completo
 details.audiodetails=Detalhes do \u00e1udio
 details.noaudio=Nenhum arquivo de \u00e1udio selecionado
 details.audio.file=Arquivo de \u00e1udio
@@ -600,7 +725,6 @@ fieldname.newsegment=Segmento
 fieldname.custom=Personalizado
 fieldname.prefix=Campo
 fieldname.distance=Dist\u00e2ncia
-fieldname.movingdistance=Dist\u00e2ncia de movimento
 fieldname.duration=Dura\u00e7\u00e3o
 fieldname.speed=Velocidade
 fieldname.verticalspeed=Velocidade vertical
@@ -615,21 +739,39 @@ units.feet=P\u00e9s
 units.feet.short=ft
 units.kilometres=Quil\u00f4metros
 units.kilometres.short=km
+units.kilometresperhour=km por hora
 units.kilometresperhour.short=km/h
 units.miles=Milhas
 units.miles.short=mi
+units.milesperhour=milhas por hora
 units.milesperhour.short=mph
+units.nauticalmiles=milhas n\u00e1uticas
+units.nauticalmiles.short=m.n.
+units.nauticalmilesperhour.short=n\u00f3s
+units.metrespersec=metros por segundo
 units.metrespersec.short=m/s
+units.feetpersec=p\u00e9s por segundo
 units.feetpersec.short=ft/s
 units.hours=horas
+units.minutes=minutos
+units.seconds=segundos
 units.degminsec=Graus-min-seg
 units.degmin=Graus-min
 units.deg=Graus
 units.iso8601=ISO 8601
+units.degreescelsius=Celsius
+units.degreescelsius.short=\u00b0C
+units.degreesfahrenheit=Fahrenheit
+units.degreesfahrenheit.short=\u00b0F
+
+# How to combine conditions, such as filters
+logic.and=e
+logic.or=ou
 
 # External urls
 url.googlemaps=maps.google.com.br
 wikipedia.lang=pt
+openweathermap.lang=pt
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -646,10 +788,13 @@ undo.deletepoint=remover ponto
 undo.removephoto=remover foto
 undo.removeaudio=remover arquivo de \u00e1udio
 undo.deleterange=remover intervalo
+undo.croptrack=recortar rota
 undo.deletemarked=remover pontos
 undo.insert=inserir pontos
 undo.reverse=inverter intervalo
 undo.mergetracksegments=mesclar segmentos de rota
+undo.splitsegments=dividir segmentos de rota
+undo.sewsegments=reunir segmentos de rota
 undo.addtimeoffset=adicionar diferen\u00e7a de tempo
 undo.addaltitudeoffset=adicionar diferen\u00e7a de altitude
 undo.rearrangewaypoints=rearrumar pontos
@@ -672,10 +817,8 @@ error.save.failed=Falha ao salvar dados para arquivo
 error.saveexif.filenotfound=Falha ao procurar o arquivo de foto
 error.saveexif.cannotoverwrite1=Arquivo de foto
 error.saveexif.cannotoverwrite2=\u00e9 somente leitura e n\u00e3o pode ser sobrescrito. Gravar para c\u00f3pia?
-error.saveexif.failed1=Falha ao salvar
-error.saveexif.failed2=das imagens
-error.saveexif.forced1=
-error.saveexif.forced2=das imagens for\u00e7adas por solicita\u00e7\u00e3o
+error.saveexif.failed=Falha ao salvar %d das imagens
+error.saveexif.forced=%d das imagens for\u00e7adas por solicita\u00e7\u00e3o
 error.load.dialogtitle=Erro ao carregar dados
 error.load.noread=N\u00e3o foi poss\u00edvel ler arquivo
 error.load.nopoints=Nenhuma informa\u00e7\u00e3o de coordenadas encontrada no arquivo
@@ -686,7 +829,7 @@ error.jpegload.dialogtitle=Erro ao carregar fotos
 error.jpegload.nofilesfound=Nenhum arquivo encontrado
 error.jpegload.nojpegsfound=Nenhum arquivo jpeg encontrado
 error.jpegload.nogpsfound=Nenhuma informa\u00e7\u00e3o de GPS encontrada
-error.jpegload.exifreadfailed=Falha ao ler informa\u00e7\u00f5es do EXIF. Nenhuma informa\u00e7\u00e3o do EXIF pode ser lida\nseja na biblioteca interna, seja na externa.
+error.jpegload.exifreadfailed=Falha ao ler informa\u00e7\u00f5es do Exif. Nenhuma informa\u00e7\u00e3o do Exif pode ser lida\nseja na biblioteca interna, seja na externa.
 error.audioload.nofilesfound=Nenhum arquivo de \u00e1udio encontrado
 error.gpsload.unknown=Erro desconhecido
 error.undofailed.title=Falha ao desfazer
@@ -705,7 +848,13 @@ error.lookupsrtm.nonefound=Nenhum valor de altitude encontrado
 error.lookupsrtm.nonerequired=Todos os pontos j\u00e1 possuem altitude, assim n\u00e3o h\u00e1 nada a procurar
 error.gpsies.uploadnotok=O servidor Gpsies retornou a mensagem
 error.gpsies.uploadfailed=O envio falhou com o erro
+error.showphoto.failed=Falha ao carregar foto
 error.playaudiofailed=Falha ao reproduzir arquivo de \u00e1udio
 error.cache.notthere=A paste de cache de fundos n\u00e3o foi encontrada
 error.cache.empty=A pasta de cache de fundos est\u00e1 vazia
 error.cache.cannotdelete=Nenhum fundo pode ser removido
+error.interpolate.invalidparameter=O n\u00famero de pontos deve estar entre 1 e 1000
+error.learnestimationparams.failed=N\u00e3o foi poss\u00edvel aprender par\u00e2metros desta rota.\nTente baixar mais rotas.
+error.tracksplit.nosplit=A rota n\u00e3o pode ser dividida.
+error.downloadsrtm.nocache=Os arquivos n\u00e3o puderam ser salvos.\nPor favor, verifique a cache do disco.
+error.sewsegments.nothingdone=Os segmentos n\u00e3o puderam ser reunidos.\nExistem agora %d segmentos na rota.
diff --git a/tim/prune/lang/prune-texts_ro.properties b/tim/prune/lang/prune-texts_ro.properties
index 992d246..b94b88c 100644
--- a/tim/prune/lang/prune-texts_ro.properties
+++ b/tim/prune/lang/prune-texts_ro.properties
@@ -3,7 +3,7 @@
 
 # Menu entries
 menu.file=Fi\u015fier
-menu.file.addphotos=Adaugare foto
+menu.file.addphotos=Adaug\u0103 foto
 menu.file.recentfiles=Fi\u015fiere recente
 menu.file.save=Salvare
 menu.file.exit=Iesire
@@ -12,8 +12,8 @@ menu.track.undo=Anulare
 menu.track.clearundo=\u015etergere lista de anulari
 menu.track.deletemarked=\u015etergere puncte marcate
 menu.track.rearrange=Rearanjare waypoint
-menu.track.rearrange.start=Toate la inceputul fisierului
-menu.track.rearrange.end=Toate la sfarsitul fisierului
+menu.track.rearrange.start=Toate la inceputul fi\u015fierului
+menu.track.rearrange.end=Toate la sfarsitul fi\u015fierului
 menu.track.rearrange.nearest=Fiecare la punctul cel mai apropiat al traseului
 menu.range=Interval
 menu.range.all=Selectare toate
@@ -36,16 +36,20 @@ menu.view.browser.google=Harti Google
 menu.view.browser.openstreetmap=Openstreetmap
 menu.view.browser.mapquest=Mapquest
 menu.view.browser.yahoo=Harti Yahoo
+menu.view.browser.bing=Harti Bing
 menu.settings=Set\u0103ri
 menu.help=Ajutor
 # Popup menu for map
 menu.map.zoomin=Apropie
 menu.map.zoomout=Departeaza
 menu.map.zoomfull=Departeaza la maxim
-menu.map.newpoint=Adaug\u0103 punct
+menu.map.newpoint=Creaz\u0103 punct
+menu.map.drawpoints=Creaz\u0103 serie de puncte
 menu.map.connect=Traseaz\u0103 linii \u00eentre puncte
 menu.map.autopan=Autovizualizare punct ales
-menu.map.showmap=Arata harta
+menu.map.showmap=Arat\u0103 harta
+menu.map.showscalebar=Arat\u0103 scar\u0103
+menu.map.editmode=Mod de editare
 
 # Alt keys for menus
 altkey.menu.file=F
@@ -81,10 +85,14 @@ function.editwaypointname=Editare nume waypoint
 function.compress=Comprima traseu
 function.deleterange=\u015etergere gama
 function.interpolate=Interpolare
+function.addtimeoffset=Adaug\u0103 decalaj timp
+function.addaltitudeoffset=Adaug\u0103 decalaj altitudine
 function.findwaypoint=Gasire waypoint
 function.charts=Grafice
 function.show3d=Vizualizare arborescenta
 function.distances=Distan\u0163e
+function.fullrangedetails=Informa\u0163ie complet
+function.loadaudio=Adaug\u0103 audio
 function.setmapbg=Fundal
 function.setcolours=Selectare culorile
 function.setlanguage=Selectare limba
@@ -97,6 +105,7 @@ function.showkeys=Arat\u0103 tastele scurt\u0103turi
 function.about=Despre GpsPrune
 function.checkversion=Verific\u0103 pentru o versiune noua
 function.saveconfig=Salvare set\u0103ri
+function.getweatherforecast=Prognoz\u0103 meteo
 
 # Dialogs
 dialog.exit.confirm.title=Ie\u015fire din programul GpsPrune
@@ -120,6 +129,7 @@ dialog.delimiter.other=Alte
 dialog.openoptions.deliminfo.records=inregistrari, cu
 dialog.openoptions.deliminfo.fields=cimpuri
 dialog.openoptions.deliminfo.norecords=Nu sunt inregistrari
+dialog.selecttracks.noname=F\u0103r\u0103 nume
 dialog.jpegload.subdirectories=Include subdirectori
 dialog.jpegload.loadjpegswithoutcoords=Include fotografii fara coordonate
 dialog.jpegload.loadjpegsoutsidearea=Include fotografii din afara zonei curente
@@ -131,14 +141,33 @@ dialog.gpsload.format=Format
 dialog.gpsload.getwaypoints=Incarcare waypoints
 dialog.gpssend.trackname=Nume traseu
 dialog.gpsbabel.filters=Filtre
+dialog.gpsbabel.filter.simplify=Simplifica
 dialog.gpsbabel.filter.distance=Distan\u0163\u0103
+dialog.gpsbabel.filter.discard.numsats=Num\u0103r de sateli\u0163i <
 dialog.saveoptions.title=Salvare fi\u015fier
 dialog.save.table.field=Cimp
 dialog.save.table.save=Salvare
 dialog.save.overwrite.title=Fi\u015fierul exist\u0103
 dialog.save.overwrite.text=Fi\u015fierul exist\u0103. \u00cel suprascriu?
+dialog.exportkml.text=Titlu
+dialog.exportkml.trackcolour=Culoarea liniei
 dialog.exportgpx.name=Nume
 dialog.exportgpx.desc=Descriere
+dialog.exportgpx.encoding.system=Sistem
+dialog.exportgpx.encoding.utf8=UTF-8
+dialog.exportpov.font=Fontul
+dialog.exportpov.camerax=Vedere X
+dialog.exportpov.cameray=Vedere Y
+dialog.exportpov.cameraz=Vedere Z
+dialog.exportpov.modelstyle=Stilul
+dialog.3d.useterrain=Arat\u0103 teren
+dialog.3d.terraingridsize=Dimensiune a grilei
+dialog.exportpov.baseimage=Imagine cartografice
+dialog.baseimage.title=Imagine cartografice
+dialog.baseimage.tiles=Tigla
+dialog.exportsvg.phi=Azimut \u03D5
+dialog.exportsvg.theta=\u00cenclina\u0163ie \u03B8
+dialog.undo.title=Anulare
 dialog.pointedit.intro=V\u0103 rog selecta\u0163i r\u00e2ndul care va fi editat
 dialog.pointedit.table.field=Cimp
 dialog.pointedit.table.value=Valoare
@@ -153,19 +182,29 @@ dialog.saveexif.table.save=Salveaza
 dialog.saveexif.photostatus.connected=Conectat
 dialog.saveexif.photostatus.disconnected=Deconectat
 dialog.saveexif.photostatus.modified=Modificat
-dialog.saveexif.overwrite=Suprascrie fisiere
+dialog.saveexif.overwrite=Suprascrie fi\u015fiere
 dialog.charts.xaxis=Axa X
 dialog.charts.yaxis=Axa Y
 dialog.distances.currentpoint=Punct curent
+dialog.addmapsource.noname=F\u0103r\u0103 nume
 dialog.gpsies.column.name=Nume
 dialog.gpsies.column.length=Lungime
 dialog.gpsies.description=Descriere
-dialog.gpsies.nodescription=Fara descriere
+dialog.gpsies.nodescription=F\u0103r\u0103 descriere
 dialog.wikipedia.column.name=Nume
 dialog.wikipedia.column.distance=Distan\u0163\u0103
-dialog.correlate.options.tip=Indiciu: By manually correlating at least one photo, the time offset can be calculated for you.
+dialog.correlate.options.offset.hours=ore,
+dialog.correlate.options.offset.minutes=minute,
+dialog.correlate.options.offset.seconds=secunde
+dialog.pastecoordinates.coords=Coordonate
 dialog.about.version=Versiunea
+dialog.about.systeminfo=Informa\u0163ii a sistemului
 dialog.about.systeminfo.os=Sistem de operare
+dialog.about.systeminfo.exiflib=Bibliotec\u0103 Exif
+dialog.about.systeminfo.exiflib.internal=Intern
+dialog.about.systeminfo.exiflib.internal.failed=Intern (absent)
+dialog.about.systeminfo.exiflib.external=Extern
+dialog.about.systeminfo.exiflib.external.failed=Extern (absent)
 dialog.about.yes=Da
 dialog.about.no=Nu
 dialog.about.readme=Cite\u015fte-m\u0103
@@ -180,10 +219,23 @@ dialog.setcolours.text=Text
 dialog.colourchooser.red=Ro\u0219u
 dialog.colourchooser.green=Verde
 dialog.colourchooser.blue=Albastru
+dialog.weather.day.today=Ast\u0103zi
+dialog.weather.day.tomorrow=M\u00e2ine
+dialog.weather.day.monday=Luni
+dialog.weather.day.tuesday=Mar\u0163i
+dialog.weather.day.wednesday=Miercuri
+dialog.weather.day.thursday=Joi
+dialog.weather.day.friday=Vineri
+dialog.weather.day.saturday=S\u00e2mb\u0103t\u0103
+dialog.weather.day.sunday=Duminic\u0103
 
 # Confirm messages
-confirm.loadfile=Date incarcate din fisier
+confirm.loadfile=Date incarcate din fi\u015fier
 confirm.save.ok1=Salvat cu succes
+confirm.save.ok2=puncte \u00een
+
+# Tips
+tip.title=Indiciu
 
 # Buttons
 button.ok=OK
@@ -214,20 +266,22 @@ button.manage=Administra
 button.combine=Combina
 
 # File types
-filetype.txt=Fisier text
+filetype.txt=Fi\u015fiere text
 filetype.jpeg=Imagine JPEG (*.jpg)
-filetype.kmlkmz=Fisier KML, KMZ
-filetype.kml=Fisier KML
-filetype.kmz=Fisier KMZ
-filetype.gpx=Fisier GPX
-filetype.pov=Fisier POV
-filetype.svg=Fisier SVG
-filetype.png=Fisier PNG
-filetype.audio=Fisier MP3, OGG, WAV
+filetype.kmlkmz=Fi\u015fiere KML, KMZ
+filetype.kml=Fi\u015fiere KML
+filetype.kmz=Fi\u015fiere KMZ
+filetype.gpx=Fi\u015fiere GPX
+filetype.pov=Fi\u015fiere POV
+filetype.svg=Fi\u015fiere SVG
+filetype.png=Fi\u015fiere PNG
+filetype.audio=Fi\u015fiere MP3, OGG, WAV
 
 # Display components
+details.trackdetails=Detalii traseul
 details.track.points=Puncte
-details.pointdetails=Punct
+details.pointdetails=Detalii punctul
+details.rangedetails=Detalii intervalul
 details.range.selected=Selectat
 details.range.to=la
 details.altitude.to=la
@@ -240,6 +294,8 @@ display.range.time.days=z
 details.range.avespeed=Viteza medie
 details.range.maxspeed=Viteza maxim\u0103
 details.lists.photos=Foto-uri
+details.lists.audio=Audio
+details.audiodetails=Detalii audio
 
 # Field names
 fieldname.latitude=Latitudine
@@ -282,3 +338,6 @@ cardinal.n=N
 cardinal.s=S
 cardinal.e=E
 cardinal.w=V
+
+wikipedia.lang=ro
+openweathermap.lang=ro
diff --git a/tim/prune/lang/prune-texts_ru.properties b/tim/prune/lang/prune-texts_ru.properties
index a34b95d..a851289 100644
--- a/tim/prune/lang/prune-texts_ru.properties
+++ b/tim/prune/lang/prune-texts_ru.properties
@@ -7,6 +7,7 @@ menu.file.addphotos=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u043
 menu.file.recentfiles=\u041f\u0440\u0438\u043d\u044f\u0442\u044b\u0435 \u0444\u0430\u0439\u043b\u044b
 menu.file.save=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u0430\u043a \u0442\u0435\u043a\u0441\u0442
 menu.file.exit=\u0412\u044b\u0445\u043e\u0434
+menu.online=\u041e\u043d\u043b\u0430\u0439\u043d
 menu.track=\u0422\u0440\u0435\u043a
 menu.track.undo=\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
 menu.track.clearundo=\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439
@@ -57,6 +58,7 @@ menu.map.editmode=\u0420\u0435\u0436\u0438\u043c \u0440\u0435\u0434\u0430\u043a\
 
 # Alt keys for menus
 altkey.menu.file=F
+altkey.menu.online=N
 altkey.menu.track=T
 altkey.menu.range=R
 altkey.menu.point=P
@@ -105,6 +107,7 @@ function.setpaths=\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \
 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
 function.lookupsrtm=\u0412\u044b\u0441\u043e\u0442\u044b \u0432 SRTM
+function.downloadsrtm=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c SRTM \u0434\u0430\u043d\u043d\u044b\u0435
 function.getwikipedia=\u0421\u0442\u0430\u0442\u044c\u044f \u043e \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0435\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0432 \u0412\u0438\u043a\u0438
 function.searchwikipedianames=\u041f\u043e\u0438\u0441\u043a \u0441\u0442\u0430\u0442\u0435\u0439 \u0432 \u0412\u0438\u043a\u0438 \u043f\u043e \u0438\u043c\u0435\u043d\u0438
 function.downloadosm=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c OSM \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u0442\u0435\u0440\u0440\u0438\u0442\u043e\u0440\u0438\u044e
@@ -133,6 +136,7 @@ function.checkversion=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0
 function.saveconfig=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438
 function.diskcache=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u0430\u0440\u0442\u044b \u043d\u0430 \u0434\u0438\u0441\u043a
 function.managetilecache=\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u0435\u0448\u0435\u043c
+function.getweatherforecast=\u043f\u0440\u043e\u0433\u043d\u043e\u0301\u0437 \u043f\u043e\u0433\u043e\u0301\u0434\u044b
 
 # Dialogs
 dialog.exit.confirm.title=\u0412\u044b\u0445\u043e\u0434
@@ -176,6 +180,9 @@ dialog.gpsload.save=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043
 dialog.gpssend.sendwaypoints=\u041f\u043e\u0441\u043b\u0430\u0442\u044c \u043f\u0443\u0442\u0435\u0432\u044b\u0435 \u0442\u043e\u0447\u043a\u0438
 dialog.gpssend.sendtracks=\u041f\u043e\u0441\u043b\u0430\u0442\u044c \u0442\u0440\u0435\u043a\u0438
 dialog.gpssend.trackname=\u0418\u043c\u044f \u0442\u0440\u0435\u043a\u0430
+dialog.gpsbabel.filters=\u0424\u0438\u043b\u044c\u0442\u0440\u044b
+dialog.addfilter.title=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u0438\u043b\u044c\u0442\u0440
+dialog.gpsbabel.filter.discard.numsats=\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043f\u0443\u0442\u043d\u0438\u043a\u043e\u0432 <
 dialog.saveoptions.title=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0444\u0430\u0439\u043b
 dialog.save.fieldstosave=\u041f\u043e\u043b\u044f \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435
 dialog.save.table.field=\u041f\u043e\u043b\u0435
@@ -210,8 +217,10 @@ dialog.exportpov.modelstyle=\u0421\u0442\u0438\u043b\u044c \u043c\u043e\u0434\u0
 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.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\ [...]
+dialog.3d.terraingridsize=\u0420\u0430\u0437\u043c\u0435\u0440 \u0441\u0435\u0442\u043a\u0438
 dialog.baseimage.mapsource=\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430
 dialog.baseimage.zoom=\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u0442
+dialog.baseimage.tiles=\u0422\u0430\u0439\u043b\u044b
 dialog.baseimage.size=\u0420\u0430\u0437\u043c\u0435\u0440 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f
 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
@@ -236,7 +245,9 @@ dialog.undo.none.text=\u041d\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0
 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\ [...]
 dialog.pointedit.title=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u0447\u043a\u0443
+dialog.pointedit.intro=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043e\u043b\u0435 \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
+dialog.pointedit.nofield=\u041f\u043e\u043b\u0435 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u0430
 dialog.pointedit.table.value=\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435
 dialog.pointnameedit.name=\u0418\u043c\u044f \u043f\u0443\u0442\u0435\u0432\u043e\u0439 \u0442\u043e\u0447\u043a\u0438
 dialog.pointnameedit.uppercase=\u0412\u0435\u0440\u0445\u043d\u0438\u0439 \u0440\u0435\u0433\u0438\u0441\u0442\u0440
@@ -283,6 +294,7 @@ dialog.estimatetime.details=\u0414\u0435\u0442\u0430\u043b\u0438\u0437\u0430\u04
 dialog.estimatetime.climb=\u041f\u043e\u0434\u044a\u0435\u043c
 dialog.estimatetime.descent=\u0421\u043f\u0443\u0441\u043a
 dialog.estimatetime.parameters=\u041f\u0430\u0440\u0430\u0301\u043c\u0435\u0442\u0440\u044b
+dialog.estimatetime.parameters.timefor=\u0412\u0440\u0435\u043c\u044f \u0434\u043b\u044f
 dialog.setmapbg.intro=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0444\u043e\u043d\u0430 \u0438\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u043d\u043e\u0432\u044b\u0439
 dialog.addmapsource.title=\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0444\u043e\u043d\u0430
 dialog.addmapsource.sourcename=\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430
@@ -318,7 +330,6 @@ dialog.correlate.photoselect.intro=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0
 dialog.correlate.select.photoname=\u0418\u043c\u044f \u0444\u043e\u0442\u043e
 dialog.correlate.select.timediff=\u0420\u0430\u0437\u043d\u0438\u0446\u0430 \u0432\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438
 dialog.correlate.select.photolater=\u0424\u043e\u0442\u043e \u043f\u043e\u0437\u0434\u043d\u0435\u0435
-dialog.correlate.options.tip=\u0421\u043e\u0432\u0435\u0442: \u041f\u0440\u0438 \u0440\u0443\u0447\u043d\u043e\u043c \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043a\u0440\u0430\u0439\u043d\u0438\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u043c\u0435\u0442\u043a\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u0430 \u0430\u0432 [...]
 dialog.correlate.options.intro=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f
 dialog.correlate.options.offsetpanel=\u041e\u0442\u043c\u0435\u0442\u043a\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438
 dialog.correlate.options.offset=\u0421\u043c\u0435\u0449\u0435\u043d\u0438\u0435
@@ -361,8 +372,7 @@ dialog.compress.duplicates.title=\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u043
 dialog.compress.douglaspeucker.title=\u0421\u0436\u0430\u0442\u0438\u0435 \u043f\u043e \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0443 Douglas-Peucker
 dialog.compress.douglaspeucker.paramdesc=\u0420\u0430\u0437\u043c\u0430\u0445
 dialog.compress.summarylabel=\u0422\u043e\u0447\u043a\u0438 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f
-dialog.compress.confirm1=
-dialog.compress.confirm2=\u0442\u043e\u0447\u043a\u0438 \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u044b.\n\u0427\u0442\u043e\u0431\u044b \u0438\u0445 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043c\u0435\u043d\u044e \u0422\u0440\u0435\u043a->\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043e\u0442\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u043e\u0447\u043a\u0438
+dialog.compress.confirm=%d \u0442\u043e\u0447\u043a\u0438 \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u044b.\n\u0427\u0442\u043e\u0431\u044b \u0438\u0445 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043c\u0435\u043d\u044e \u0422\u0440\u0435\u043a->\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043e\u0442\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u043e\u0447\u043a\u0438
 dialog.compress.confirmnone=\u043d\u0435\u0442 \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0445 \u0442\u043e\u0447\u0435\u043a
 dialog.deletemarked.nonefound=\u041d\u0435\u0442 \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0445 \u0442\u043e\u0447\u0435\u043a \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f
 dialog.pastecoordinates.desc=\u0417\u0430\u0434\u0430\u0439\u0442\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0437\u0434\u0435\u0441\u044c
@@ -470,13 +480,27 @@ dialog.diskcache.tileset.multiple=\u043c\u043d\u043e\u0436\u0435\u0441\u0442\u04
 dialog.diskcache.deleteold=\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0440\u044b\u0445 \u0442\u0430\u0439\u043b\u043e\u0432
 dialog.diskcache.maximumage=\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0432\u043e\u0437\u0440\u0430\u0441\u0442 (\u0432 \u0434\u043d\u044f\u0445)
 dialog.diskcache.deleteall=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0432\u0441\u0435 \u0442\u0430\u0439\u043b\u044b
-dialog.diskcache.deleted1=\u0423\u0434\u0430\u043b\u0435\u043d\u043e
-dialog.diskcache.deleted2=\u0444\u0430\u0439\u043b\u043e\u0432 \u0438\u0437 \u043a\u044d\u0448\u0430
+dialog.diskcache.deleted=\u0423\u0434\u0430\u043b\u0435\u043d\u043e %d \u0444\u0430\u0439\u043b\u043e\u0432 \u0438\u0437 \u043a\u044d\u0448\u0430
 dialog.deletefieldvalues.intro=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043e\u043b\u0435 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0438\u0437 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430
 dialog.deletefieldvalues.nofields=\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430 \u043d\u0435\u0442 \u043f\u043e\u043b\u0435\u0439 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f
 dialog.setlinewidth.text=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u043e\u043b\u0449\u0438\u043d\u0443 \u043b\u0438\u043d\u0438\u0439 \u0434\u043b\u044f \u0442\u0440\u0435\u043a\u043e\u0432 (1-4)
 dialog.downloadosm.desc=\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 OSM \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438:
 dialog.searchwikipedianames.search=\u041f\u043e\u0438\u0441\u043a \u0434\u043b\u044f:
+dialog.weather.location=\u043c\u0435\u0301\u0441\u0442\u043e
+dialog.weather.temperatureunits=\u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0301\u0440\u0430
+dialog.weather.day.now=\u0422\u0435\u043a\u0443\u0449\u0430\u044f \u043f\u043e\u0433\u043e\u0434\u0430
+dialog.weather.day.today=\u0421\u0435\u0433\u043e\u0434\u043d\u044f
+dialog.weather.day.tomorrow=\u0417\u0430\u0432\u0442\u0440\u0430
+dialog.weather.day.monday=\u041f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a
+dialog.weather.day.tuesday=\u0412\u0442\u043e\u0440\u043d\u0438\u043a
+dialog.weather.day.wednesday=\u0421\u0440\u0435\u0434\u0430
+dialog.weather.day.thursday=\u0427\u0435\u0442\u0432\u0435\u0440\u0433
+dialog.weather.day.friday=\u041f\u044f\u0442\u043d\u0438\u0446\u0430
+dialog.weather.day.saturday=\u0421\u0443\u0431\u0431\u043e\u0442\u0430
+dialog.weather.day.sunday=\u0412\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u044c\u0435
+dialog.weather.wind=\u0432\u0435\u0301\u0442\u0435\u0440
+dialog.weather.temp=\u0442\u0435\u043c\u043f
+dialog.weather.humidity=\u0432\u043b\u0430\u0301\u0433\u0430
 
 # 3d window
 dialog.3d.title=GpsPrune 3D-\u0432\u0438\u0434
@@ -498,8 +522,7 @@ confirm.rearrangephotos=\u0424\u043e\u0442\u043e \u043f\u0435\u0440\u0435\u0434\
 confirm.cutandmove=\u041e\u0442\u043e\u0431\u0440\u0430\u043d\u043d\u043e\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u043e
 confirm.interpolate=\u0422\u043e\u0447\u043a\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b
 confirm.convertnamestotimes=\u0418\u043c\u044f \u043f\u0443\u0442\u0435\u0432\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 \u043f\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043d\u043e
-confirm.saveexif.ok1=\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043e
-confirm.saveexif.ok2=\u0444\u0430\u0439\u043b\u044b \u0441 \u0444\u043e\u0442\u043e
+confirm.saveexif.ok=\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043e %d \u0444\u0430\u0439\u043b\u044b \u0441 \u0444\u043e\u0442\u043e
 confirm.undo.single=\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u043e\u0442\u043c\u0435\u043d\u044b
 confirm.undo.multi=\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043e\u0442\u043c\u0435\u043d\u044b
 confirm.jpegload.single=\u0444\u043e\u0442\u043e \u0431\u044b\u043b\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e
@@ -513,13 +536,18 @@ confirm.correlatephotos.multi=\u0444\u043e\u0442\u043e \u0431\u044b\u043b\u0438
 confirm.createpoint=\u0442\u043e\u0447\u043a\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0430
 confirm.rotatephoto=\u0444\u043e\u0442\u043e \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u043e
 confirm.running=\u0420\u0430\u0431\u043e\u0442\u0430\u044e...
-confirm.lookupsrtm1=\u041d\u0430\u0439\u0434\u0435\u043d\u043e
-confirm.lookupsrtm2=\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432\u044b\u0441\u043e\u0442\u044b
+confirm.lookupsrtm=\u041d\u0430\u0439\u0434\u0435\u043d\u043e %d \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432\u044b\u0441\u043e\u0442\u044b
+confirm.downloadsrtm=\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e %d \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u043a\u0435\u0448
+confirm.downloadsrtm.1=\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e %d \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u043a\u0435\u0448
 confirm.deletefieldvalues=\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u044b
 confirm.audioload=\u0424\u0430\u0439\u043b\u044b \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b
 confirm.correlateaudios.single=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u044c \u0431\u044b\u043b\u0438 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430
 confirm.correlateaudios.multi=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0438 \u0431\u044b\u043b\u0438 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b
 
+# Tips, shown just once when appropriate
+tip.title=\u0421\u043e\u0432\u0435\u0442
+tip.manuallycorrelateone=\u041f\u0440\u0438 \u0440\u0443\u0447\u043d\u043e\u043c \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043a\u0440\u0430\u0439\u043d\u0438\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u043c\u0435\u0442\u043a\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438 [...]
+
 # Buttons
 button.ok=OK
 button.back=\u041d\u0430\u0437\u0430\u0434
@@ -623,7 +651,6 @@ fieldname.newsegment=\u0421\u0435\u0433\u043c\u0435\u043d\u0442
 fieldname.custom=\u041e\u0431\u044b\u0447\u043d\u043e
 fieldname.prefix=\u041f\u043e\u043b\u0435
 fieldname.distance=\u0420\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435
-fieldname.movingdistance=\u0420\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u0435\u0437 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043a\u043e\u0432
 fieldname.duration=\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c
 fieldname.speed=\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c
 fieldname.verticalspeed=\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c
@@ -710,10 +737,8 @@ error.save.failed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441
 error.saveexif.filenotfound=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0444\u0430\u0439\u043b \u0441 \u0444\u043e\u0442\u043e
 error.saveexif.cannotoverwrite1=\u0444\u0430\u0439\u043b \u0441 \u0444\u043e\u0442\u043e
 error.saveexif.cannotoverwrite2=\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f, \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u043d! \u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u0430\u043a \u043a\u043e\u043f\u0438\u044e?
-error.saveexif.failed1=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c
-error.saveexif.failed2=\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435(-\u0438\u0439)
-error.saveexif.forced1=
-error.saveexif.forced2=\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435(-\u0438\u0439) \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e
+error.saveexif.failed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c %d \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435(-\u0438\u0439)
+error.saveexif.forced=%d \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435(-\u0438\u0439) \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e
 error.load.dialogtitle=\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0434\u0430\u043d\u043d\u044b\u0445
 error.load.noread=\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0444\u0430\u0439\u043b
 error.load.nopoints=\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u0445
@@ -724,7 +749,7 @@ error.jpegload.dialogtitle=\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u04
 error.jpegload.nofilesfound=\u0424\u0430\u0439\u043b\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
 error.jpegload.nojpegsfound=JEPG-\u0444\u0430\u0439\u043b\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
 error.jpegload.nogpsfound=\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430 GPS-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f
-error.jpegload.exifreadfailed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c EXIF-\u200b\u200b\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e. \u041d\u0435\u0442 EXIF-\u200b\u200b\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u200b\u200b\u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c\n\u0431\u0435\u0437 \u0438\u0441\ [...]
+error.jpegload.exifreadfailed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c Exif-\u200b\u200b\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e. \u041d\u0435\u0442 Exif-\u200b\u200b\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u200b\u200b\u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c\n\u0431\u0435\u0437 \u0438\u0441\ [...]
 error.audioload.nofilesfound=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
 error.gpsload.unknown=\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430
 error.undofailed.title=\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c
diff --git a/tim/prune/lang/prune-texts_sv.properties b/tim/prune/lang/prune-texts_sv.properties
new file mode 100644
index 0000000..eeffcd9
--- /dev/null
+++ b/tim/prune/lang/prune-texts_sv.properties
@@ -0,0 +1,58 @@
+# Text entries for the GpsPrune application
+# Swedish entries
+
+# Menu entries
+menu.file=Fil
+menu.file.addphotos=L\u00e4gg till foto
+menu.file.recentfiles=Senaste filer
+menu.file.save=Spara som text
+menu.file.exit=Avsluta
+menu.track=Sp\u00e5r
+menu.track.undo=\u00c5ngra
+menu.track.clearundo=Rensa \u00e5ngra
+menu.track.markrectangle=Markera punkter i rektangel
+menu.track.deletemarked=Radera markerade punkter
+menu.track.rearrange=Arrangera om ruttpunkter
+menu.track.rearrange.start=Alla till b\u00f6rjan av fil
+menu.track.rearrange.end=Alla till slut av fil
+menu.track.rearrange.nearest=Varje till n\u00e4rmaste sp\u00e5rpunkt
+menu.range=Omr\u00e5de
+menu.range.all=V\u00e4lj alla
+menu.range.none=V\u00e4lj ingen
+menu.range.start=St\u00e4ll in b\u00f6rjan p\u00e5 omr\u00e5de
+menu.range.end=St\u00e4ll in slut p\u00e5 omr\u00e5de
+menu.range.average=Medelv\u00e4rdesval
+menu.range.reverse=Backa omr\u00e5de
+menu.range.mergetracksegments=Sl\u00e5 ihop sp\u00e5rsegment
+menu.range.cutandmove=Klipp och flytta urval
+menu.point=Punkt
+menu.point.editpoint=Redigera punkt
+menu.point.deletepoint=Radera punkt
+menu.photo=Foto
+menu.photo.saveexif=Spara och avsluta
+menu.audio=Ljud
+menu.view=Vy
+menu.view.showsidebars=Visa sidolister
+menu.view.browser=Karta i ett l\u00e4sarf\u00f6nster
+menu.view.browser.google=Google Maps
+menu.view.browser.openstreetmap=Openstreetmap
+menu.view.browser.mapquest=Mapquest
+menu.view.browser.yahoo=Yahoo maps
+menu.view.browser.bing=Bing maps
+menu.settings=Inst\u00e4llningar
+menu.settings.onlinemode=Ladda karta fr\u00e5n Internet
+menu.settings.autosave=Autospara inst\u00e4llningar vid avslut
+menu.help=Hj\u00e4lp
+
+# Alt keys for menus
+altkey.menu.file=F
+altkey.menu.track=S
+altkey.menu.range=O
+altkey.menu.point=P
+altkey.menu.view=V
+altkey.menu.photo=T
+altkey.menu.audio=L
+altkey.menu.settings=I
+altkey.menu.help=H
+
+openweathermap.lang=se
diff --git a/tim/prune/lang/prune-texts_tr.properties b/tim/prune/lang/prune-texts_tr.properties
index 32eed12..4baf0de 100644
--- a/tim/prune/lang/prune-texts_tr.properties
+++ b/tim/prune/lang/prune-texts_tr.properties
@@ -5,7 +5,7 @@
 menu.file=Dosya
 menu.file.addphotos=Foto ekle
 menu.file.save=Kaydet
-menu.file.exit=�\u0131k\u0131\u015f
+menu.file.exit=\u00c7\u0131k\u0131\u015f
 menu.track=\u0130z
 menu.track.undo=Geri al
 menu.track.clearundo=Geri alma listesi s\u0131f\u0131rla
@@ -198,7 +198,7 @@ dialog.saveexif.overwrite=Dosyalar\u0131n \u00fczerinde yaz
 dialog.saveexif.force=Ufak hatalar\u0131 bo\u015fver
 dialog.charts.xaxis=X axis
 dialog.charts.yaxis=Y axis
-dialog.charts.output=�\u0131kt\u0131
+dialog.charts.output=\u00c7\u0131kt\u0131
 dialog.charts.screen=Ekranda g\u00f6ster
 dialog.charts.svg=SVG dosya olarak g\u00f6ster
 dialog.charts.svgwidth=SVG geni\u015fli\u011fi
@@ -228,7 +228,7 @@ dialog.correlate.options.offset.seconds=saniye
 dialog.correlate.options.photolater=Foto noktadan sonra
 dialog.correlate.options.pointlaterphoto=Nokta fotodan sonra
 dialog.pastecoordinates.coords=Koordinatlar
-dialog.help.help=Ayr\u0131nt\u0131l\u0131 bilgi ve kullanma k\u0131lavuzu i\u00e7in l\u00fctfen\n http://activityworkshop.net/software/gpsprune/\n sitesinde bak.
+dialog.help.help=Ayr\u0131nt\u0131l\u0131 bilgi ve kullanma k\u0131lavuzu i\u00e7in l\u00fctfen\n http://gpsprune.activityworkshop.net/\n sitesinde bak.
 dialog.about.version=S\u00fcr\u00fcm
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune GPS ayg\u0131tlardan veri y\u00fckler, g\u00f6r\u00fcnt\u00fcler ver d\u00fczenler bir uygulamad\u0131r.
@@ -245,7 +245,7 @@ dialog.about.systeminfo.gpsbabel=Gpsbabel kuruldu
 dialog.about.systeminfo.gnuplot=Gnuplot kuruldu
 dialog.about.yes=Evet
 dialog.about.no=Hay\u0131r
-dialog.about.credits.translators=�evirmen
+dialog.about.credits.translators=\u00c7evirmen
 dialog.about.credits.thanks=Te\u015fekk\u00fcrler
 dialog.about.readme=Beni oku
 dialog.checkversion.uptodate=GpsPrune'nin so s\u00fcr\u00fcm\u00fc kullan\u0131yorsun.
@@ -253,7 +253,7 @@ dialog.checkversion.newversion1=GpsPrune'nin yeni bir s\u00fcr\u00fcm\u00fc \u00
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Yeni s\u00fcr\u00fcm\u00fcn\u00fcn devir tarihi
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Yeni s\u00fcr\u00fcm indirmek i\u00e7in http://activityworkshop.net/software/gpsprune/download.html adresine git.
+dialog.checkversion.download=Yeni s\u00fcr\u00fcm indirmek i\u00e7in http://gpsprune.activityworkshop.net/download.html adresine git.
 dialog.keys.intro=Fare yerinde a\u015fa\u011f\u0131daki k\u0131sayol tu\u015flar\u0131 kullanabilirsin:
 dialog.keys.keylist=<table><tr><td>Ok tu\u015flar\u0131</td><td>Haritay\u0131 sola/sa\u011fa/a\u015fa\u011f\u0131/yukar\u0131 kayd\u0131r</td></tr><tr><td>Ctrl + sol, sa\u011f</td><td>\u00d6nceki/sonraki noktay\u0131 se\u00e7</td></tr><tr><td>Ctrl + yukar/a\u015fa\u011f\u0131</td><td>Yak\u0131nla\u015ft\u0131r/Uzakla\u015ft\u0131r</td></tr><tr><td>Del</td><td>Se\u00e7ili noltay\u0131 sil</td></tr></table>
 dialog.saveconfig.desc=A\u011fa\u015f\u0131daki ayarlar\u0131 bir dasyada kaydedilir:
@@ -274,7 +274,7 @@ dialog.addaltitude.noaltitudes=Se\u00e7ili s\u0131rada y\u00fckseklik bilgisi bu
 dialog.addaltitude.desc=Eklenecek y\u00fckseklik ofseti
 dialog.setcolours.background=Arkafonu
 dialog.setcolours.borders=Kenarlar
-dialog.setcolours.lines=�izgiler
+dialog.setcolours.lines=\u00c7izgiler
 dialog.setcolours.primary=Birincil
 dialog.setcolours.secondary=\u0130kincil
 dialog.setcolours.point=Noktalar
@@ -300,7 +300,7 @@ button.overwrite=\u00dczerinde yaz
 button.moveup=Yukar\u0131
 button.movedown=A\u015fa\u011f\u0131
 button.edit=D\u00fczenle
-button.exit=�\u0131k\u0131\u015f
+button.exit=\u00c7\u0131k\u0131\u015f
 button.close=Kapat
 button.continue=Devam
 button.yes=Evet
@@ -400,6 +400,8 @@ units.iso8601=ISO 8601
 
 # External urls
 url.googlemaps=maps.google.com
+wikipedia.lang=tr
+openweathermap.lang=tr
 
 # Cardinals for 3d plots
 cardinal.n=K
diff --git a/tim/prune/lang/prune-texts_uk.properties b/tim/prune/lang/prune-texts_uk.properties
new file mode 100644
index 0000000..6f74d55
--- /dev/null
+++ b/tim/prune/lang/prune-texts_uk.properties
@@ -0,0 +1,234 @@
+# Text entries for the GpsPrune application
+# Ukrainian entries thanks to serhijdubyk
+
+# Menu entries
+menu.file=\u0424\u0430\u0439\u043b
+menu.file.addphotos=\u0414\u043e\u0434\u0430\u0442\u0438 \u0444\u043e\u0442\u043e
+menu.file.recentfiles=\u041f\u0440\u0438\u0439\u043d\u044f\u0442\u0456 \u0444\u0430\u0439\u043b\u0438
+menu.file.save=\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u044f\u043a \u0442\u0435\u043a\u0441\u0442
+menu.file.exit=\u0412\u0438\u0445\u0456\u0434
+menu.track=\u0422\u0440\u0435\u043a
+menu.track.undo=\u0421\u043a\u0430\u0441\u0443\u0432\u0430\u0442\u0438
+menu.track.clearundo=\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0437\u043c\u0456\u043d
+menu.track.markrectangle=\u041f\u043e\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0438 \u0443 \u043f\u0440\u044f\u043c\u043e\u043a\u0443\u0442\u043d\u0438\u043a\u0443
+menu.track.deletemarked=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u043f\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0456 \u0442\u043e\u0447\u043a\u0438
+menu.track.rearrange=\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0438
+menu.track.rearrange.start=\u0423\u0441\u0435 \u043d\u0430 \u043f\u043e\u0447\u0430\u0442\u043e\u043a \u0444\u0430\u0439\u043b\u0443
+menu.track.rearrange.end=\u0423\u0441\u0435 \u043d\u0430 \u043a\u0456\u043d\u0435\u0446\u044c \u0444\u0430\u0439\u043b\u0443
+menu.track.rearrange.nearest=\u041f\u0435\u0440\u0435\u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u0434\u043e \u043d\u0430\u0439\u0431\u043b\u0438\u0436\u0447\u043e\u0457
+menu.range=\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b
+menu.range.all=\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0443\u0441\u0456
+menu.range.none=\u0421\u043a\u0430\u0441\u0443\u0432\u0430\u0442\u0438 \u0432\u0438\u0431\u0456\u0440\u043a\u0443
+menu.range.start=\u041f\u043e\u0447\u0430\u0442\u043e\u043a \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0443
+menu.range.end=\u041a\u0456\u043d\u0435\u0446\u044c \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0443
+menu.range.average=\u0422\u043e\u0447\u043a\u0430 \u043f\u043e \u0441\u0435\u0440\u0435\u0434\u043d\u044c\u043e\u043c\u0443
+menu.range.reverse=\u041f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u0442\u0438 \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b
+menu.range.mergetracksegments=\u0417\u043b\u0438\u0442\u0438 \u0441\u0435\u0433\u043c\u0435\u043d\u0442\u0438 \u0442\u0440\u0435\u043a\u0443
+menu.range.cutandmove=\u0412\u0438\u0440\u0456\u0437\u0430\u0442\u0438 \u0456 \u043f\u0435\u0440\u0435\u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u0432\u0438\u0431\u0456\u0440\u043a\u0443
+menu.point=\u0422\u043e\u0447\u043a\u0430
+menu.point.editpoint=\u0420\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u0442\u043e\u0447\u043a\u0443
+menu.point.deletepoint=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0443
+menu.photo=\u0421\u0432\u0456\u0442\u043b\u0438\u043d\u0438
+menu.photo.saveexif=\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u0432 Exif
+menu.audio=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0438
+menu.view=\u0412\u0438\u0433\u043b\u044f\u0434
+menu.view.showsidebars=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043f\u0430\u043d\u0435\u043b\u044c
+menu.view.browser=\u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u043c\u0430\u043f\u0443 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0456
+menu.view.browser.google=\u041c\u0430\u043f\u0438 Google
+menu.view.browser.openstreetmap=Openstreetmap
+menu.view.browser.mapquest=Mapquest
+menu.view.browser.yahoo=\u041c\u0430\u043f\u0438 Yahoo
+menu.view.browser.bing=\u041c\u0430\u043f\u0438 Bing
+menu.settings=\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f
+menu.settings.onlinemode=\u0417\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u043c\u0430\u043f\u0438 \u0437 \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442
+menu.settings.autosave=\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043d\u044f \u043f\u0440\u0438 \u0432\u0438\u0445\u043e\u0434\u0456
+menu.help=\u0414\u043e\u043f\u043e\u043c\u043e\u0433\u0430
+# Popup menu for map
+menu.map.zoomin=\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438
+menu.map.zoomout=\u0417\u043c\u0435\u043d\u0448\u0438\u0442\u0438
+menu.map.zoomfull=\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u0434\u043e \u043f\u043e\u0432\u043d\u043e\u0457 \u0448\u043a\u0430\u043b\u0438
+menu.map.newpoint=\u0421\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043e\u0434\u043d\u0443 \u0442\u043e\u0447\u043a\u0443
+menu.map.drawpoints=\u0421\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043a\u0456\u043b\u044c\u043a\u0430 \u0442\u043e\u0447\u043e\u043a
+menu.map.connect=\u0417\u2019\u0454\u0434\u043d\u0430\u0442\u0438 \u0442\u043e\u0447\u043a\u0438 \u0442\u0440\u0435\u043a\u0443 \u0437 \u043b\u0456\u043d\u0456\u0454\u044e
+menu.map.autopan=\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u0438 \u0432\u0438\u0431\u0440\u0430\u043d\u0435
+menu.map.showmap=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u041e\u0421\u041c-\u043c\u0430\u043f\u0443
+menu.map.showscalebar=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043b\u0456\u043d\u0456\u0439\u043a\u0443 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0443
+menu.map.editmode=\u0420\u0435\u0436\u0438\u043c \u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u043d\u043d\u044f
+
+# 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=\u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u0444\u0430\u0439\u043b
+function.importwithgpsbabel=\u0406\u043c\u043f\u043e\u0440\u0442 \u0444\u0430\u0439\u043b\u0443 \u0437GPSBabel
+function.loadfromgps=\u0412\u0438\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0437 GPS
+function.sendtogps=\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0432 GPS
+function.exportkml=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 KML
+function.exportgpx=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 GPX
+function.exportpov=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 POV
+function.exportsvg=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 SVG
+function.exportimage=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f
+function.editwaypointname=\u0420\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u0456\u043c\u2019\u044f \u0448\u043b\u044f\u0445\u043e\u0432\u043e\u0457 \u0442\u043e\u0447\u043a\u0438
+function.compress=\u0421\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \u0442\u0440\u0435\u043a
+function.deleterange=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b
+function.croptrack=\u041e\u0431\u0440\u0456\u0437\u0430\u0442\u0438 \u0442\u0440\u0435\u043a
+function.interpolate=\u0406\u043d\u0442\u0435\u0440\u043f\u043e\u043b\u044f\u0446\u0456\u044f \u0442\u043e\u0447\u043e\u043a
+function.addtimeoffset=\u0414\u043e\u0434\u0430\u0442\u0438 \u043f\u043e\u0437\u043d\u0430\u0447\u043a\u0443 \u0447\u0430\u0441\u0443
+function.addaltitudeoffset=\u0414\u043e\u0434\u0430\u0442\u0438 \u043f\u043e\u0437\u043d\u0430\u0447\u043a\u0443 \u0432\u0438\u0441\u043e\u0442\u0438
+function.convertnamestotimes=\u041f\u0435\u0440\u0435\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u0456\u043c\u2019\u044f \u0448\u043b\u044f\u0445\u043e\u0432\u043e\u0457 \u0442\u043e\u0447\u043a\u0438 \u0443 \u0447\u0430\u0441
+function.deletefieldvalues=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u043e\u043b\u044f
+function.findwaypoint=\u0417\u043d\u0430\u0439\u0442\u0438 \u0448\u043b\u044f\u0445\u043e\u0432\u0443 \u0442\u043e\u0447\u043a\u0443
+function.pastecoordinates=\u0412\u0432\u0435\u0434\u0435\u043d\u043d\u044f \u043d\u043e\u0432\u0438\u0445 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442
+function.charts=\u0413\u0440\u0430\u0444\u0456\u043a\u0438
+function.show3d=3D-\u0432\u0438\u0433\u043b\u044f\u0434
+function.distances=\u0412\u0456\u0434\u0441\u0442\u0430\u043d\u0456
+function.fullrangedetails=\u0414\u0435\u0442\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f \u043f\u043e \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0443
+function.estimatetime=\u041f\u0440\u0438\u0431\u043b\u0438\u0437\u043d\u0438\u0439 \u0447\u0430\u0441
+function.learnestimationparams=\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0432\u0433\u0430\u0434\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0431\u043b\u0438\u0437\u043d\u043e\u0433\u043e \u0447\u0430\u0441\u0443
+function.setmapbg=\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u043c\u0430\u043f\u0443-\u043f\u0456\u0434\u043a\u043b\u0430\u0434\u043a\u0443
+function.setpaths=\u0412\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u0448\u043b\u044f\u0445\u0438 \u0434\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c
+function.getgpsies=\u0417\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0442\u0440\u0435\u043a\u0438 \u0437 Gpsies
+function.uploadgpsies=\u0412\u0438\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0442\u0440\u0435\u043a\u0438 \u043d\u0430 Gpsies
+function.lookupsrtm=\u041e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0432\u0438\u0441\u043e\u0442\u0438 \u0437 SRTM
+function.getwikipedia=\u041e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u043d\u0430\u0439\u0431\u043b\u0438\u0436\u0447\u0443 \u0441\u0442\u0430\u0442\u0442\u044e \u0437 \u0412\u0456\u043a\u0456\u043f\u0435\u0434\u0456\u0457
+function.searchwikipedianames=\u041f\u043e\u0448\u0443\u043a \u0441\u0442\u0430\u0442\u0435\u0439 \u0437 \u0412\u0456\u043a\u0456\u043f\u0435\u0434\u0456\u0457 \u0437\u0430 \u043d\u0430\u0437\u0432\u043e\u044e
+function.downloadosm=\u0417\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 OSM-\u0434\u0430\u043d\u0456 \u043d\u0430 \u0442\u0435\u0440\u0438\u0442\u043e\u0440\u0456\u044e
+function.duplicatepoint=\u041a\u043e\u043f\u0456\u044e\u0432\u0430\u0442\u0438 \u0442\u043e\u0447\u043a\u0443 \u0432 \u043a\u0456\u043d\u0435\u0446\u044c \u0442\u0440\u0435\u043a\u0443
+function.setcolours=\u0412\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u043a\u043e\u043b\u044c\u043e\u0440\u0438
+function.setlinewidth=\u0417\u0430\u0434\u0430\u0442\u0438 \u0448\u0438\u0440\u0438\u043d\u0443 \u043b\u0456\u043d\u0456\u0457
+function.setlanguage=\u0412\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u043c\u043e\u0432\u0443
+function.connecttopoint=\u041f\u0440\u0438\u043a\u0440\u0456\u043f\u0438\u0442\u0438 \u0434\u043e \u0442\u043e\u0447\u043a\u0438
+function.disconnectfrompoint=\u0412\u0456\u0434\u043a\u0440\u0456\u043f\u0438\u0442\u0438 \u0432\u0456\u0434 \u0442\u043e\u0447\u043a\u0438
+function.removephoto=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0443
+function.correlatephotos=\u0417\u0456\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0438 \u0437\u0430 \u0447\u0430\u0441\u043e\u043c
+function.rearrangephotos=\u0412\u043f\u043e\u0440\u044f\u0434\u043a\u0443\u0432\u0430\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0438 \u0437\u0430 \u0442\u0440\u0435\u043a\u043e\u043c
+function.rotatephotoleft=\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0443 \u043d\u0430 90\u00b0 \u0432\u043b\u0456\u0432\u043e
+function.rotatephotoright=\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0443 \u043d\u0430 90\u00b0 \u0432\u043f\u0440\u0430\u0432\u043e
+function.photopopup=\u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0443 \u0432 \u043e\u043a\u0440\u0435\u043c\u043e\u043c\u0443 \u0432\u0456\u043a\u043d\u0456
+function.ignoreexifthumb=\u0417\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u043f\u043e\u0432\u043d\u0443 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0443
+function.loadaudio=\u0414\u043e\u0434\u0430\u0442\u0438 \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441
+function.removeaudio=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441
+function.correlateaudios=\u0417\u0456\u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0438 \u0437\u0430 \u0447\u0430\u0441\u043e\u043c
+function.playaudio=\u041f\u0440\u043e\u0433\u0440\u0430\u0442\u0438 \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441
+function.stopaudio=\u0417\u0443\u043f\u0438\u043d\u0438\u0442\u0438 \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441
+function.help=\u0414\u043e\u043f\u043e\u043c\u043e\u0433\u0430
+function.showkeys=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u0433\u0430\u0440\u044f\u0447\u0456 \u043a\u043b\u0430\u0432\u0456\u0448\u0456
+function.about=\u041f\u0440\u043e GpsPrune
+function.checkversion=\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0438\u0442\u0438 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f
+function.saveconfig=\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f
+function.diskcache=\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u043c\u0430\u043f\u0438 \u043d\u0430 \u0434\u0438\u0441\u043a
+function.managetilecache=\u0423\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u043a\u0435\u0448\u0435\u043c
+
+# Dialogs
+dialog.exit.confirm.title=\u0412\u0438\u0445\u0456\u0434
+dialog.exit.confirm.text=\u0414\u0430\u043d\u0456 \u043d\u0435 \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u0456! \u041f\u0440\u043e\u0434\u043e\u0432\u0436\u0438\u0442\u0438?
+dialog.openappend.title=\u0414\u043e\u0434\u0430\u0442\u0438 \u0430\u0431\u043e \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u0437\u0430\u043c\u0456\u0441\u0442\u044c \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438\u0445.
+dialog.openappend.text=\u0414\u043e\u0434\u0430\u0442\u0438 \u0434\u043e \u043f\u043e\u0442\u043e\u0447\u043d\u0438\u0445 \u0434\u0430\u043d\u0438\u0445?
+dialog.deletepoint.title=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0443
+dialog.deletepoint.deletephoto=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0443 \u0437 \u0446\u0456\u0454\u0457 \u0442\u043e\u0447\u043a\u0438?
+dialog.deletephoto.title=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0443
+dialog.deletephoto.deletepoint=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0443 \u0437 \u0446\u0456\u0454\u0457 \u0447\u0432\u0456\u0442\u043b\u0438\u043d\u0438?
+dialog.deleteaudio.deletepoint=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0443 \u0437 \u0446\u044c\u043e\u0433\u043e \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0443?
+dialog.openoptions.title=\u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u043e\u043f\u0446\u0456\u0457
+dialog.openoptions.filesnippet=\u0424\u0440\u0430\u0433\u043c\u0435\u043d\u0442 \u0444\u0430\u0439\u043b\u0443
+dialog.load.table.field=\u041f\u043e\u043b\u0435
+dialog.load.table.datatype=\u0422\u0438\u043f \u0434\u0430\u043d\u0438\u0445
+dialog.load.table.description=\u041e\u043f\u0438\u0441
+dialog.delimiter.label=\u0420\u043e\u0437\u0434\u0456\u043b\u044c\u043d\u0438\u043a \u043f\u043e\u043b\u0456\u0432
+dialog.delimiter.comma=\u041a\u043e\u043c\u0430 ,
+dialog.delimiter.tab=\u0422\u0430\u0431\u0443\u043b\u044f\u0446\u0456\u044f
+dialog.delimiter.space=\u041f\u0440\u043e\u0431\u0456\u043b
+dialog.delimiter.semicolon=\u041a\u0440\u0430\u043f\u043a\u0430 \u0437 \u043a\u043e\u043c\u043e\u044e ;
+dialog.delimiter.other=\u0406\u043d\u0448\u0435
+dialog.openoptions.deliminfo.records=\u0437\u0430\u043f\u0438\u0441, \u0437
+dialog.openoptions.deliminfo.fields=\u043f\u043e\u043b\u0435
+dialog.openoptions.deliminfo.norecords=\u041d\u0435\u043c\u0430\u0454 \u0437\u0430\u043f\u0438\u0441\u0456\u0432
+dialog.openoptions.altitudeunits=\u041e\u0434\u0438\u043d\u0438\u0446\u0456 \u0432\u0438\u0441\u043e\u0442
+dialog.openoptions.speedunits=\u041e\u0434\u0438\u043d\u0438\u0446\u0456 \u0448\u0432\u0438\u0434\u043a\u043e\u0441\u0442\u0456
+dialog.openoptions.vertspeedunits=\u041e\u0434\u0438\u043d\u0438\u0446\u0456 \u0448\u0432\u0438\u0434\u043a\u043e\u0441\u0442\u0456 \u043f\u043e \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0456
+dialog.openoptions.vspeed.positiveup=\u041f\u043e\u0437\u0438\u0442\u0438\u0432\u043d\u0456 \u0448\u0432\u0438\u0434\u043a\u043e\u0441\u0442\u0456 \u0432\u0433\u043e\u0440\u0443
+dialog.openoptions.vspeed.positivedown=\u041f\u043e\u0437\u0438\u0442\u0438\u0432\u043d\u0456 \u0448\u0432\u0438\u0434\u043a\u043e\u0441\u0442\u0456 \u0432\u043d\u0438\u0437
+dialog.open.contentsdoubled=\u0426\u0435\u0439 \u0444\u0430\u0439\u043b \u043c\u0456\u0441\u0442\u0438\u0442\u044c \u0434\u0443\u0431\u043b\u044e\u0432\u0430\u043d\u043d\u044f \u0432 \u043a\u043e\u0436\u043d\u0456\u0439 \u0442\u043e\u0447\u0446\u0456, \n\u043e\u0434\u043d\u0430 \u044f\u043a \u0448\u043b\u044f\u0445\u043e\u0432\u0430 \u0442\u043e\u0447\u043a\u0430 \u0456 \u043e\u0434\u043d\u0430 \u044f\u043a \u0442\u0440\u0435\u043a\u043e\u0432\u0430 \u0442\u043e\u0447\u043a\u0430.
+dialog.selecttracks.intro=\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0440\u0435\u043a(-\u0438) \u0434\u043b\u044f \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0442\u044f
+dialog.selecttracks.noname=\u0411\u0435\u0437\u0456\u043c\u0435\u043d\u043d\u0438\u0439
+dialog.jpegload.subdirectories=\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0456\u0434\u0442\u0435\u043a\u0438
+dialog.jpegload.loadjpegswithoutcoords=\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0438 \u0431\u0435\u0437 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442
+dialog.jpegload.loadjpegsoutsidearea=\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u0438\u043d\u0438 \u0437\u0430 \u043c\u0435\u0436\u0430\u043c\u0438 \u043f\u043e\u0442\u043e\u0447\u043d\u043e\u0457 \u043e\u0431\u043b\u0430\u0441\u0442\u0456
+dialog.jpegload.progress.title=\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u0441\u0432\u0456\u0442\u043b\u0438\u043d
+dialog.jpegload.progress=\u0411\u0443\u0434\u044c-\u043b\u0430\u0441\u043a\u0430, \u0437\u0430\u0447\u0435\u043a\u0430\u0439\u0442\u0435, \u0439\u0434\u0435 \u043f\u043e\u0448\u0443\u043a \u0441\u0432\u0456\u0442\u043b\u0438\u043d
+dialog.gpsload.nogpsbabel=\u201egpsbabel\u201c \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e. \u041f\u0440\u043e\u0434\u043e\u0432\u0436\u0438\u0442\u0438?
+dialog.gpsload.device=\u0406\u043c\u2019\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e
+dialog.gpsload.format=\u0424\u043e\u0440\u043c\u0430\u0442
+dialog.gpsload.getwaypoints=\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0448\u043b\u044f\u0445\u043e\u0432\u0456 \u0442\u043e\u0447\u043a\u0438
+dialog.gpsload.gettracks=\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0442\u0440\u0435\u043a\u0438
+dialog.gpsload.save=\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u0434\u043e \u0444\u0430\u0439\u043b\u0443
+dialog.gpssend.sendwaypoints=\u0412\u0438\u0441\u043b\u0430\u0442\u0438 \u0448\u043b\u044f\u0445\u043e\u0432\u0456 \u0442\u043e\u0447\u043a\u0438
+dialog.gpssend.sendtracks=\u0412\u0438\u0441\u043b\u0430\u0442\u0438 \u0442\u0440\u0435\u043a\u0438
+dialog.gpssend.trackname=\u041d\u0430\u0437\u0432\u0430 \u0442\u0440\u0435\u043a\u0443
+dialog.gpsbabel.filters=\u0424\u0456\u043b\u044c\u0442\u0440\u0438
+dialog.addfilter.title=\u0414\u043e\u0434\u0430\u0442\u0438 \u0444\u0456\u043b\u044c\u0442\u0440
+dialog.gpsbabel.filter.discard=\u0412\u0456\u0434\u043a\u0438\u043d\u0443\u0442\u0438
+dialog.gpsbabel.filter.simplify=\u0421\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u0438
+dialog.gpsbabel.filter.distance=\u0412\u0456\u0434\u0441\u0442\u0430\u043d\u044c
+dialog.gpsbabel.filter.interpolate=\u0406\u043d\u0442\u0435\u0440\u043f\u043e\u043b\u044e\u0432\u0430\u0442\u0438
+dialog.gpsbabel.filter.discard.intro=\u0412\u0456\u0434\u043a\u0438\u043d\u0443\u0442\u0438 \u0442\u043e\u0447\u043a\u0438 \u044f\u043a\u0449\u043e
+dialog.gpsbabel.filter.discard.hdop=\u0413\u043e\u0440\u041f\u043e\u0433\u0456\u0440\u0448\u0422\u043e\u0447\u043d >
+dialog.gpsbabel.filter.discard.vdop=\u0412\u0435\u0440\u0442\u041f\u043e\u0433\u0456\u0440\u0448\u0422\u043e\u0447\u043d >
+dialog.gpsbabel.filter.discard.numsats=\u0427\u0438\u0441\u043b\u043e \u0441\u0443\u043f\u0443\u0442\u043d\u0438\u043a\u0456\u0432 <
+dialog.gpsbabel.filter.discard.nofix=\u0422\u043e\u0447\u043a\u0430 \u043d\u0435 \u043c\u0430\u0454 \u0432\u0438\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044f
+dialog.gpsbabel.filter.discard.unknownfix=\u0422\u043e\u0447\u043a\u0430 \u043c\u0430\u0454 \u043d\u0435\u0432\u0456\u0434\u043e\u043c\u0435 \u0432\u0438\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044f
+dialog.gpsbabel.filter.simplify.intro=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0438 \u0434\u043e
+dialog.gpsbabel.filter.simplify.maxpoints=\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0442\u043e\u0447\u043e\u043a <
+dialog.gpsbabel.filter.simplify.maxerror=\u0430\u0431\u043e \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u0432\u0456\u0434\u0441\u0442\u0430\u043d\u0456 <
+dialog.gpsbabel.filter.simplify.crosstrack=\u043f\u0435\u0440\u0435\u0445\u0440\u0435\u0449\u0435\u043d\u043d\u044f \u0442\u0440\u0435\u043a\u0456\u0432
+dialog.gpsbabel.filter.simplify.length=\u0440\u0456\u0437\u043d\u0438\u0446\u044f \u0432 \u0434\u043e\u0432\u0436\u0438\u043d\u0456
+dialog.gpsbabel.filter.simplify.relative=\u0432\u0456\u0434\u043d\u043e\u0441\u043d\u043e \u0413\u043e\u0440\u041f\u043e\u0433\u0456\u0440\u0448\u0422\u043e\u0447\u043d
+dialog.gpsbabel.filter.distance.intro=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0438, \u044f\u043a\u0449\u043e \u0431\u043b\u0438\u0437\u044c\u043a\u043e \u0434\u043e \u0431\u0443\u0434\u044c-\u044f\u043a\u043e\u0457 \u043f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u044c\u043e\u0457 \u0442\u043e\u0447\u043a\u0438
+dialog.gpsbabel.filter.distance.distance=\u042f\u043a\u0449\u043e \u0432\u0456\u0434\u0441\u0442\u0430\u043d\u044c <
+dialog.gpsbabel.filter.distance.time=\u0456 \u0440\u0456\u0437\u043d\u0438\u0446\u044f \u0432 \u0447\u0430\u0441\u0456 <
+dialog.gpsbabel.filter.interpolate.intro=\u0414\u043e\u0434\u0430\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u0442\u043e\u0447\u043a\u0438 \u043c\u0456\u0436 \u0442\u043e\u0447\u043e\u043a \u043d\u0430 \u0442\u0440\u0435\u043a\u0443
+dialog.gpsbabel.filter.interpolate.distance=\u042f\u043a\u0449\u043e \u0432\u0456\u0434\u0441\u0442\u0430\u043d\u044c >
+dialog.gpsbabel.filter.interpolate.time=\u0430\u0431\u043e \u0440\u0456\u0437\u043d\u0438\u0446\u044f \u0432 \u0447\u0430\u0441\u0456 >
+dialog.saveoptions.title=\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u0444\u0430\u0439\u043b
+dialog.save.fieldstosave=\u041f\u043e\u043b\u044f \u0434\u043b\u044f \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043d\u044f
+dialog.save.table.field=\u041f\u043e\u043b\u0435
+dialog.save.table.hasdata=\u041c\u0430\u0454 \u0434\u0430\u0442\u0443
+dialog.save.table.save=\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438
+dialog.save.headerrow=\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0440\u044f\u0434\u043a\u0430
+dialog.save.coordinateunits=\u041e\u0434\u0438\u043d\u0438\u0446\u0456 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442
+dialog.save.altitudeunits=\u041e\u0434\u0438\u043d\u0438\u0446\u0456 \u0432\u0438\u0441\u043e\u0442\u0438
+dialog.save.timestampformat=\u0424\u043e\u0440\u043c\u0430\u0442 \u0447\u0430\u0441\u0443
+dialog.save.overwrite.title=\u0424\u0430\u0439\u043b \u0432\u0436\u0435 \u0456\u0441\u043d\u0443\u0454
+dialog.save.overwrite.text=\u0424\u0430\u0439\u043b \u0432\u0436\u0435 \u0456\u0441\u043d\u0443\u0454. \u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0439\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u0442\u0438?
+dialog.save.notypesselected=\u041d\u0435 \u0432\u0438\u0431\u0440\u0430\u043d\u043e \u0442\u0438\u043f \u0442\u043e\u0447\u043e\u043a
+dialog.exportkml.text=\u043e\u043f\u0438\u0441 \u0434\u043e \u0434\u0430\u043d\u0438\u0445
+dialog.exportkml.altitude=\u0410\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u0456 \u0432\u0438\u0441\u043e\u0442\u0438 (\u0434\u043b\u044f \u0430\u0432\u0456\u0430\u0446\u0456\u0457)
+dialog.exportkml.kmz=\u0421\u0442\u0438\u0441\u043d\u0435\u043d\u043d\u044f \u0434\u043b\u044f kmz-\u0444\u0430\u0439\u043b\u0443
+dialog.exportkml.exportimages=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0435\u0441\u043a\u0456\u0437\u0443 \u0432 kmz
+dialog.exportkml.trackcolour=\u041a\u043e\u043b\u0456\u0440 \u0442\u0440\u0435\u043a\u0443
+
+# External urls
+url.googlemaps=maps.google.com.ua
+wikipedia.lang=uk
+openweathermap.lang=ua
+
+# Below here is still Russian
+#############################
+
diff --git a/tim/prune/lang/prune-texts_zh.properties b/tim/prune/lang/prune-texts_zh.properties
index 2a605bf..a7576e0 100644
--- a/tim/prune/lang/prune-texts_zh.properties
+++ b/tim/prune/lang/prune-texts_zh.properties
@@ -7,6 +7,7 @@ menu.file.addphotos=\u6dfb\u52a0\u7167\u7247
 menu.file.recentfiles=\u6700\u8fd1\u6253\u5f00\u8fc7\u6587\u4ef6
 menu.file.save=\u4fdd\u5b58
 menu.file.exit=\u9000\u51fa
+menu.online=\u8054\u7f51
 menu.track=\u8f68\u8ff9
 menu.track.undo=\u64a4\u9500
 menu.track.clearundo=\u6e05\u9664\u64a4\u9500\u6e05\u5355
@@ -57,6 +58,7 @@ menu.map.editmode=\u7f16\u8f91\u6a21\u5f0f
 
 # Alt keys for menus
 altkey.menu.file=F
+altkey.menu.online=N
 altkey.menu.track=T
 altkey.menu.range=R
 altkey.menu.point=P
@@ -104,9 +106,12 @@ 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.setpaths=\u8bbe\u7f6e\u7a0b\u5e8f\u8def\u5f84
+function.splitsegments=\u5206\u5272\u8f68\u8ff9
+function.sewsegments=\u63a5\u5408\u8f68\u8ff9\u7247\u6bb5
 function.getgpsies=\u83b7\u53d6Gpsies\u8f68\u8ff9
 function.uploadgpsies=\u4e0a\u4f20\u8f68\u8ff9\u5230Gpsies
 function.lookupsrtm=\u4eceSRTM\u83b7\u5f97\u9ad8\u5ea6\u4fe1\u606f
+function.downloadsrtm=\u4e0b\u8f7dSRTM\u6570\u636e
 function.getwikipedia=\u7ef4\u57fa\u767e\u79d1\u6709\u5173\u672c\u5730\u6587\u7ae0
 function.searchwikipedianames=\u6309\u540d\u5b57\u4ece\u7ef4\u57fa\u767e\u79d1\u67e5\u627e
 function.downloadosm=\u4e0b\u8f7d\u6b64\u5730OSM\u6570\u636e
@@ -135,6 +140,7 @@ function.checkversion=\u68c0\u67e5\u66f4\u65b0
 function.saveconfig=\u4fdd\u5b58\u8bbe\u7f6e
 function.diskcache=\u4fdd\u5b58\u5730\u56fe
 function.managetilecache=\u7ba1\u7406\u5730\u56fe\u533a\u57df\u6570\u636e\u7f13\u5b58
+function.getweatherforecast=\u83b7\u53d6\u5929\u6c14\u9884\u62a5
 
 # Dialogs
 dialog.exit.confirm.title=\u9000\u51fa
@@ -241,7 +247,9 @@ 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.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.3d.warningtracksize=\u8f68\u8ff9\u542b\u6709\u592a\u591a\u822a\u70b9\uff0cJAVA3D\u53ef\u80fd\u65e0\u6cd5\u663e\u793a\n\u662f\u5426\u7ee7\u7eed\uff1f
+dialog.3d.useterrain=\u663e\u793a\u5730\u5f62
+dialog.3d.terraingridsize=\u7f51\u683c\u5927\u5c0f
 dialog.exportpov.baseimage=\u57fa\u7840\u56fe
 dialog.exportpov.cannotmakebaseimage=\u65e0\u6cd5\u4fdd\u5b58\u57fa\u7840\u56fe
 dialog.baseimage.title=\u8bbe\u7f6e\u57fa\u7840\u56fe
@@ -257,6 +265,7 @@ 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.drawtrackpoints=\u7ed8\u51fa\u5404\u8f68\u8ff9\u70b9
 dialog.exportimage.textscalepercent=\u6587\u5b57\u7f29\u653e\u6bd4\u4f8b (%)
 dialog.pointtype.desc=\u4fdd\u5b58\u4e0b\u5217\u70b9\uff1a
 dialog.pointtype.track=\u8f68\u8ff9\u70b9
@@ -265,9 +274,9 @@ dialog.pointtype.photo=\u7167\u7247\u70b9
 dialog.pointtype.audio=\u5e26\u58f0\u97f3\u7684\u822a\u70b9
 dialog.pointtype.selection=\u4ec5\u5df2\u9009\u62e9\u822a\u6bb5
 dialog.confirmreversetrack.title=\u786e\u8ba4\u53cd\u8f6c
-dialog.confirmreversetrack.text=\u8f68\u8ff9\u5305\u542b\u65f6\u95f4\u4fe1\u606f\uff0c\u53cd\u8f6c\u540e\u53ef\u80fd\u4e22\u5931\u3002\n\u662f\u5426\u7ee7\u7eed\uff1f
+dialog.confirmreversetrack.text=\u8f68\u8ff9\u5305\u542b\u65f6\u95f4\u4fe1\u606f\uff0c\u53cd\u8f6c\u540e\u53ef\u80fd\u4e22\u5931\n\u662f\u5426\u7ee7\u7eed\uff1f
 dialog.confirmcutandmove.title=\u786e\u8ba4\u526a\u5207\u548c\u79fb\u52a8
-dialog.confirmcutandmove.text=\u8f68\u8ff9\u5305\u542b\u65f6\u95f4\u4fe1\u606f\uff0c\u79fb\u52a8\u540e\u53ef\u80fd\u4e22\u5931\u3002\n\u662f\u5426\u7ee7\u7eed\uff1f
+dialog.confirmcutandmove.text=\u8f68\u8ff9\u5305\u542b\u65f6\u95f4\u4fe1\u606f\uff0c\u79fb\u52a8\u540e\u53ef\u80fd\u4e22\u5931\n\u662f\u5426\u7ee7\u7eed\uff1f
 dialog.interpolate.parameter.text=\u6240\u9009\u4e24\u70b9\u4e2d\u63d2\u5165\u70b9\u7684\u4e2a\u6570
 dialog.interpolate.betweenwaypoints=\u521b\u5efa\u4e2d\u7ee7\u822a\u70b9\uff1f
 dialog.undo.title=\u64a4\u9500\u64cd\u4f5c
@@ -372,13 +381,12 @@ dialog.gpsies.activity.skating=\u6ed1\u51b0
 dialog.wikipedia.column.name=\u6587\u7ae0\u9898\u76ee
 dialog.wikipedia.column.distance=\u8ddd\u79bb
 dialog.correlate.notimestamps=\u6570\u636e\u70b9\u4e2d\u65e0\u65f6\u95f4\u4fe1\u606f\uff0c\u7167\u7247\u65e0\u6cd5\u94fe\u63a5
-dialog.correlate.nouncorrelatedphotos=\u6240\u6709\u7167\u7247\u5df2\u94fe\u63a5\u3002\u7ee7\u7eed\uff1f
-dialog.correlate.nouncorrelatedaudios=\u6240\u6709\u97f3\u9891\u5df2\u94fe\u63a5\u3002\u7ee7\u7eed\uff1f
+dialog.correlate.nouncorrelatedphotos=\u6240\u6709\u7167\u7247\u5df2\u94fe\u63a5\uff0c\u7ee7\u7eed\uff1f
+dialog.correlate.nouncorrelatedaudios=\u6240\u6709\u97f3\u9891\u5df2\u94fe\u63a5\uff0c\u7ee7\u7eed\uff1f
 dialog.correlate.photoselect.intro=\u9009\u62e9\u5df2\u94fe\u63a5\u7167\u7247\u4f5c\u4e3a\u65f6\u95f4\u504f\u79fb
 dialog.correlate.select.photoname=\u7167\u7247\u540d
 dialog.correlate.select.timediff=\u65f6\u95f4\u5dee
 dialog.correlate.select.photolater=\u7167\u7247\u5ef6\u540e
-dialog.correlate.options.tip=\u63d0\u793a\uff1a\u624b\u52a8\u94fe\u63a5\u81f3\u5c11\u4e00\u5f20\u7167\u7247\uff0c\u53ef\u81ea\u52a8\u8ba1\u7b97\u65f6\u95f4\u504f\u79fb
 dialog.correlate.options.intro=\u9009\u62e9\u81ea\u52a8\u94fe\u63a5\u8bbe\u7f6e
 dialog.correlate.options.offsetpanel=\u65f6\u95f4\u504f\u79fb
 dialog.correlate.options.offset=\u504f\u79fb
@@ -395,7 +403,7 @@ dialog.correlate.options.timelimit=\u65f6\u95f4\u9650\u5236
 dialog.correlate.options.nodistancelimit=\u65e0\u8ddd\u79bb\u9650\u5236
 dialog.correlate.options.distancelimit=\u8ddd\u79bb\u9650\u5236
 dialog.correlate.options.correlate=\u5173\u8054
-dialog.correlate.alloutsiderange=\u65e0\u6cd5\u94fe\u63a5\uff0c\u6240\u6709\u7167\u7247\u8d85\u51fa\u8f68\u8ff9\u65f6\u95f4\u8303\u56f4\u3002\n\u8bf7\u6539\u53d8\u65f6\u95f4\u504f\u79fb\u6216\u624b\u52a8\u94fe\u63a5\u81f3\u5c11\u4e00\u5f20\u5728\u7167\u7247\u3002
+dialog.correlate.alloutsiderange=\u65e0\u6cd5\u94fe\u63a5\uff0c\u6240\u6709\u7167\u7247\u8d85\u51fa\u8f68\u8ff9\u65f6\u95f4\u8303\u56f4\n\u8bf7\u6539\u53d8\u65f6\u95f4\u504f\u79fb\u6216\u624b\u52a8\u94fe\u63a5\u81f3\u5c11\u4e00\u5f20\u5728\u7167\u7247
 dialog.correlate.filetimes=\u6587\u4ef6\u65f6\u95f4\u8868\u793a\u58f0\u97f3\u7684\uff1a
 dialog.correlate.filetimes2=\u90e8\u5206
 dialog.correlate.correltimes=\u5982\u8981\u5173\u8054\uff0c\u8bf7\u4f7f\u7528\uff1a
@@ -421,8 +429,7 @@ dialog.compress.duplicates.title=\u91cd\u590d\u70b9\u5220\u9664
 dialog.compress.douglaspeucker.title=Douglas-Peucker \u538b\u7f29
 dialog.compress.douglaspeucker.paramdesc=\u95f4\u8ddd\u7cfb\u6570
 dialog.compress.summarylabel=\u8981\u5220\u9664\u7684\u70b9
-dialog.compress.confirm1=\u5df2\u6807\u8bb0
-dialog.compress.confirm2=\u70b9\u3002\n\u70b9\u51fb \u8f68\u8ff9->\u5220\u9664 \u5220\u9664\u8fd9\u4e9b\u70b9
+dialog.compress.confirm=\u5df2\u6807\u8bb0 %d \u70b9\n\u70b9\u51fb \u8f68\u8ff9->\u5220\u9664 \u5220\u9664\u8fd9\u4e9b\u70b9
 dialog.compress.confirmnone=\u672a\u6807\u8bb0\u4efb\u4f55\u70b9
 dialog.deletemarked.nonefound=\u65e0\u6cd5\u5220\u9664\u6570\u636e\u70b9
 dialog.pastecoordinates.desc=\u5728\u6b64\u8f93\u5165\u6216\u7c98\u8d34\u5750\u6807\u70b9
@@ -530,13 +537,31 @@ dialog.diskcache.tileset.multiple=\u6570\u76ee
 dialog.diskcache.deleteold=\u5220\u9664\u65e7\u5730\u56fe\u5757
 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.diskcache.deleted=\u5df2\u5220\u9664 %d \u7f13\u5b58\u5185\u6587\u4ef6
 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:
+dialog.weather.location=\u5730\u70b9
+dialog.weather.update=\u5929\u6c14\u9884\u62a5\u66f4\u65b0
+dialog.weather.sunrise=\u65e5\u51fa
+dialog.weather.sunset=\u65e5\u843d
+dialog.weather.temperatureunits=\u6e29\u5ea6
+dialog.weather.currentforecast=\u5929\u6c14\u73b0\u72b6
+dialog.weather.dailyforecast=\u9010\u65e5\u9884\u62a5
+dialog.weather.3hourlyforecast=\u4e09\u5c0f\u65f6\u9884\u62a5
+dialog.weather.day.now=\u5929\u6c14\u73b0\u72b6
+dialog.weather.day.today=\u4eca\u65e5
+dialog.weather.day.tomorrow=\u660e\u65e5
+dialog.weather.day.monday=\u5468\u4e00
+dialog.weather.day.tuesday=\u5468\u4e8c
+dialog.weather.day.wednesday=\u5468\u4e09
+dialog.weather.day.thursday=\u5468\u56db
+dialog.weather.day.friday=\u5468\u4e94
+dialog.weather.day.saturday=\u5468\u516d
+dialog.weather.day.sunday=\u5468\u65e5
+dialog.weather.creditnotice=\u5929\u6c14\u4fe1\u606f\u83b7\u53d6\u81eaopenweathermap.org\uff0c\u83b7\u53d6\u66f4\u591a\u5929\u6c14\u8be6\u60c5\u8bf7\u8bbf\u95ee\u7f51\u7ad9\u3002
 
 # 3d window
 dialog.3d.title=GpsPrune 3D \u663e\u793a
@@ -555,11 +580,12 @@ confirm.addtimeoffset=\u5df2\u52a0\u4e0a\u65f6\u95f4\u504f\u5dee
 confirm.addaltitudeoffset=\u5df2\u52a0\u4e0a\u9ad8\u5ea6\u504f\u5dee
 confirm.rearrangewaypoints=\u91cd\u65b0\u914d\u7f6e\u7684\u822a\u70b9
 confirm.rearrangephotos=\u7167\u7247\u5df2\u91cd\u6392
+confirm.splitsegments=\u8f68\u8ff9\u5df2\u5206\u5272\u4e3a %d \u6bb5
+confirm.sewsegments=%d \u8f68\u8ff9\u6bb5\u5df2\u5408\u5e76
 confirm.cutandmove=\u5df2\u79fb\u52a8\u7684\u8f68\u8ff9\u6bb5
 confirm.interpolate=\u8f68\u8ff9\u70b9\u5df2\u6dfb\u52a0
 confirm.convertnamestotimes=\u822a\u70b9\u540d\u79f0\u5df2\u8f6c\u6362
-confirm.saveexif.ok1=\u5df2\u4fdd\u5b58
-confirm.saveexif.ok2=\u7167\u7247\u6587\u4ef6
+confirm.saveexif.ok=\u5df2\u4fdd\u5b58 %d \u7167\u7247\u6587\u4ef6
 confirm.undo.single=\u5df2\u64a4\u9500\u7684\u64cd\u4f5c
 confirm.undo.multi=\u5df2\u64a4\u9500\u7684\u64cd\u4f5c
 confirm.jpegload.single=\u5df2\u52a0\u5165\u7167\u7247
@@ -573,13 +599,23 @@ confirm.correlatephotos.multi=\u7167\u7247\u5df2\u94fe\u63a5
 confirm.createpoint=\u5df2\u521b\u5efa\u70b9
 confirm.rotatephoto=\u7167\u7247\u5df2\u65cb\u8f6c
 confirm.running=\u8bf7\u7a0d\u7b49...
-confirm.lookupsrtm1=\u627e\u5230
-confirm.lookupsrtm2=\u9ad8\u5ea6\u503c
+confirm.lookupsrtm=\u627e\u5230 %d \u9ad8\u5ea6\u503c
+confirm.downloadsrtm=\u4e0b\u8f7d %d \u9ad8\u5ea6\u6587\u4ef6\u5230\u7f13\u5b58\u4e2d
+confirm.downloadsrtm.1=\u4e0b\u8f7d %d \u9ad8\u5ea6\u6587\u4ef6\u5230\u7f13\u5b58\u4e2d
+confirm.downloadsrtm.none=\u65e0\u9700\u4e0b\u8f7d\uff0c\u6587\u4ef6\u5df2\u5b58\u50a8\u5728\u7f13\u5b58\u4e2d
 confirm.deletefieldvalues=\u533a\u57df\u6570\u636e\u5df2\u5220\u9664
 confirm.audioload=\u5df2\u6dfb\u52a0\u58f0\u97f3\u6587\u4ef6
 confirm.correlateaudios.single=\u58f0\u97f3\u5df2\u5173\u8054
 confirm.correlateaudios.multi=\u58f0\u97f3\u5df2\u5173\u8054
 
+# Tips, shown just once when appropriate
+tip.title=\u63d0\u793a
+tip.useamapcache=\u542f\u7528\u78c1\u76d8\u7f13\u5b58 (\u8bbe\u7f6e -> \u4fdd\u5b58\u5730\u56fe)\n\u53ef\u4ee5\u63d0\u9ad8\u663e\u793a\u901f\u5ea6\uff0c\u51cf\u5c11\u7f51\u7edc\u6d41\u91cf
+tip.learntimeparams=\u5bf9\u8bb0\u5f55\u7684\u8f68\u8ff9\u542f\u7528 \u8f68\u8ff9 -> \u4f7f\u7528\u5f53\u524d\u8f68\u8ff9\u53c2\u6570\u4f30\u8ba1\u65f6\u95f4\n\u5c06\u4f7f\u7ed3\u679c\u66f4\u52a0\u7cbe\u786e
+tip.downloadsrtm=\u53ef\u4ee5\u70b9\u51fb \u8054\u7f51 -> \u4e0b\u8f7dSRTM\u6570\u636e\n\u5c06\u6570\u636e\u4fdd\u5b58\u5230\u78c1\u76d8\u7f13\u5b58\u4ee5\u63d0\u9ad8\u901f\u5ea6
+tip.usesrtmfor3d=\u6b64\u8f68\u8ff9\u6ca1\u6709\u9ad8\u5ea6\u4fe1\u606f\n\u53ef\u4ee5\u901a\u8fc7SRTM\u83b7\u53d6\u5927\u81f4\u9ad8\u5ea6\u4ee5\u663e\u793a3D\u89c6\u56fe
+tip.manuallycorrelateone=\u63d0\u793a\uff1a\u624b\u52a8\u94fe\u63a5\u81f3\u5c11\u4e00\u5f20\u7167\u7247\uff0c\u53ef\u81ea\u52a8\u8ba1\u7b97\u65f6\u95f4\u504f\u79fb
+
 # Buttons
 button.ok=\u786e\u5b9a
 button.back=\u8fd4\u56de
@@ -597,6 +633,7 @@ button.yes=\u662f
 button.no=\u5426
 button.yestoall=\u5168\u90e8\u662f
 button.notoall=\u5168\u90e8\u5426
+button.always=\u603b\u662f
 button.select=\u9009\u62e9
 button.selectall=\u5168\u9009
 button.selectnone=\u5168\u4e0d\u9009
@@ -685,7 +722,6 @@ fieldname.newsegment=\u6bb5
 fieldname.custom=\u7528\u6237
 fieldname.prefix=\u6570\u636e\u6bb5
 fieldname.distance=\u8ddd\u79bb
-fieldname.movingdistance=\u79fb\u52a8\u8ddd\u79bb
 fieldname.duration=\u65f6\u957f
 fieldname.speed=\u901f\u5ea6
 fieldname.verticalspeed=\u5782\u76f4\u901f\u5ea6
@@ -720,6 +756,10 @@ units.degminsec=\u5ea6-\u5206-\u79d2
 units.degmin=\u5ea6-\u5206
 units.deg=\u5ea6
 units.iso8601=ISO 8601
+units.degreescelsius=\u6444\u6c0f\u5ea6
+units.degreescelsius.short=\u00b0C
+units.degreesfahrenheit=\u534e\u6c0f\u5ea6
+units.degreesfahrenheit.short=\u00b0F
 
 # How to combine conditions, such as filters
 logic.and=\u4e0e
@@ -728,6 +768,7 @@ logic.or=\u6216
 # External urls
 url.googlemaps=ditu.google.cn
 wikipedia.lang=zh
+openweathermap.lang=zh_cn
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -749,6 +790,8 @@ undo.deletemarked=\u538b\u7f29\u8f68\u8ff9
 undo.insert=\u63d2\u5165\u822a\u70b9
 undo.reverse=\u53cd\u8f6c\u6bb5
 undo.mergetracksegments=\u5408\u5e76\u6bb5
+undo.splitsegments=\u5206\u5272\u8f68\u8ff9
+undo.sewsegments=\u5408\u5e76\u8f68\u8ff9\u7247\u6bb5
 undo.addtimeoffset=\u6dfb\u52a0\u65f6\u95f4\u504f\u79fb
 undo.addaltitudeoffset=\u52a0\u5165\u9ad8\u5ea6\u504f\u79fb
 undo.rearrangewaypoints=\u91cd\u65b0\u914d\u7f6e\u822a\u70b9
@@ -770,11 +813,9 @@ error.save.nodata=\u65e0\u6570\u636e\u4fdd\u5b58
 error.save.failed=\u5411\u6587\u4ef6\u4fdd\u5b58\u6570\u636e\u5931\u8d25
 error.saveexif.filenotfound=\u627e\u4e0d\u5230\u7167\u7247\u6587\u4ef6
 error.saveexif.cannotoverwrite1=\u7167\u7247
-error.saveexif.cannotoverwrite2=\u662f\u53ea\u8bfb\u6587\u4ef6\u3002\u4fdd\u5b58\u526f\u672c\uff1f
-error.saveexif.failed1=\u65e0\u6cd5\u4fdd\u5b58
-error.saveexif.failed2=\u5f20\u7167\u7247
-error.saveexif.forced1=
-error.saveexif.forced2=\u5f20\u7167\u7247\u9700\u8981\u5f3a\u5236\u6267\u884c
+error.saveexif.cannotoverwrite2=\u662f\u53ea\u8bfb\u6587\u4ef6\uff0c\u4fdd\u5b58\u526f\u672c\uff1f
+error.saveexif.failed=\u65e0\u6cd5\u4fdd\u5b58 %d \u5f20\u7167\u7247
+error.saveexif.forced=%d \u5f20\u7167\u7247\u9700\u8981\u5f3a\u5236\u6267\u884c
 error.load.dialogtitle=\u5bfc\u5165\u6570\u636e\u9519\u8bef
 error.load.noread=\u65e0\u6cd5\u8bfb\u6587\u4ef6
 error.load.nopoints=\u6587\u4ef6\u4e2d\u65e0\u5750\u6807\u4fe1\u606f
@@ -785,7 +826,7 @@ error.jpegload.dialogtitle=\u5bfc\u5165\u7167\u7247\u9519\u8bef
 error.jpegload.nofilesfound=\u627e\u4e0d\u5230\u6587\u4ef6
 error.jpegload.nojpegsfound=\u627e\u4e0d\u5230Jpeg\u6587\u4ef6
 error.jpegload.nogpsfound=\u627e\u4e0d\u5230GPS\u4fe1\u606f
-error.jpegload.exifreadfailed=Exif\u8bfb\u53d6\u9519\u8bef\u3002\u9700\u8981\u5185\u90e8\u6216\u8005\u5916\u90e8\u5e93\u624d\u80fd\u8bfb\u53d6
+error.jpegload.exifreadfailed=Exif\u8bfb\u53d6\u9519\u8bef\n\u9700\u8981\u5185\u90e8\u6216\u8005\u5916\u90e8\u5e93\u624d\u80fd\u8bfb\u53d6
 error.audioload.nofilesfound=\u672a\u627e\u5230\u58f0\u97f3\u6587\u4ef6
 error.gpsload.unknown=\u672a\u77e5\u9519\u8bef
 error.undofailed.title=\u64a4\u9500\u5931\u8d25
@@ -810,4 +851,7 @@ 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
+error.learnestimationparams.failed=\u65e0\u6cd5\u4ece\u6b64\u8f68\u8ff9\u5f97\u5230\u53c2\u6570\n \u5c1d\u8bd5\u8f7d\u5165\u66f4\u591a\u8f68\u8ff9
+error.tracksplit.nosplit=\u6b64\u8f68\u8ff9\u65e0\u6cd5\u5206\u5272
+error.downloadsrtm.nocache=\u6587\u4ef6\u65e0\u6cd5\u4fdd\u5b58\n\u8bf7\u68c0\u67e5\u78c1\u76d8\u7f13\u5b58
+error.sewsegments.nothingdone=\u8f68\u8ff9\u7247\u6bb5\u65e0\u6cd5\u5408\u5e76\n\u8be5\u8f68\u8ff9\u73b0\u5305\u542b %d \u4e2a\u7247\u6bb5
diff --git a/tim/prune/load/BabelFileFormats.java b/tim/prune/load/BabelFileFormats.java
index 2d1c9e8..39359c1 100644
--- a/tim/prune/load/BabelFileFormats.java
+++ b/tim/prune/load/BabelFileFormats.java
@@ -11,7 +11,7 @@ public abstract class BabelFileFormats
 	/**
 	 * @return an object array for the format descriptions
 	 */
-	public static Object[] getDescriptions() {
+	public static String[] getDescriptions() {
 		return getColumn(0);
 	}
 
@@ -26,8 +26,10 @@ public abstract class BabelFileFormats
 		{
 			final String[] suffixes = getColumn(2);
 			for (int i=0; i<suffixes.length; i++)
+			{
 				if (suffixes[i] != null && suffixes[i].equalsIgnoreCase(inSuffix))
 					return i;
+			}
 		}
 		return -1;
 	}
diff --git a/tim/prune/load/BabelLoadFromFile.java b/tim/prune/load/BabelLoadFromFile.java
index 1897e0c..23ce909 100644
--- a/tim/prune/load/BabelLoadFromFile.java
+++ b/tim/prune/load/BabelLoadFromFile.java
@@ -41,7 +41,7 @@ public class BabelLoadFromFile extends BabelLoader
 	// Label for filename
 	private JLabel _inputFileLabel = null;
 	// Dropdown for format of file
-	private JComboBox _formatDropdown = null;
+	private JComboBox<String> _formatDropdown = null;
 	// Last used file suffix
 	private String _lastSuffix = null;
 
@@ -133,7 +133,7 @@ public class BabelLoadFromFile extends BabelLoader
 		grid.add(_inputFileLabel);
 		JLabel formatLabel = new JLabel(I18nManager.getText("dialog.gpsload.format"));
 		grid.add(formatLabel);
-		_formatDropdown = new JComboBox(BabelFileFormats.getDescriptions());
+		_formatDropdown = new JComboBox<String>(BabelFileFormats.getDescriptions());
 		grid.add(_formatDropdown);
 		gridPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
 		gridPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 20));
@@ -201,19 +201,33 @@ public class BabelLoadFromFile extends BabelLoader
 	}
 
 	/**
+	 * @return the suffix of the selected filename
+	 */
+	private String getSelectedSuffix()
+	{
+		String filename = _inputFile.getName();
+		if (filename == null) {return "";}
+		int dotPos = filename.lastIndexOf('.');
+		return (dotPos > 0 ? filename.substring(dotPos) : "");
+	}
+
+	/**
 	 * Initialise dialog
 	 */
 	protected void initDialog()
 	{
 		_inputFileLabel.setText(_inputFile.getName());
 		// Get suffix of filename and compare with previous one
-		String filename = _inputFile.getName();
-		int dotPos = filename.lastIndexOf('.');
-		String suffix = (dotPos > 0 ? filename.substring(dotPos) : null);
-		if (suffix != null && !suffix.equals(".") && (_lastSuffix == null || !suffix.equalsIgnoreCase(_lastSuffix)))
+		String suffix = getSelectedSuffix();
+		if (_lastSuffix == null || !suffix.equalsIgnoreCase(_lastSuffix))
 		{
-			// New suffix chosen, so select first appropriate format (if any)
+			// New suffix has been chosen, so select first appropriate format (if any)
 			int selIndex = BabelFileFormats.getIndexForFileSuffix(suffix);
+			if (selIndex < 0)
+			{
+				// Use the previous one from the Config (if any)
+				selIndex = Config.getConfigInt(Config.KEY_IMPORT_FILE_FORMAT);
+			}
 			if (selIndex >= 0) {
 				_formatDropdown.setSelectedIndex(selIndex);
 			}
@@ -226,10 +240,15 @@ public class BabelLoadFromFile extends BabelLoader
 	 */
 	protected void saveConfigValues()
 	{
-		// Save the filter string (but don't remove it if it's now blank)
+		// Save the filter string, clear it if it's now blank
 		final String filter = _filterPanel.getFilterString();
-		if (filter != null && !filter.equals("")) {
-			Config.setConfigString(Config.KEY_GPSBABEL_FILTER, filter);
+		Config.setConfigString(Config.KEY_GPSBABEL_FILTER, filter);
+
+		// Check if there is a standard file type for the selected suffix
+		int selIndex = BabelFileFormats.getIndexForFileSuffix(getSelectedSuffix());
+		// If there is none, then get the index which the user chose and set in the Config
+		if (selIndex < 0) {
+			Config.setConfigInt(Config.KEY_IMPORT_FILE_FORMAT, _formatDropdown.getSelectedIndex());
 		}
 	}
 }
diff --git a/tim/prune/load/FileCacher.java b/tim/prune/load/FileCacher.java
index da354aa..a151c2e 100644
--- a/tim/prune/load/FileCacher.java
+++ b/tim/prune/load/FileCacher.java
@@ -47,7 +47,9 @@ public class FileCacher
 				}
 				while (currLine != null)
 				{
-					if (currLine.indexOf('\0') >= 0) {
+					if (currLine.indexOf('\0') >= 0)
+					{
+						reader.close();
 						return; // it's a binary file, shouldn't use this cacher
 					}
 					if (currLine.trim().length() > 0)
diff --git a/tim/prune/load/MediaLoadProgressDialog.java b/tim/prune/load/MediaLoadProgressDialog.java
index a052b55..9372704 100644
--- a/tim/prune/load/MediaLoadProgressDialog.java
+++ b/tim/prune/load/MediaLoadProgressDialog.java
@@ -1,32 +1,17 @@
 package tim.prune.load;
 
-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.JPanel;
-import javax.swing.JProgressBar;
 
-import tim.prune.I18nManager;
 import tim.prune.function.Cancellable;
+import tim.prune.gui.GenericProgressDialog;
 
 /**
  * Class to show a progress dialog for loading media.
- * Used for regular photo / audio loads plus the async
- * loading function.
+ * Used for regular photo / audio loads plus the async loading function.
+ * Maybe this class isn't really needed...
  */
-public class MediaLoadProgressDialog
+public class MediaLoadProgressDialog extends GenericProgressDialog
 {
-	private JDialog _progressDialog   = null;
-	private JProgressBar _progressBar = null;
-	private JFrame _parentFrame = null;
-	private Cancellable _function = null;
-
 	/**
 	 * Constructor
 	 * @param inParentFrame parent frame for creating dialog
@@ -34,74 +19,6 @@ public class MediaLoadProgressDialog
 	 */
 	public MediaLoadProgressDialog(JFrame inParentFrame, Cancellable inFunction)
 	{
-		_parentFrame = inParentFrame;
-		_function = inFunction;
-	}
-
-	/**
-	 * Create the dialog to show the progress
-	 */
-	private void createProgressDialog()
-	{
-		_progressDialog = new JDialog(_parentFrame, I18nManager.getText("dialog.jpegload.progress.title"));
-		_progressDialog.setLocationRelativeTo(_parentFrame);
-		_progressBar = new JProgressBar(0, 100);
-		_progressBar.setValue(0);
-		_progressBar.setStringPainted(true);
-		_progressBar.setString("");
-		JPanel panel = new JPanel();
-		panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
-		panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
-		panel.add(new JLabel(I18nManager.getText("dialog.jpegload.progress")));
-		panel.add(_progressBar);
-		JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
-		cancelButton.addActionListener(new ActionListener() {
-			public void actionPerformed(ActionEvent e)
-			{
-				_function.cancel();
-			}
-		});
-		panel.add(cancelButton);
-		_progressDialog.getContentPane().add(panel);
-		_progressDialog.pack();
-		_progressDialog.setVisible(true);
-	}
-
-	/**
-	 * Show the dialog in indeterminate mode, before limits are calculated
-	 */
-	public void show()
-	{
-		if (_progressDialog == null)
-		{
-			createProgressDialog();
-			_progressBar.setIndeterminate(true);
-		}
-	}
-
-	/**
-	 * Update the progress bar
-	 * @param inCurrent current value
-	 * @param inMax maximum value
-	 */
-	public void showProgress(int inCurrent, int inMax)
-	{
-		if (_progressDialog == null)
-			createProgressDialog();
-		if (_progressBar.isIndeterminate())
-			_progressBar.setIndeterminate(false);
-		if (inMax > 0)
-			_progressBar.setMaximum(inMax);
-		_progressBar.setValue(inCurrent);
-		_progressBar.setString("" + inCurrent + " / " + _progressBar.getMaximum());
-	}
-
-	/**
-	 * Close the dialog
-	 */
-	public void close()
-	{
-		if (_progressDialog != null)
-			_progressDialog.dispose();
+		super("dialog.jpegload.progress.title", "dialog.jpegload.progress", inParentFrame, inFunction);
 	}
 }
diff --git a/tim/prune/load/TextFileLoader.java b/tim/prune/load/TextFileLoader.java
index 53d6d2a..8fd30b2 100644
--- a/tim/prune/load/TextFileLoader.java
+++ b/tim/prune/load/TextFileLoader.java
@@ -49,13 +49,13 @@ public class TextFileLoader
 	private JLabel _statusLabel = null;
 	private DelimiterInfo[] _delimiterInfos = null;
 	private FileCacher _fileCacher = null;
-	private JList _snippetBox = null;
+	private JList<String> _snippetBox = null;
 	private FileExtractTableModel _fileExtractTableModel = null;
 	private JTable _fieldTable;
 	private FieldSelectionTableModel _fieldTableModel = null;
-	private JComboBox _altitudeUnitsDropdown = null;
-	private JComboBox _hSpeedUnitsDropdown = null;
-	private JComboBox _vSpeedUnitsDropdown = null;
+	private JComboBox<String> _altitudeUnitsDropdown = null;
+	private JComboBox<String> _hSpeedUnitsDropdown = null;
+	private JComboBox<String> _vSpeedUnitsDropdown = null;
 	private JRadioButton _vSpeedUpwardsRadio = null;
 	private ComponentHider _componentHider = null;
 	private int _selectedField = -1;
@@ -326,7 +326,7 @@ public class TextFileLoader
 		delimsPanel.add(_statusLabel);
 		firstCard.add(delimsPanel, BorderLayout.SOUTH);
 		// load snippet to show first few lines
-		_snippetBox = new JList(_fileCacher.getSnippet(SNIPPET_SIZE, MAX_SNIPPET_WIDTH));
+		_snippetBox = new JList<String>(_fileCacher.getSnippet(SNIPPET_SIZE, MAX_SNIPPET_WIDTH));
 		_snippetBox.setEnabled(false);
 		firstCard.add(makeLabelledPanel("dialog.openoptions.filesnippet", _snippetBox), BorderLayout.CENTER);
 
@@ -414,7 +414,7 @@ public class TextFileLoader
 		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);
+		_altitudeUnitsDropdown = new JComboBox<String>(altUnits);
 		altGrid.add(_altitudeUnitsDropdown);
 		holderPanel.add(altUnitsPanel);
 		// Horizontal speed
@@ -423,7 +423,7 @@ public class TextFileLoader
 		speedPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.speed")));
 		JLabel speedLabel = new JLabel(I18nManager.getText("dialog.openoptions.speedunits") + ": ");
 		speedGrid.add(speedLabel);
-		_hSpeedUnitsDropdown = new JComboBox();
+		_hSpeedUnitsDropdown = new JComboBox<String>();
 		for (Unit spUnit : UnitSetLibrary.ALL_SPEED_UNITS) {
 			_hSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
 		}
@@ -435,7 +435,7 @@ public class TextFileLoader
 		vSpeedPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.verticalspeed")));
 		JLabel vSpeedLabel = new JLabel(I18nManager.getText("dialog.openoptions.vertspeedunits") + ": ");
 		vSpeedGrid.add(vSpeedLabel);
-		_vSpeedUnitsDropdown = new JComboBox();
+		_vSpeedUnitsDropdown = new JComboBox<String>();
 		for (Unit spUnit : UnitSetLibrary.ALL_SPEED_UNITS) {
 			_vSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
 		}
@@ -587,7 +587,7 @@ public class TextFileLoader
 		_fieldTableModel.updateData(startFieldArray);
 		_fieldTable.setModel(_fieldTableModel);
 		// add dropdowns to second column
-		JComboBox fieldTypesBox = new JComboBox();
+		JComboBox<String> fieldTypesBox = new JComboBox<String>();
 		String[] fieldNames = Field.getFieldNames();
 		for (int i=0; i<fieldNames.length; i++)
 		{
diff --git a/tim/prune/load/babel/DiscardFilter.java b/tim/prune/load/babel/DiscardFilter.java
index f310031..22f36f7 100644
--- a/tim/prune/load/babel/DiscardFilter.java
+++ b/tim/prune/load/babel/DiscardFilter.java
@@ -31,7 +31,7 @@ public class DiscardFilter extends FilterDefinition
 
 	private WholeNumberField _hdopField = null;
 	private WholeNumberField _vdopField = null;
-	private JComboBox _combineDopsCombo = null;
+	private JComboBox<String> _combineDopsCombo = null;
 	private WholeNumberField _numSatsField = null;
 	private JCheckBox _noFixCheckbox = null;
 	private JCheckBox _unknownFixCheckbox = null;
@@ -65,7 +65,7 @@ public class DiscardFilter extends FilterDefinition
 		_hdopField = new WholeNumberField(2);
 		_hdopField.addKeyListener(_paramChangeListener);
 		dopPanel.add(_hdopField);
-		_combineDopsCombo = new JComboBox(new String[] {I18nManager.getText("logic.and"), I18nManager.getText("logic.or")});
+		_combineDopsCombo = new JComboBox<String>(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);
diff --git a/tim/prune/load/babel/DistanceFilter.java b/tim/prune/load/babel/DistanceFilter.java
index 7554160..4826bc8 100644
--- a/tim/prune/load/babel/DistanceFilter.java
+++ b/tim/prune/load/babel/DistanceFilter.java
@@ -27,7 +27,7 @@ public class DistanceFilter extends FilterDefinition
 	}
 
 	private DecimalNumberField _distField = null;
-	private JComboBox _distUnitsCombo = null;
+	private JComboBox<String> _distUnitsCombo = null;
 	private WholeNumberField _secondsField = null;
 
 
@@ -54,7 +54,7 @@ public class DistanceFilter extends FilterDefinition
 		_distField = new DecimalNumberField();
 		_distField.addKeyListener(_paramChangeListener);
 		gridPanel.add(_distField);
-		_distUnitsCombo = new JComboBox(new String[] {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")});
+		_distUnitsCombo = new JComboBox<String>(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);
diff --git a/tim/prune/load/babel/InterpolateFilter.java b/tim/prune/load/babel/InterpolateFilter.java
index a169846..9670fb2 100644
--- a/tim/prune/load/babel/InterpolateFilter.java
+++ b/tim/prune/load/babel/InterpolateFilter.java
@@ -28,7 +28,7 @@ public class InterpolateFilter extends FilterDefinition
 	}
 
 	private DecimalNumberField _distField = null;
-	private JComboBox _distUnitsCombo = null;
+	private JComboBox<String> _distUnitsCombo = null;
 	private WholeNumberField _secondsField = null;
 
 
@@ -55,7 +55,7 @@ public class InterpolateFilter extends FilterDefinition
 		_distField = new DecimalNumberField();
 		_distField.addKeyListener(_paramChangeListener);
 		gridPanel.add(_distField);
-		_distUnitsCombo = new JComboBox(new String[] {I18nManager.getText("units.kilometres"), I18nManager.getText("units.miles")});
+		_distUnitsCombo = new JComboBox<String>(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);
diff --git a/tim/prune/load/babel/SimplifyFilter.java b/tim/prune/load/babel/SimplifyFilter.java
index ebbc9f4..1567c88 100644
--- a/tim/prune/load/babel/SimplifyFilter.java
+++ b/tim/prune/load/babel/SimplifyFilter.java
@@ -31,7 +31,7 @@ public class SimplifyFilter extends FilterDefinition
 
 	private WholeNumberField _maxPointsField = null;
 	private DecimalNumberField _distField = null;
-	private JComboBox _distUnitsCombo = null;
+	private JComboBox<String> _distUnitsCombo = null;
 	private JRadioButton _crossTrackRadio = null;
 	private JRadioButton _lengthRadio = null;
 	private JRadioButton _relativeRadio = null;
@@ -65,7 +65,7 @@ public class SimplifyFilter extends FilterDefinition
 		_distField = new DecimalNumberField();
 		_distField.addKeyListener(_paramChangeListener);
 		gridPanel.add(_distField);
-		_distUnitsCombo = new JComboBox(new String[] {
+		_distUnitsCombo = new JComboBox<String>(new String[] {
 			I18nManager.getText(UnitSetLibrary.UNITS_KILOMETRES.getNameKey()),
 			I18nManager.getText(UnitSetLibrary.UNITS_MILES.getNameKey())
 		});
diff --git a/tim/prune/load/xml/GzipFileLoader.java b/tim/prune/load/xml/GzipFileLoader.java
index 3ebd4ca..4be2bab 100644
--- a/tim/prune/load/xml/GzipFileLoader.java
+++ b/tim/prune/load/xml/GzipFileLoader.java
@@ -3,8 +3,6 @@ package tim.prune.load.xml;
 import java.io.File;
 import java.io.FileInputStream;
 import java.util.zip.GZIPInputStream;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
 import tim.prune.App;
 import tim.prune.I18nManager;
 import tim.prune.data.SourceInfo;
@@ -42,8 +40,8 @@ public class GzipFileLoader
 		{
 			istream = new GZIPInputStream(new FileInputStream(inFile));
 			_xmlLoader.reset();
-			SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
-			saxParser.parse(istream, _xmlLoader);
+			// Parse the stream using either Xerces or java classes
+			_xmlLoader.parseXmlStream(istream);
 			XmlHandler handler = _xmlLoader.getHandler();
 			if (handler == null) {
 				_app.showErrorMessage("error.load.dialogtitle", "error.load.noread");
diff --git a/tim/prune/load/xml/XmlFileLoader.java b/tim/prune/load/xml/XmlFileLoader.java
index 783ac79..5af471a 100644
--- a/tim/prune/load/xml/XmlFileLoader.java
+++ b/tim/prune/load/xml/XmlFileLoader.java
@@ -2,14 +2,19 @@ package tim.prune.load.xml;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 
 import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
 import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.helpers.XMLReaderFactory;
 
 import tim.prune.App;
 import tim.prune.I18nManager;
@@ -66,13 +71,19 @@ public class XmlFileLoader extends DefaultHandler implements Runnable
 	public void run()
 	{
 		FileInputStream inStream = null;
+		boolean success = false;
 		try
 		{
-			// Construct a SAXParser and use this as a default handler
-			SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
 			inStream = new FileInputStream(_file);
-			saxParser.parse(inStream, this);
+			success = parseXmlStream(inStream);
+		}
+		catch (FileNotFoundException fnfe) {}
+
+		// Clean up the stream, don't need it any more
+		try {inStream.close();} catch (IOException e2) {}
 
+		if (success)
+		{
 			// Check whether handler was properly instantiated
 			if (_handler == null)
 			{
@@ -90,18 +101,47 @@ public class XmlFileLoader extends DefaultHandler implements Runnable
 					new MediaLinkInfo(_handler.getLinkArray()));
 			}
 		}
-		catch (Exception e)
+	}
+
+
+	/**
+	 * Try both Xerces and the built-in java classes to parse the given xml stream
+	 * @param inStream input stream from file / zip / gzip
+	 * @return true on success, false if both xerces and built-in parser failed
+	 */
+	public boolean parseXmlStream(InputStream inStream)
+	{
+		boolean success = false;
+		// Firstly, try to use xerces to parse the xml (will throw an exception if not available)
+		try
 		{
-			// Show error dialog
-			_app.showErrorMessageNoLookup("error.load.dialogtitle",
-				I18nManager.getText("error.load.othererror") + " " + e.getMessage());
+			XMLReader xmlReader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
+			xmlReader.setContentHandler(this);
+			xmlReader.parse(new InputSource(inStream));
+			success = true; // worked
 		}
-		finally {
-			try {inStream.close();} catch (IOException e2) {}
+		catch (Exception e) {} // don't care too much if it didn't work, there's a backup
+
+		// If that didn't work, try the built-in classes (which work for xml1.0 but handling for 1.1 contains bugs)
+		if (!success)
+		{
+			try
+			{
+				// Construct a SAXParser and use this as a default handler
+				SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
+				saxParser.parse(inStream, this);
+				success = true;
+			}
+			catch (Exception e)
+			{
+				// Show error dialog
+				_app.showErrorMessageNoLookup("error.load.dialogtitle",
+					I18nManager.getText("error.load.othererror") + " " + e.getMessage());
+			}
 		}
+		return success;
 	}
 
-
 	/**
 	 * Receive a tag
 	 * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
diff --git a/tim/prune/load/xml/ZipFileLoader.java b/tim/prune/load/xml/ZipFileLoader.java
index 74e48f9..eb2f9a6 100644
--- a/tim/prune/load/xml/ZipFileLoader.java
+++ b/tim/prune/load/xml/ZipFileLoader.java
@@ -56,8 +56,8 @@ public class ZipFileLoader
 					if (suffix.equals(".kml") || suffix.equals(".gpx") || suffix.equals(".xml"))
 					{
 						_xmlLoader.reset();
-						SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
-						saxParser.parse(file.getInputStream(entry), _xmlLoader);
+						// Parse the stream using either Xerces or java classes
+						_xmlLoader.parseXmlStream(file.getInputStream(entry));
 						XmlHandler handler = _xmlLoader.getHandler();
 						if (handler == null) {
 							_app.showErrorMessage("error.load.dialogtitle", "error.load.othererror");
diff --git a/tim/prune/readme.txt b/tim/prune/readme.txt
index 8a47ebe..d586911 100644
--- a/tim/prune/readme.txt
+++ b/tim/prune/readme.txt
@@ -1,11 +1,11 @@
-GpsPrune version 15.1
+GpsPrune version 16.2
 =====================
 
 GpsPrune is an application for viewing, editing and managing coordinate data from GPS systems,
 including format conversion, charting and photo correlation.
 Full details can be found at http://activityworkshop.net/software/gpsprune/
 
-GpsPrune is copyright 2006-2013 activityworkshop.net and distributed under the terms of the Gnu GPL version 2.
+GpsPrune is copyright 2006-2014 activityworkshop.net and distributed under the terms of the Gnu GPL version 2.
 You may freely use the software, and may help others to freely use it too.  For further information
 on your rights and how they are protected, see the included license.txt file.
 
@@ -17,7 +17,7 @@ Running
 =======
 
 To run GpsPrune from the jar file, simply call it from a command prompt or shell:
-   java -jar gpsprune_15.1.jar
+   java -jar gpsprune_16.2.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,20 +25,37 @@ 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_15.1.jar --lang=DE
+   java -jar gpsprune_16.2.jar --lang=DE
 
 
-New with version 15.1
+New with version 16.2
 =====================
+The following fixes were added since version 16.1:
+  - Fix for Gpx-slicing UTF8 files
+  - Conversion of sunrise/sunset times to local timezone
+  - Removal of Cloudmade maps
+  - Additional translations
+
+New with version 16.1
+=====================
+The following fixes were added since version 16:
+  - Caching of terrain information for three-dimensional views
+  - Additional translations
+  - Improved void filling by interpolation
+  - Remembering file type of imported files
+
+New with version 16
+===================
 The following features were added since version 15:
-  - Improved translations
-  - Fixed bug with speed charts using gnuplot
-  - Fixed bug with dragging a mid-point within a selection
-  - Fixed bug with duplicate entries in profile popup menu
-  - Fixed bug with loading zoom level of custom map sources
+  - Extend povray output using terrain and/or map image
+  - Extend java3d output using terrain and/or map image
+  - Weather forecasts
+  - Splitting a track into segments based on distance or time
+  - Sewing track segments together
+  - Function to download and save SRTM tiles
 
 New with version 15
-===================
+=====================
 The following features were added since version 14:
   - Extend povray output using map image on base plane
   - Export an image of the map and track at a selected zoom level
@@ -52,7 +69,7 @@ The following features were added since version 14:
   - 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
index 514ebd7..0265569 100644
--- a/tim/prune/save/BaseImageConfigDialog.java
+++ b/tim/prune/save/BaseImageConfigDialog.java
@@ -1,7 +1,6 @@
 package tim.prune.save;
 
 import java.awt.BorderLayout;
-import java.awt.Color;
 import java.awt.Component;
 import java.awt.FlowLayout;
 import java.awt.GridLayout;
@@ -18,22 +17,23 @@ import javax.swing.JComboBox;
 import javax.swing.JDialog;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
+import javax.swing.JProgressBar;
 
-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;
+import tim.prune.threedee.ImageDefinition;
 
 /**
  * Dialog to let you choose the parameters for a base image
- * (source and zoom)
+ * (source and zoom) including preview
  */
 public class BaseImageConfigDialog implements Runnable
 {
 	/** Parent to notify */
-	private DataSubscriber _parent = null;
+	private BaseImageConsumer _parent = null;
 	/** Parent dialog for position */
 	private JDialog _parentDialog = null;
 	/** Track to use for preview image */
@@ -45,25 +45,27 @@ public class BaseImageConfigDialog implements Runnable
 	/** Panel to hold the other controls */
 	private JPanel _mainPanel = null;
 	/** Dropdown for map source */
-	private JComboBox _mapSourceDropdown = null;
+	private JComboBox<String> _mapSourceDropdown = null;
 	/** Dropdown for zoom levels */
-	private JComboBox _zoomDropdown = null;
-	/** Warning label that image is incomplete */
-	private JLabel _imageIncompleteLabel = null;
+	private JComboBox<String> _zoomDropdown = null;
+	/** Button to trigger a download of the missing map tiles */
+	private JButton _downloadTilesButton = null;
+	/** Progress bar for downloading additional tiles */
+	private JProgressBar _progressBar = 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;
+	/** Grouter, used to avoid regenerating images */
+	private MapGrouter _grouter = new MapGrouter();
 	/** 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;
+	private ImageDefinition _imageDef = new ImageDefinition();
 
 
 	/**
@@ -72,7 +74,7 @@ public class BaseImageConfigDialog implements Runnable
 	 * @param inParentDialog parent dialog
 	 * @param inTrack track object
 	 */
-	public BaseImageConfigDialog(DataSubscriber inParent, JDialog inParentDialog, Track inTrack)
+	public BaseImageConfigDialog(BaseImageConsumer inParent, JDialog inParentDialog, Track inTrack)
 	{
 		_parent = inParent;
 		_parentDialog = inParentDialog;
@@ -84,6 +86,17 @@ public class BaseImageConfigDialog implements Runnable
 	}
 
 	/**
+	 * @param inDefinition image definition object from previous dialog
+	 */
+	public void setImageDefinition(ImageDefinition inDefinition)
+	{
+		_imageDef = inDefinition;
+		if (_imageDef == null) {
+			_imageDef = new ImageDefinition();
+		}
+	}
+
+	/**
 	 * Begin the function
 	 */
 	public void begin()
@@ -110,26 +123,29 @@ public class BaseImageConfigDialog implements Runnable
 	private void initDialog()
 	{
 		_rebuilding = true;
-		_useImageCheckbox.setSelected(_useImage);
+		_useImageCheckbox.setSelected(_imageDef.getUseImage());
 		// Populate the dropdown of map sources from the library in case it has changed
 		_mapSourceDropdown.removeAllItems();
 		for (int i=0; i<MapSourceLibrary.getNumSources(); i++)
 		{
 			_mapSourceDropdown.addItem(MapSourceLibrary.getSource(i).getName());
 		}
-		if (_sourceIndex < 0 || _sourceIndex >= _mapSourceDropdown.getItemCount()) {
-			_sourceIndex = 0;
+		int sourceIndex = _imageDef.getSourceIndex();
+		if (sourceIndex < 0 || sourceIndex >= _mapSourceDropdown.getItemCount()) {
+			sourceIndex = 0;
 		}
-		_mapSourceDropdown.setSelectedIndex(_sourceIndex);
+		_mapSourceDropdown.setSelectedIndex(sourceIndex);
 
 		// Zoom level
-		if (_useImage)
+		int zoomLevel = _imageDef.getZoom();
+		if (_imageDef.getUseImage())
 		{
 			for (int i=0; i<_zoomDropdown.getItemCount(); i++)
 			{
 				String item = _zoomDropdown.getItemAt(i).toString();
 				try {
-					if (Integer.parseInt(item) == _zoomLevel) {
+					if (Integer.parseInt(item) == zoomLevel)
+					{
 						_zoomDropdown.setSelectedIndex(i);
 						break;
 					}
@@ -154,8 +170,11 @@ public class BaseImageConfigDialog implements Runnable
 			currentZoom = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
 		}
 		catch (Exception nfe) {}
+		// First time in, the dropdown might be empty but we still might have a zoom in the definition
+		if (_zoomDropdown.getItemCount() == 0) {
+			currentZoom = _imageDef.getZoom();
+		}
 		// 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;
 
@@ -248,24 +267,10 @@ public class BaseImageConfigDialog implements Runnable
 
 
 	/**
-	 * @return true if image has been selected
+	 * @return image definition object
 	 */
-	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;
+	public ImageDefinition getImageDefinition() {
+		return _imageDef;
 	}
 
 	/**
@@ -296,7 +301,7 @@ public class BaseImageConfigDialog implements Runnable
 		JLabel sourceLabel = new JLabel(I18nManager.getText("dialog.baseimage.mapsource") + ": ");
 		sourceLabel.setHorizontalAlignment(JLabel.RIGHT);
 		controlsPanel.add(sourceLabel);
-		_mapSourceDropdown = new JComboBox();
+		_mapSourceDropdown = new JComboBox<String>();
 		_mapSourceDropdown.addItem("name of map source");
 		// Add listener to dropdown to change zoom levels
 		_mapSourceDropdown.addActionListener(new ActionListener() {
@@ -309,7 +314,7 @@ public class BaseImageConfigDialog implements Runnable
 		JLabel zoomLabel = new JLabel(I18nManager.getText("dialog.baseimage.zoom") + ": ");
 		zoomLabel.setHorizontalAlignment(JLabel.RIGHT);
 		controlsPanel.add(zoomLabel);
-		_zoomDropdown = new JComboBox();
+		_zoomDropdown = new JComboBox<String>();
 		// Add action listener to enable ok button when zoom changed
 		_zoomDropdown.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent arg0) {
@@ -331,10 +336,21 @@ public class BaseImageConfigDialog implements Runnable
 		// 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 downloadPanel = new JPanel();
+		downloadPanel.setLayout(new BorderLayout(4, 4));
+		_downloadTilesButton = new JButton(I18nManager.getText("button.load"));
+		_downloadTilesButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent arg0) {
+				downloadRemainingTiles();
+			}
+		});
+		_downloadTilesButton.setVisible(false);
+		downloadPanel.add(_downloadTilesButton, BorderLayout.NORTH);
+		_progressBar = new JProgressBar();
+		_progressBar.setIndeterminate(true);
+		_progressBar.setVisible(false);
+		downloadPanel.add(_progressBar, BorderLayout.SOUTH);
+		labelPanel.add(downloadPanel, BorderLayout.NORTH);
 		JPanel labelGridPanel = new JPanel();
 		labelGridPanel.setLayout(new GridLayout(0, 2, 10, 4));
 		labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.tiles") + ": "));
@@ -401,7 +417,7 @@ public class BaseImageConfigDialog implements Runnable
 	private void updateImagePreview()
 	{
 		// Clear labels
-		_imageIncompleteLabel.setVisible(false);
+		_downloadTilesButton.setVisible(false);
 		_tilesFoundLabel.setText("");
 		_imageSizeLabel.setText("");
 		if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getSelectedIndex() >= 0
@@ -423,17 +439,11 @@ public class BaseImageConfigDialog implements Runnable
 	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);
+		_imageDef.setUseImage(_useImageCheckbox.isSelected(),
+			_mapSourceDropdown.getSelectedIndex(),
+			getSelectedZoomLevel());
+		// Inform parent that details have changed
+		_parent.baseImageChanged();
 	}
 
 	/**
@@ -447,16 +457,11 @@ public class BaseImageConfigDialog implements Runnable
 		final int zoomIndex = _zoomDropdown.getSelectedIndex();
 		if (!_useImageCheckbox.isSelected() || mapIndex < 0 || zoomIndex < 0) {return;}
 
-		// Get the map source and zoom level
+		// Get the map source from the index
 		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);
+		GroutedImage groutedImage = _grouter.createMapImage(_track, mapSource, getSelectedZoomLevel());
 
 		// If the dialog hasn't changed, pass the generated image to the preview panel
 		if (_useImageCheckbox.isSelected()
@@ -465,8 +470,11 @@ public class BaseImageConfigDialog implements Runnable
 			&& groutedImage != null)
 		{
 			_previewPanel.setImage(groutedImage);
+			final int numTilesRemaining = groutedImage.getNumTilesTotal() - groutedImage.getNumTilesUsed();
+			final boolean offerDownload = numTilesRemaining > 0 && numTilesRemaining < 50;
 			// Set values of labels
-			_imageIncompleteLabel.setVisible(!groutedImage.isComplete());
+			_downloadTilesButton.setVisible(offerDownload);
+			_downloadTilesButton.setEnabled(offerDownload);
 			_tilesFoundLabel.setText(groutedImage.getNumTilesUsed() + " / " + groutedImage.getNumTilesTotal());
 			if (groutedImage.getImageSize() > 0) {
 				_imageSizeLabel.setText("" + groutedImage.getImageSize());
@@ -479,17 +487,78 @@ public class BaseImageConfigDialog implements Runnable
 		{
 			_previewPanel.setImage(null);
 			// Clear labels
-			_imageIncompleteLabel.setVisible(false);
+			_downloadTilesButton.setVisible(false);
 			_tilesFoundLabel.setText("");
 			_imageSizeLabel.setText("");
 		}
 	}
 
 	/**
+	 * @return zoom level selected in the dropdown
+	 */
+	private int getSelectedZoomLevel()
+	{
+		int zoomLevel = 0;
+		try {
+			zoomLevel = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
+		}
+		catch (Exception e) {
+			System.err.println("Exception: " + e.getClass().getName() + " : " + e.getMessage());
+		}
+		return zoomLevel;
+	}
+
+	/**
 	 * @return true if any map data has been found for the image
 	 */
 	public boolean getFoundData()
 	{
-		return _useImage && _zoomLevel > 0 && _previewPanel != null && _previewPanel.getTilesFound();
+		return _imageDef.getUseImage() && _imageDef.getZoom() > 0
+			&& _previewPanel != null && _previewPanel.getTilesFound();
+	}
+
+	/**
+	 * @return true if selected zoom is valid for the current track (based only on pixel size)
+	 */
+	public boolean isSelectedZoomValid()
+	{
+		final double xyExtent = Math.max(_track.getXRange().getRange(), _track.getYRange().getRange());
+		// How many pixels does this give?
+		final int zoomFactor = 1 << _imageDef.getZoom();
+		final int pixCount = (int) (xyExtent * zoomFactor * 256);
+		return (pixCount > 100     // less than this isn't worth it
+			&& pixCount < 4000);   // don't want to run out of memory
+	}
+
+	/**
+	 * @return the map grouter for retrieval of generated image
+	 */
+	public MapGrouter getGrouter()
+	{
+		return _grouter;
+	}
+
+	/**
+	 * @return method triggered by "download" button, to asynchronously download the missing tiles
+	 */
+	private void downloadRemainingTiles()
+	{
+		_downloadTilesButton.setEnabled(false);
+		new Thread(new Runnable() {
+			public void run()
+			{
+				_progressBar.setVisible(true);
+				// Use a grouter to get all tiles from the TileManager, including downloading
+				MapGrouter grouter = new MapGrouter();
+				final int mapIndex = _mapSourceDropdown.getSelectedIndex();
+				if (!_useImageCheckbox.isSelected() || mapIndex < 0) {return;}
+				MapSource mapSource = MapSourceLibrary.getSource(mapIndex);
+				grouter.createMapImage(_track, mapSource, getSelectedZoomLevel(), true);
+				_progressBar.setVisible(false);
+				// And then refresh the dialog
+				_grouter.clearMapImage();
+				updateImagePreview();
+			}
+		}).start();
 	}
 }
diff --git a/tim/prune/save/BaseImageConsumer.java b/tim/prune/save/BaseImageConsumer.java
new file mode 100644
index 0000000..3930099
--- /dev/null
+++ b/tim/prune/save/BaseImageConsumer.java
@@ -0,0 +1,10 @@
+package tim.prune.save;
+
+/**
+ * Interface used to inform consumers that the base image has been changed
+ */
+public interface BaseImageConsumer
+{
+	/** Notify consumer that base image has changed */
+	public void baseImageChanged();
+}
diff --git a/tim/prune/save/ExifSaver.java b/tim/prune/save/ExifSaver.java
index 3c1a7b8..d17c5d4 100644
--- a/tim/prune/save/ExifSaver.java
+++ b/tim/prune/save/ExifSaver.java
@@ -265,20 +265,17 @@ public class ExifSaver implements Runnable
 		}
 		_progressBar.setVisible(false);
 		// Show confirmation
-		UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.saveexif.ok1") + " "
-			+ numSaved + " " + I18nManager.getText("confirm.saveexif.ok2"));
+		UpdateMessageBroker.informSubscribers(I18nManager.getTextWithNumber("confirm.saveexif.ok", numSaved));
 		if (numFailed > 0)
 		{
 			JOptionPane.showMessageDialog(_parentFrame,
-				I18nManager.getText("error.saveexif.failed1") + " " + numFailed + " "
-				+ I18nManager.getText("error.saveexif.failed2"),
+				I18nManager.getTextWithNumber("error.saveexif.failed", numFailed),
 				I18nManager.getText("dialog.saveexif.title"), JOptionPane.ERROR_MESSAGE);
 		}
 		if (numForced > 0)
 		{
 			JOptionPane.showMessageDialog(_parentFrame,
-				I18nManager.getText("error.saveexif.forced1") + " " + numForced + " "
-				+ I18nManager.getText("error.saveexif.forced2"),
+				I18nManager.getTextWithNumber("error.saveexif.forced", numForced),
 				I18nManager.getText("dialog.saveexif.title"), JOptionPane.WARNING_MESSAGE);
 		}
 		// close dialog, all finished
diff --git a/tim/prune/save/GpxExporter.java b/tim/prune/save/GpxExporter.java
index 8d9df8a..b0cb283 100644
--- a/tim/prune/save/GpxExporter.java
+++ b/tim/prune/save/GpxExporter.java
@@ -11,7 +11,6 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
-import java.nio.charset.Charset;
 
 import javax.swing.BorderFactory;
 import javax.swing.Box;
@@ -67,7 +66,6 @@ public class GpxExporter extends GenericFunction implements Runnable
 	private JPanel _encodingsPanel = null;
 	private JRadioButton _useSystemRadio = null, _forceUtf8Radio = null;
 	private File _exportFile = null;
-	private static String _systemEncoding = null;
 
 	/** this program name */
 	private static final String GPX_CREATOR = "GpsPrune v" + GpsPrune.VERSION_NUMBER + " activityworkshop.net";
@@ -99,15 +97,16 @@ public class GpxExporter extends GenericFunction implements Runnable
 			_dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
 			_dialog.setLocationRelativeTo(_parentFrame);
 			_dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
-			_systemEncoding = getSystemEncoding();
 			_dialog.getContentPane().add(makeDialogComponents());
 			_dialog.pack();
 		}
 		_pointTypeSelector.init(_app.getTrackInfo());
-		_encodingsPanel.setVisible(!isSystemUtf8());
-		if (!isSystemUtf8()) {
+		_encodingsPanel.setVisible(!XmlUtils.isSystemUtf8());
+		if (!XmlUtils.isSystemUtf8())
+		{
+			String systemEncoding = XmlUtils.getSystemEncoding();
 			_useSystemRadio.setText(I18nManager.getText("dialog.exportgpx.encoding.system")
-				+ " (" + (_systemEncoding == null ? "unknown" : _systemEncoding) + ")");
+				+ " (" + (systemEncoding == null ? "unknown" : systemEncoding) + ")");
 		}
 		_dialog.setVisible(true);
 	}
@@ -148,7 +147,7 @@ public class GpxExporter extends GenericFunction implements Runnable
 		mainPanel.add(checkPanel);
 		// panel for selecting character encoding
 		_encodingsPanel = new JPanel();
-		if (!isSystemUtf8())
+		if (!XmlUtils.isSystemUtf8())
 		{
 			// only add this panel if system isn't utf8 (or can't be identified yet)
 			_encodingsPanel.setBorder(BorderFactory.createCompoundBorder(
@@ -486,7 +485,9 @@ public class GpxExporter extends GenericFunction implements Runnable
 					// get the source from the point (if any)
 					String pointSource = getPointSource(inCachers, point);
 					// Clear point source if it's the wrong type of point (eg changed from waypoint or route point)
-					if (pointSource != null && !pointSource.toLowerCase().startsWith(inPointTag)) {pointSource = null;}
+					if (pointSource != null && !pointSource.trim().toLowerCase().startsWith(inPointTag)) {
+						pointSource = null;
+					}
 					if (pointSource != null || !inOnlyCopies)
 					{
 						// restart track segment if necessary
@@ -626,62 +627,9 @@ public class GpxExporter extends GenericFunction implements Runnable
 	 */
 	private static String getXmlHeaderString(OutputStreamWriter inWriter)
 	{
-		return "<?xml version=\"1.0\" encoding=\"" + getEncoding(inWriter) + "\"?>\n";
-	}
-
-
-	/**
-	 * Get the default system encoding using a writer
-	 * @param inWriter writer object
-	 * @return string defining encoding
-	 */
-	private static String getEncoding(OutputStreamWriter inWriter)
-	{
-		String encoding = inWriter.getEncoding();
-		try {
-			encoding =  Charset.forName(encoding).name();
-		}
-		catch (Exception e) {} // ignore failure to find encoding
-		return encoding;
-	}
-
-
-	/**
-	 * Use a temporary file to obtain the name of the default system encoding
-	 * @return name of default system encoding, or null if write failed
-	 */
-	private static String getSystemEncoding()
-	{
-		File tempFile = null;
-		String encoding = null;
-		try
-		{
-			tempFile = File.createTempFile("prune", null);
-			OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile));
-			encoding = getEncoding(writer);
-			writer.close();
-		}
-		catch (IOException e) {} // value stays null
-		// Delete temp file
-		if (tempFile != null && tempFile.exists()) {
-			if (!tempFile.delete()) {
-				System.err.println("Cannot delete temp file: " + tempFile.getAbsolutePath());
-			}
-		}
-		// If writing failed (eg permissions) then just ask system for default
-		if (encoding == null) encoding = Charset.defaultCharset().name();
-		return encoding;
+		return "<?xml version=\"1.0\" encoding=\"" + XmlUtils.getEncoding(inWriter) + "\"?>\n";
 	}
 
-	/**
-	 * Creates temp file if necessary to check system encoding
-	 * @return true if system uses UTF-8 by default
-	 */
-	private static boolean isSystemUtf8()
-	{
-		if (_systemEncoding == null) _systemEncoding = getSystemEncoding();
-		return (_systemEncoding != null && _systemEncoding.toUpperCase().equals("UTF-8"));
-	}
 
 	/**
 	 * Get the header string for the gpx tag
@@ -694,6 +642,7 @@ public class GpxExporter extends GenericFunction implements Runnable
 		if (inCachers != null) {gpxHeader = inCachers.getFirstHeader();}
 		if (gpxHeader == null || gpxHeader.length() < 5)
 		{
+			// TODO: Consider changing this to default to GPX 1.1
 			// Create default (1.0) header
 			gpxHeader = "<gpx version=\"1.0\" creator=\"" + GPX_CREATOR
 				+ "\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
diff --git a/tim/prune/save/GroutedImage.java b/tim/prune/save/GroutedImage.java
index 4400067..d5cbc05 100644
--- a/tim/prune/save/GroutedImage.java
+++ b/tim/prune/save/GroutedImage.java
@@ -38,13 +38,6 @@ public class GroutedImage
 	}
 
 	/**
-	 * @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()
diff --git a/tim/prune/save/ImageExporter.java b/tim/prune/save/ImageExporter.java
index fabed33..41bc0e6 100644
--- a/tim/prune/save/ImageExporter.java
+++ b/tim/prune/save/ImageExporter.java
@@ -16,6 +16,7 @@ import java.io.IOException;
 
 import javax.imageio.ImageIO;
 import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JDialog;
@@ -23,10 +24,8 @@ 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;
@@ -34,24 +33,26 @@ import tim.prune.config.Config;
 import tim.prune.data.DataPoint;
 import tim.prune.data.DoubleRange;
 import tim.prune.data.Track;
+import tim.prune.gui.BaseImageDefinitionPanel;
 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;
+import tim.prune.threedee.ImageDefinition;
 
 /**
  * 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
+public class ImageExporter extends GenericFunction implements BaseImageConsumer
 {
 	private JDialog   _dialog = null;
 	private JCheckBox _drawDataCheckbox = null;
+	private JCheckBox _drawTrackPointsCheckbox = null;
 	private WholeNumberField _textScaleField = null;
-	private JLabel    _baseImageLabel = null;
-	private BaseImageConfigDialog _baseImageConfig = null;
+	private BaseImageDefinitionPanel _baseImagePanel = null;
 	private JFileChooser _fileChooser = null;
 	private JButton   _okButton = null;
 
@@ -83,10 +84,6 @@ public class ImageExporter extends GenericFunction implements DataSubscriber
 			_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())
@@ -95,7 +92,8 @@ public class ImageExporter extends GenericFunction implements DataSubscriber
 			return;
 		}
 
-		updateBaseImageDetails();
+		_baseImagePanel.updateBaseImageDetails();
+		baseImageChanged();
 		// Show dialog
 		_dialog.setVisible(true);
 	}
@@ -111,6 +109,15 @@ public class ImageExporter extends GenericFunction implements DataSubscriber
 		// Checkbox for drawing track or not
 		_drawDataCheckbox = new JCheckBox(I18nManager.getText("dialog.exportimage.drawtrack"));
 		_drawDataCheckbox.setSelected(true); // draw by default
+		// Also whether to draw track points or not
+		_drawTrackPointsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportimage.drawtrackpoints"));
+		_drawTrackPointsCheckbox.setSelected(true);
+		// Add listener to en/disable trackpoints checkbox
+		_drawDataCheckbox.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent arg0) {
+				_drawTrackPointsCheckbox.setEnabled(_drawDataCheckbox.isSelected());
+			}
+		});
 
 		// TODO: Maybe have other controls such as line width, symbol scale factor
 		JPanel controlsPanel = new JPanel();
@@ -128,7 +135,7 @@ public class ImageExporter extends GenericFunction implements DataSubscriber
 			public void actionPerformed(ActionEvent e)
 			{
 				doExport();
-				MapGrouter.clearMapImage();
+				_baseImagePanel.getGrouter().clearMapImage();
 				_dialog.dispose();
 			}
 		});
@@ -137,7 +144,7 @@ public class ImageExporter extends GenericFunction implements DataSubscriber
 		cancelButton.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent e)
 			{
-				MapGrouter.clearMapImage();
+				_baseImagePanel.getGrouter().clearMapImage();
 				_dialog.dispose();
 			}
 		});
@@ -150,96 +157,41 @@ public class ImageExporter extends GenericFunction implements DataSubscriber
 			{
 				if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
 					_dialog.dispose();
-					MapGrouter.clearMapImage();
+					_baseImagePanel.getGrouter().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))
-		);
+		_baseImagePanel = new BaseImageDefinitionPanel(this, _dialog, _app.getTrackInfo().getTrack());
+
+		// Panel for the checkboxes at the top
+		JPanel checkPanel = new JPanel();
+		checkPanel.setLayout(new BoxLayout(checkPanel, BoxLayout.Y_AXIS));
+		checkPanel.add(_drawDataCheckbox);
+		checkPanel.add(_drawTrackPointsCheckbox);
 
 		// add these panels to the holder panel
 		JPanel holderPanel = new JPanel();
 		holderPanel.setLayout(new BorderLayout(5, 5));
-		holderPanel.add(_drawDataCheckbox, BorderLayout.NORTH);
+		holderPanel.add(checkPanel, BorderLayout.NORTH);
 		holderPanel.add(controlsPanel, BorderLayout.CENTER);
-		holderPanel.add(imagePanel, BorderLayout.SOUTH);
+		holderPanel.add(_baseImagePanel, 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);
+		// OK pressed, so choose output file
 		if (_fileChooser == null)
 		{
 			_fileChooser = new JFileChooser();
@@ -296,9 +248,11 @@ public class ImageExporter extends GenericFunction implements DataSubscriber
 	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());
+		ImageDefinition imageDef = _baseImagePanel.getImageDefinition();
+		MapSource source = MapSourceLibrary.getSource(imageDef.getSourceIndex());
+		MapGrouter grouter = _baseImagePanel.getGrouter();
+		GroutedImage baseImage = grouter.getMapImage(_app.getTrackInfo().getTrack(), source,
+			imageDef.getZoom());
 		if (baseImage == null || !baseImage.isValid())
 		{
 			_app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage");
@@ -332,9 +286,9 @@ public class ImageExporter extends GenericFunction implements DataSubscriber
 		// Work out x, y limits for drawing
 		DoubleRange xRange = inImage.getXRange();
 		DoubleRange yRange = inImage.getYRange();
-		int zoomFactor = 1 << _baseImageConfig.getZoomLevel();
+		final int zoomFactor = 1 << _baseImagePanel.getImageDefinition().getZoom();
 		Graphics g = inImage.getImage().getGraphics();
-		// TODO: Set colour, line width
+		// TODO: Set line width, style etc
 		g.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
 
 		// Loop over points
@@ -355,8 +309,11 @@ public class ImageExporter extends GenericFunction implements DataSubscriber
 					// draw from previous point to this one
 					g.drawLine(prevX, prevY, px, py);
 				}
-				// draw this point
-				g.drawRect(px-2, py-2, 3, 3);
+				// Only draw points if requested
+				if (_drawTrackPointsCheckbox.isSelected())
+				{
+					g.drawRect(px-2, py-2, 3, 3);
+				}
 				// save coordinates
 				prevX = px; prevY = py;
 			}
@@ -451,4 +408,16 @@ public class ImageExporter extends GenericFunction implements DataSubscriber
 		// Note: Differences from main map: No mapPosition (modifying position and visible points),
 		//       no selection, no opacities, maybe different scale/text factors
 	}
+
+	/**
+	 * Base image has changed, need to enable/disable ok button
+	 */
+	public void baseImageChanged()
+	{
+		final boolean useImage = _baseImagePanel.getImageDefinition().getUseImage();
+		final int zoomLevel = _baseImagePanel.getImageDefinition().getZoom();
+		final boolean okEnabled = useImage && _baseImagePanel.getFoundData()
+			&& MapGrouter.isZoomLevelOk(_app.getTrackInfo().getTrack(), zoomLevel);
+		_okButton.setEnabled(okEnabled);
+	}
 }
diff --git a/tim/prune/save/KmlExporter.java b/tim/prune/save/KmlExporter.java
index 6fbf749..8021834 100644
--- a/tim/prune/save/KmlExporter.java
+++ b/tim/prune/save/KmlExporter.java
@@ -525,7 +525,7 @@ public class KmlExporter extends GenericFunction implements Runnable
 			}
 			// Make a blob with description for each photo
 			// Photos have already been written so picture sizes already known
-			if (point.getPhoto() != null && writePhotos && writeCurrentPoint)
+			if (point.getPhoto() != null && point.getPhoto().isValid() && writePhotos && writeCurrentPoint)
 			{
 				if (!writtenPhotoHeader)
 				{
@@ -876,21 +876,21 @@ public class KmlExporter extends GenericFunction implements Runnable
 		ImageWriter imageWriter = writers.next();
 
 		// Check selection checkbox
-		boolean justSelection = _pointTypeSelector.getJustSelection();
+		final boolean justSelection = _pointTypeSelector.getJustSelection();
 		int selStart = -1, selEnd = -1;
 		if (justSelection) {
 			selStart = _trackInfo.getSelection().getStart();
 			selEnd = _trackInfo.getSelection().getEnd();
 		}
 
-		int numPoints = _track.getNumPoints();
+		final int numPoints = _track.getNumPoints();
 		DataPoint point = null;
 		int photoNum = 0;
 		// Loop over all points in track
 		for (int i=0; i<numPoints && !_cancelPressed; i++)
 		{
 			point = _track.getPoint(i);
-			if (point.getPhoto() != null && (!justSelection || (i>=selStart && i<=selEnd)))
+			if (point.getPhoto() != null && point.getPhoto().isValid() && (!justSelection || (i>=selStart && i<=selEnd)))
 			{
 				photoNum++;
 				// Make a new entry in zip file
diff --git a/tim/prune/save/MapGrouter.java b/tim/prune/save/MapGrouter.java
index 65e23c0..263a306 100644
--- a/tim/prune/save/MapGrouter.java
+++ b/tim/prune/save/MapGrouter.java
@@ -1,11 +1,11 @@
 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 tim.prune.gui.map.MapTileManager;
+import tim.prune.gui.map.TileConsumer;
 
 import java.awt.Color;
 import java.awt.Graphics;
@@ -17,15 +17,15 @@ 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
+public class MapGrouter implements TileConsumer
 {
 	/** The most recently produced image */
-	private static GroutedImage _lastGroutedImage = null;
+	private GroutedImage _lastGroutedImage = null;
 
 	/**
 	 * Clear the last image, it's not needed any more
 	 */
-	public static void clearMapImage() {
+	public void clearMapImage() {
 		_lastGroutedImage = null;
 	}
 
@@ -36,7 +36,20 @@ public abstract class MapGrouter
 	 * @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)
+	public GroutedImage createMapImage(Track inTrack, MapSource inMapSource, int inZoom)
+	{
+		return createMapImage(inTrack, inMapSource, inZoom, false);
+	}
+
+	/**
+	 * 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
+	 * @param inDownload true to download tiles, false (by default) to just pull from disk
+	 * @return grouted image, or null if no image could be created
+	 */
+	public GroutedImage createMapImage(Track inTrack, MapSource inMapSource, int inZoom, boolean inDownload)
 	{
 		// Get the extents of the track including a standard (10%) border around the data
 		TrackExtents extents = new TrackExtents(inTrack);
@@ -44,8 +57,6 @@ public abstract class MapGrouter
 		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);
@@ -55,7 +66,7 @@ public abstract class MapGrouter
 
 		// 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;}
+		if (pixCount < 2 || inZoom == 0) {return null;}
 		BufferedImage resultImage = new BufferedImage(pixCount, pixCount, BufferedImage.TYPE_INT_RGB);
 		Graphics g = resultImage.getGraphics();
 		g.setColor(Color.WHITE);
@@ -63,8 +74,16 @@ public abstract class MapGrouter
 		// Work out where to start drawing the tiles on the image
 		int xOffset = (int) ((minTileX - xRange.getMinimum() * zoomFactor) * 256);
 
+		// Make a map tile manager to load (or download) the tiles
+		MapTileManager tileManager = new MapTileManager(this);
+		tileManager.setMapSource(inMapSource);
+		tileManager.enableTileDownloading(inDownload);
+		tileManager.setReturnIncompleteImages();
+		tileManager.setZoom(inZoom);
+
 		int numTilesUsed = 0;
 		int numTilesMissing = 0;
+
 		// Loop over the tiles
 		for (int x = minTileX; x <= maxTileX; x++)
 		{
@@ -73,13 +92,25 @@ public abstract class MapGrouter
 			{
 				for (int layer=0; layer < inMapSource.getNumLayers(); layer++)
 				{
-					Image tile = DiskTileCacher.getTile(cachePath, inMapSource.makeFilePath(layer, inZoom, x, y), false);
+					Image tile = tileManager.getTile(layer, x, y, true);
+					// If we're downloading tiles, wait until the tile isn't null
+					int waitCount = 0;
+					while (tile == null && inDownload && waitCount < 3)
+					{
+						// System.out.println("wait " + waitCount + " for tile to be not null");
+						try {Thread.sleep(250);} catch (InterruptedException e) {}
+						tile = tileManager.getTile(layer, x, y, false); // don't request another download
+						waitCount++;
+					}
+					// See if there's a tile or not
 					if (tile != null)
 					{
 						// Wait until tile is available (loaded asynchronously)
-						while (tile.getWidth(null) < 0) {
+						while (tile.getWidth(null) < 0 && !inDownload)
+						{
+							// System.out.println("Wait for tile width");
 							try {
-								Thread.sleep(100);
+								Thread.sleep(inDownload ? 500 : 100);
 							}
 							catch (InterruptedException ie) {}
 						}
@@ -88,7 +119,11 @@ public abstract class MapGrouter
 						numTilesUsed++;
 						g.drawImage(tile, xOffset, yOffset, null);
 					}
-					else numTilesMissing++;
+					else
+					{
+						// null tile, that means it's either not available or really slow to start downloading
+						numTilesMissing++;
+					}
 				}
 				yOffset += 256;
 			}
@@ -112,7 +147,7 @@ public abstract class MapGrouter
 	 * @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)
+	public synchronized GroutedImage getMapImage(Track inTrack, MapSource inMapSource, int inZoom)
 	{
 		if (_lastGroutedImage == null) {
 			_lastGroutedImage = createMapImage(inTrack, inMapSource, inZoom);
@@ -136,4 +171,10 @@ public abstract class MapGrouter
 		final int pixCount = (int) (extents.getXRange().getRange() * zoomFactor * 256);
 		return pixCount > 2 && pixCount < 4000;
 	}
+
+	/** React to tiles being updated by the tile manager */
+	public void tilesUpdated(boolean inIsOk)
+	{
+		// Doesn't need any action
+	}
 }
diff --git a/tim/prune/save/PovExporter.java b/tim/prune/save/PovExporter.java
index ba66058..19e4485 100644
--- a/tim/prune/save/PovExporter.java
+++ b/tim/prune/save/PovExporter.java
@@ -26,27 +26,32 @@ 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.FunctionLibrary;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.data.NumberUtils;
 import tim.prune.data.Track;
 import tim.prune.function.Export3dFunction;
+import tim.prune.function.srtm.LookupSrtmFunction;
+import tim.prune.gui.BaseImageDefinitionPanel;
 import tim.prune.gui.DialogCloser;
+import tim.prune.gui.TerrainDefinitionPanel;
 import tim.prune.gui.map.MapSource;
 import tim.prune.gui.map.MapSourceLibrary;
 import tim.prune.load.GenericFileFilter;
+import tim.prune.threedee.ImageDefinition;
+import tim.prune.threedee.TerrainCache;
+import tim.prune.threedee.TerrainDefinition;
+import tim.prune.threedee.TerrainHelper;
 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 implements DataSubscriber
+public class PovExporter extends Export3dFunction
 {
 	private Track _track = null;
 	private JDialog _dialog = null;
@@ -55,9 +60,10 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 	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;
+	/** Panel for defining the base image */
+	private BaseImageDefinitionPanel _baseImagePanel = null;
+	/** Component for defining the terrain */
+	private TerrainDefinitionPanel _terrainPanel = null;
 
 	// defaults
 	private static final double DEFAULT_CAMERA_DISTANCE = 30.0;
@@ -107,17 +113,17 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 	 */
 	public void begin()
 	{
-		// Make dialog window to select angles, colours etc
+		// Make dialog window to select inputs
 		if (_dialog == null)
 		{
 			_dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
 			_dialog.setLocationRelativeTo(_parentFrame);
 			_dialog.getContentPane().add(makeDialogComponents());
 		}
-		// Make base image dialog
-		if (_baseImageConfig == null)
-		{
-			_baseImageConfig = new BaseImageConfigDialog(this, _dialog, _track);
+		// Get exaggeration factor from config
+		final int exaggFactor = Config.getConfigInt(Config.KEY_HEIGHT_EXAGGERATION);
+		if (exaggFactor > 0) {
+			_altFactor = exaggFactor / 100.0;
 		}
 
 		// Set angles
@@ -125,7 +131,14 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 		_cameraYField.setText(_cameraY);
 		_cameraZField.setText(_cameraZ);
 		_altitudeFactorField.setText("" + _altFactor);
-		updateBaseImageDetails();
+		// Pass terrain and image def parameters (if any) to the panels
+		if (_terrainDef != null) {
+			_terrainPanel.initTerrainParameters(_terrainDef);
+		}
+		if (_imageDef != null) {
+			_baseImagePanel.initImageParameters(_imageDef);
+		}
+		_baseImagePanel.updateBaseImageDetails();
 		// Show dialog
 		_dialog.pack();
 		_dialog.setVisible(true);
@@ -150,8 +163,14 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 		okButton.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent e)
 			{
-				doExport();
-				MapGrouter.clearMapImage();
+				// Need to launch export in new thread
+				new Thread(new Runnable() {
+					public void run()
+					{
+						doExport();
+						_baseImagePanel.getGrouter().clearMapImage();
+					}
+				}).start();
 				_dialog.dispose();
 			}
 		});
@@ -160,7 +179,7 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 		cancelButton.addActionListener(new ActionListener() {
 			public void actionPerformed(ActionEvent e)
 			{
-				MapGrouter.clearMapImage();
+				_baseImagePanel.getGrouter().clearMapImage();
 				_dialog.dispose();
 			}
 		});
@@ -223,26 +242,10 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 		group.add(_ballsAndSticksButton); group.add(tubesButton);
 		stylePanel.add(radioPanel);
 
-		// 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);
+		// Panel for the base image (parent is null because we don't need callback)
+		_baseImagePanel = new BaseImageDefinitionPanel(null, _dialog, _track);
+		// Panel for the terrain definition
+		_terrainPanel = new TerrainDefinitionPanel();
 
 		// add these panels to the holder panel
 		JPanel holderPanel = new JPanel();
@@ -253,48 +256,15 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 		boxPanel.add(Box.createVerticalStrut(4));
 		boxPanel.add(stylePanel);
 		boxPanel.add(Box.createVerticalStrut(4));
-		boxPanel.add(imageHolderPanel);
+		boxPanel.add(_terrainPanel);
+		boxPanel.add(Box.createVerticalStrut(4));
+		boxPanel.add(_baseImagePanel);
 		holderPanel.add(boxPanel, BorderLayout.CENTER);
 
 		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
@@ -333,20 +303,26 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 				}
 				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();
+				final File terrainFile = new File(povFile.getParentFile(), povFile.getName().substring(0, nameLen) + "_terrain.png");
+				final boolean imageExists = _baseImagePanel.getImageDefinition().getUseImage() && imageFile.exists();
+				final boolean terrainFileExists = _terrainPanel.getUseTerrain() && terrainFile.exists();
+
 				// Check if files exist and if necessary prompt for overwrite
 				Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
-				if ((!povFile.exists() && !imageExists) || JOptionPane.showOptionDialog(_parentFrame,
+				if ((!povFile.exists() && !imageExists && !terrainFileExists)
+					|| 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(povFile, imageFile))
+					// Export the file(s)
+					if (exportFiles(povFile, imageFile, terrainFile))
 					{
 						// file saved - store directory in config for later
 						Config.setConfigString(Config.KEY_TRACK_DIR, povFile.getParentFile().getAbsolutePath());
+						// also store exaggeration
+						Config.setConfigInt(Config.KEY_HEIGHT_EXAGGERATION, (int) (_altFactor * 100));
 					}
 					else
 					{
@@ -365,12 +341,13 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 
 
 	/**
-	 * Export the track data to the specified file
+	 * Export the data to the specified file(s)
 	 * @param inPovFile File object to save pov file to
 	 * @param inImageFile file object to save image to
+	 * @param inTerrainFile file object to save terrain to
 	 * @return true if successful
 	 */
-	private boolean exportFile(File inPovFile, File inImageFile)
+	private boolean exportFiles(File inPovFile, File inImageFile, File inTerrainFile)
 	{
 		FileWriter writer = null;
 		// find out the line separator for this system
@@ -383,20 +360,23 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 			try
 			{
 				// try to use given altitude cap
-				double altFactor = Double.parseDouble(_altitudeFactorField.getText());
-				model.setAltitudeFactor(altFactor);
+				double givenFactor = Double.parseDouble(_altitudeFactorField.getText());
+				if (givenFactor > 0.0) _altFactor = givenFactor;
 			}
 			catch (NumberFormatException nfe) { // parse failed, reset
-				_altitudeFactorField.setText("1.0");
+				_altitudeFactorField.setText("" + _altFactor);
 			}
-			model.scale();
+			model.setAltitudeFactor(_altFactor);
 
-			boolean useImage = _baseImageConfig.useImage();
+			// Write base image if necessary
+			ImageDefinition imageDef = _baseImagePanel.getImageDefinition();
+			boolean useImage = imageDef.getUseImage();
 			if (useImage)
 			{
 				// Get base image from grouter
-				MapSource mapSource = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex());
-				GroutedImage baseImage = MapGrouter.getMapImage(_track, mapSource, _baseImageConfig.getZoomLevel());
+				MapSource mapSource = MapSourceLibrary.getSource(imageDef.getSourceIndex());
+				MapGrouter grouter = _baseImagePanel.getGrouter();
+				GroutedImage baseImage = grouter.getMapImage(_track, mapSource, imageDef.getZoom());
 				try
 				{
 					useImage = ImageIO.write(baseImage.getImage(), "png", inImageFile);
@@ -410,9 +390,49 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 				}
 			}
 
+			boolean useTerrain = _terrainPanel.getUseTerrain();
+			if (useTerrain)
+			{
+				TerrainHelper terrainHelper = new TerrainHelper(_terrainPanel.getGridSize());
+				// See if there's a previously saved terrain track we can reuse
+				TerrainDefinition terrainDef = new TerrainDefinition(_terrainPanel.getUseTerrain(), _terrainPanel.getGridSize());
+				Track terrainTrack = TerrainCache.getTerrainTrack(_app.getCurrentDataStatus(), terrainDef);
+				if (terrainTrack == null)
+				{
+					// Construct the terrain track according to these extents and the grid size
+					terrainTrack = terrainHelper.createGridTrack(_track);
+					// Get the altitudes from SRTM for all the points in the track
+					LookupSrtmFunction srtmLookup = (LookupSrtmFunction) FunctionLibrary.FUNCTION_LOOKUP_SRTM;
+					srtmLookup.begin(terrainTrack);
+					while (srtmLookup.isRunning())
+					{
+						try {
+							Thread.sleep(750);  // just polling in a wait loop isn't ideal but simple
+						}
+						catch (InterruptedException e) {}
+					}
+					// Fix the voids
+					terrainHelper.fixVoids(terrainTrack);
+
+					// Store this back in the cache, maybe we'll need it again
+					TerrainCache.storeTerrainTrack(terrainTrack, _app.getCurrentDataStatus(), terrainDef);
+				}
+
+				model.setTerrain(terrainTrack);
+				model.scale();
+
+				// Call TerrainHelper to write out the data from the model
+				terrainHelper.writeHeightMap(model, inTerrainFile);
+			}
+			else
+			{
+				// No terrain required, so just scale the model as it is
+				model.scale();
+			}
+
 			// Create file and write basics
 			writer = new FileWriter(inPovFile);
-			writeStartOfFile(writer, lineSeparator, useImage ? inImageFile : null);
+			writeStartOfFile(writer, lineSeparator, useImage ? inImageFile : null, useTerrain ? inTerrainFile : null);
 
 			// write out points
 			if (_ballsAndSticksButton.isSelected()) {
@@ -451,12 +471,15 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 	 * @param inWriter Writer to use for writing file
 	 * @param inLineSeparator line separator to use
 	 * @param inImageFile image file to reference (or null if none)
+	 * @param inTerrainFile terrain file to reference (or null if none)
 	 * @throws IOException on file writing error
 	 */
-	private void writeStartOfFile(FileWriter inWriter, String inLineSeparator, File inImageFile)
+	private void writeStartOfFile(FileWriter inWriter, String inLineSeparator, File inImageFile, File inTerrainFile)
 	throws IOException
 	{
-		inWriter.write("// Pov file produced by GpsPrune - see http://activityworkshop.net/");
+		inWriter.write("// Pov file produced by GpsPrune - see http://gpsprune.activityworkshop.net/");
+		inWriter.write(inLineSeparator);
+		inWriter.write("#version 3.6;");
 		inWriter.write(inLineSeparator);
 		inWriter.write(inLineSeparator);
 		// Select font based on user input
@@ -471,17 +494,20 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 
 		// 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> }"
-			:
+		final boolean useImageOnBox = useImage && (inTerrainFile == null);
+		final String boxDefinition = (useImageOnBox ?
 			"   <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>");
+				+ "   translate <-10.0, 0, -10.0>"
+			: "   <-10.0, -0.15, -10.0>," + inLineSeparator
+				+ "   <10.0, 0.0, 10.0>" + inLineSeparator
+				+ "   pigment { color rgb <0.5 0.75 0.8> }");
 		// TODO: Maybe could use the same geometry for the imageless case, would simplify code a bit
 
+		// Definition of terrain shape if any
+		final String terrainDefinition = makeTerrainString(inTerrainFile, inImageFile, inLineSeparator);
+
 		// Set up output
 		String[] outputLines = {
 		  "global_settings { ambient_light rgb <4, 4, 4> }", "",
@@ -574,6 +600,8 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 		  "box {",
 		  boxDefinition,
 		  "}", "",
+		  // terrain
+		  terrainDefinition,
 		// write cardinals
 		  "// Cardinal letters N,S,E,W",
 		  "text {",
@@ -612,6 +640,31 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 		}
 	}
 
+	/**
+	 * Make a description of the height_field object for the terrain, depending on terrain and image
+	 * @param inTerrainFile terrain file, or null if none
+	 * @param inImageFile image file, or null if none
+	 * @param inLineSeparator line separator
+	 * @return String for inserting into pov file
+	 */
+	private static String makeTerrainString(File inTerrainFile, File inImageFile, String inLineSeparator)
+	{
+		if (inTerrainFile == null) {return "";}
+		StringBuilder sb = new StringBuilder();
+		sb.append("//Terrain").append(inLineSeparator)
+			.append("height_field {").append(inLineSeparator)
+			.append("\tpng \"").append(inTerrainFile.getName()).append("\" smooth").append(inLineSeparator)
+			.append("\tfinish {diffuse 0.7 phong 0.2}").append(inLineSeparator);
+		if (inImageFile != null) {
+			sb.append("\tpigment {image_map { png \"").append(inImageFile.getName()).append("\"  } rotate x*90}").append(inLineSeparator);
+		}
+		else {
+			sb.append("\tpigment {color rgb <0.55 0.7 0.55> }").append(inLineSeparator);
+		}
+		sb.append("\tscale 20.0").append(inLineSeparator)
+			.append("\ttranslate <-10.0, 0, -10.0>").append(inLineSeparator).append("}");
+		return sb.toString();
+	}
 
 	/**
 	 * Write out all the data points to the file in the balls-and-sticks style
@@ -668,7 +721,6 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 		inWriter.write("// Data points:");
 		inWriter.write(inLineSeparator);
 		int numPoints = inModel.getNumPoints();
-		int numTrackPoints = 0;
 		// Loop over all points and write out waypoints as balls
 		for (int i=0; i<numPoints; i++)
 		{
@@ -686,7 +738,6 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 				}
 				inWriter.write(inLineSeparator);
 			}
-			else {numTrackPoints++;}
 		}
 		inWriter.write(inLineSeparator);
 
@@ -878,16 +929,4 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
 		}
 		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 33f42f9..d623b4a 100644
--- a/tim/prune/save/SvgExporter.java
+++ b/tim/prune/save/SvgExporter.java
@@ -86,13 +86,18 @@ public class SvgExporter extends Export3dFunction
 	 */
 	public void begin()
 	{
-		// Make dialog window to select angles, colours etc
+		// Make dialog window to select input parameters
 		if (_dialog == null)
 		{
 			_dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
 			_dialog.setLocationRelativeTo(_parentFrame);
 			_dialog.getContentPane().add(makeDialogComponents());
 		}
+		// Get exaggeration factor from config
+		final int exaggFactor = Config.getConfigInt(Config.KEY_HEIGHT_EXAGGERATION);
+		if (exaggFactor > 0) {
+			_altFactor = exaggFactor / 100.0;
+		}
 
 		// Set angles
 		NumberFormat threeDP = NumberFormat.getNumberInstance();
@@ -228,6 +233,8 @@ public class SvgExporter extends Export3dFunction
 					{
 						// file saved - store directory in config for later
 						Config.setConfigString(Config.KEY_TRACK_DIR, file.getParentFile().getAbsolutePath());
+						// also store exaggeration
+						Config.setConfigInt(Config.KEY_HEIGHT_EXAGGERATION, (int) (_altFactor * 100));
 					}
 					else {
 						// export failed so need to choose again
diff --git a/tim/prune/save/xml/ByteBuffer.java b/tim/prune/save/xml/ByteBuffer.java
new file mode 100644
index 0000000..67091e1
--- /dev/null
+++ b/tim/prune/save/xml/ByteBuffer.java
@@ -0,0 +1,88 @@
+package tim.prune.save.xml;
+
+import java.nio.charset.Charset;
+
+/**
+ * Class to collect the bytes from an input stream
+ * and turn them into a String
+ */
+public class ByteBuffer
+{
+	// Array of bytes
+	private byte[] _bytes = new byte[1024];
+	// Current position to append
+	private int _currPos = 0;
+	// Flag for recognising utf8 encoded streams
+	private boolean _streamUtf8 = false;
+	// Flag for whether system is utf8 or not
+	private final boolean _systemUtf8 = XmlUtils.isSystemUtf8();
+
+	/**
+	 * Append the given byte to the buffer
+	 * @param inB byte to append
+	 */
+	public void appendByte(byte inB)
+	{
+		// Resize array if necessary
+		if (_currPos >= _bytes.length)
+		{
+			byte[] bigger = new byte[_bytes.length * 2];
+			System.arraycopy(_bytes, 0, bigger, 0, _bytes.length);
+			_bytes = bigger;
+		}
+		// Append byte and increment counter
+		_bytes[_currPos] = inB;
+		_currPos++;
+	}
+
+	/**
+	 * Clear the buffer and reset
+	 */
+	public void clear()
+	{
+		_currPos = 0;
+		// Reduce size back to default if it's got too big
+		if (_bytes.length > 5000) {
+			_bytes = new byte[1024];
+		}
+	}
+
+	/**
+	 * Set the flag that this stream is encoded with utf8
+	 */
+	public void setEncodingUtf8() {
+		_streamUtf8 = true;
+	}
+
+	/**
+	 * @return contents of buffer as a String
+	 */
+	public String toString()
+	{
+		// Sometimes the encoding of the read file isn't the default encoding of the system
+		if (_streamUtf8 && !_systemUtf8)
+		{
+			return new String(_bytes, 0, _currPos, Charset.forName("UTF-8"));
+		}
+		// Otherwise just use system encoding
+		return new String(_bytes, 0, _currPos);
+	}
+
+	/**
+	 * Look for the given character sequence in the last characters read
+	 * @param inChars sequence to look for
+	 * @return true if sequence found
+	 */
+	public boolean foundSequence(char[] inChars)
+	{
+		final int numChars = inChars.length;
+		if (_currPos < numChars) {return false;}
+		for (int i=0; i<numChars; i++)
+		{
+			char searchChar = inChars[numChars - 1 - i];
+			char sourceChar = (char) _bytes[_currPos - 1 - i];
+			if (searchChar != sourceChar) {return false;}
+		}
+		return true;
+	}
+}
diff --git a/tim/prune/save/xml/GpxSlicer.java b/tim/prune/save/xml/GpxSlicer.java
index efc58b4..d18d760 100644
--- a/tim/prune/save/xml/GpxSlicer.java
+++ b/tim/prune/save/xml/GpxSlicer.java
@@ -12,8 +12,6 @@ public class GpxSlicer
 {
 	/** listener to receive tags */
 	private TagReceiver _receiver = null;
-	/** string builder for copying source xml */
-	private StringBuilder _builder = null;
 
 	// character sequences for start and end of tags
 	private static final char[] GPX_START = "<gpx".toCharArray();
@@ -27,7 +25,6 @@ public class GpxSlicer
 	private static final char[] CDATA_START = "<![CDATA[".toCharArray();
 	private static final char[] CDATA_END = "]]>".toCharArray();
 
-
 	/**
 	 * Constructor
 	 * @param inReceiver listener for tags
@@ -43,7 +40,8 @@ public class GpxSlicer
 	 */
 	public void slice(InputStream inStream)
 	{
-		_builder = new StringBuilder(100);
+		StringBuffer beginBuffer = new StringBuffer(200);
+		ByteBuffer byteBuffer = new ByteBuffer();
 		boolean insideTag = false;
 		boolean insideCdata = false;
 		char[] endTag = null;
@@ -53,37 +51,49 @@ public class GpxSlicer
 		{
 			while ((b = inStream.read()) >= 0)
 			{
-				if (!insideTag && !insideCdata) {
-					if (b == '<') _builder.setLength(0);
-				}
 				// copy character
-				_builder.append((char)b);
+				byteBuffer.appendByte((byte) b);
+				// clear buffer if necessary
+				if (!insideTag && !insideCdata && (b == '>' || b == '\n'))
+				{
+					byteBuffer.clear();
+					continue;
+				}
+				// if we're still at the beginning, copy to the begin buffer as well
+				if (beginBuffer != null) {beginBuffer.append((char) b);}
 
 				if (insideCdata) {
 					// Just look for end of cdata block
-					if (foundSequence(CDATA_END)) {insideCdata = false;}
+					if (byteBuffer.foundSequence(CDATA_END)) {insideCdata = false;}
 				}
 				else
 				{
 					if (!insideTag)
 					{
 						// Look for start of one of the tags
-						if (!foundHeader && foundSequence(GPX_START)) {
+						if (!foundHeader && byteBuffer.foundSequence(GPX_START))
+						{
 							insideTag = true;
 							foundHeader = true;
 							endTag = GPX_END;
+							// Check begin buffer for utf8 encoding
+							if (beginBuffer != null && beginBuffer.toString().toLowerCase().indexOf("encoding=\"utf-8\"") > 0)
+							{
+								byteBuffer.setEncodingUtf8();
+							}
+							beginBuffer = null; // don't need it any more
 						}
 						else if (b == 't')
 						{
-							if (foundSequence(TRKPT_START)) {
+							if (byteBuffer.foundSequence(TRKPT_START)) {
 								insideTag = true;
 								endTag = TRKPT_END;
 							}
-							else if (foundSequence(WPT_START)) {
+							else if (byteBuffer.foundSequence(WPT_START)) {
 								insideTag = true;
 								endTag = WPT_END;
 							}
-							else if (foundSequence(RTEPT_START)) {
+							else if (byteBuffer.foundSequence(RTEPT_START)) {
 								insideTag = true;
 								endTag = RTEPT_END;
 							}
@@ -92,37 +102,21 @@ public class GpxSlicer
 					else
 					{
 						// Look for end of found tag
-						if (foundSequence(endTag)) {
-							_receiver.reportTag(_builder.toString());
-							_builder.setLength(0);
+						if (byteBuffer.foundSequence(endTag))
+						{
+							String tag = byteBuffer.toString();
+							_receiver.reportTag(tag);
+							byteBuffer.clear();
 							insideTag = false;
 						}
 					}
 					// Look for start of cdata block
-					if (foundSequence(CDATA_START)) {insideCdata = true;}
+					if (byteBuffer.foundSequence(CDATA_START)) {
+						insideCdata = true;
+					}
 				}
 			}
 		}
 		catch (IOException e) {} // ignore
 	}
-
-	/**
-	 * Look for the given character sequence in the last characters read
-	 * @param inChars sequence to look for
-	 * @return true if sequence found
-	 */
-	private boolean foundSequence(char[] inChars)
-	{
-		final int numChars = inChars.length;
-		final int bufflen = _builder.length();
-		if (bufflen < numChars) {return false;}
-		for (int i=0; i<numChars; i++)
-		{
-			char searchChar = inChars[numChars - 1 - i];
-			char sourceChar = _builder.charAt(bufflen - 1 - i);
-			if (searchChar != sourceChar) {return false;}
-			//if (Character.toLowerCase(searchChar) != Character.toLowerCase(sourceChar)) {return false;}
-		}
-		return true;
-	}
 }
diff --git a/tim/prune/save/xml/XmlUtils.java b/tim/prune/save/xml/XmlUtils.java
index a4f9cae..dc8284c 100644
--- a/tim/prune/save/xml/XmlUtils.java
+++ b/tim/prune/save/xml/XmlUtils.java
@@ -1,5 +1,11 @@
 package tim.prune.save.xml;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+
 /**
  * Collection of utility functions for handling XML
  */
@@ -9,6 +15,8 @@ public abstract class XmlUtils
 	private static final String CDATA_START = "<![CDATA[";
 	/** End of Cdata sequence */
 	private static final String CDATA_END = "]]>";
+	/** Cached copy of system encoding string */
+	private static String _systemEncoding = null;
 
 	/**
 	 * Fix the CDATA blocks in the given String to give valid xml
@@ -34,4 +42,68 @@ public abstract class XmlUtils
 		}
 		return CDATA_START + result + CDATA_END;
 	}
+
+
+	/**
+	 * @return true if system uses UTF-8 by default
+	 */
+	public static boolean isSystemUtf8()
+	{
+		String systemEncoding = getSystemEncoding();
+		return (systemEncoding != null && systemEncoding.toUpperCase().equals("UTF-8"));
+	}
+
+	/**
+	 * @return name of the system's character encoding
+	 */
+	public static String getSystemEncoding()
+	{
+		if (_systemEncoding == null) {
+			_systemEncoding = determineSystemEncoding();
+		}
+		return _systemEncoding;
+	}
+
+	/**
+	 * Use a temporary file to obtain the name of the default system encoding
+	 * @return name of default system encoding, or null if write failed
+	 */
+	private static String determineSystemEncoding()
+	{
+		File tempFile = null;
+		String encoding = null;
+		try
+		{
+			tempFile = File.createTempFile("gpsprune", null);
+			OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile));
+			encoding = getEncoding(writer);
+			writer.close();
+		}
+		catch (IOException e) {} // value stays null
+		// Delete temp file
+		if (tempFile != null && tempFile.exists()) {
+			if (!tempFile.delete()) {
+				System.err.println("Cannot delete temp file: " + tempFile.getAbsolutePath());
+			}
+		}
+		// If writing failed (eg permissions) then just ask system for default
+		if (encoding == null) encoding = Charset.defaultCharset().name();
+		return encoding;
+	}
+
+
+	/**
+	 * Get the default system encoding using a writer
+	 * @param inWriter writer object
+	 * @return string defining encoding
+	 */
+	public static String getEncoding(OutputStreamWriter inWriter)
+	{
+		String encoding = inWriter.getEncoding();
+		try {
+			encoding =  Charset.forName(encoding).name();
+		}
+		catch (Exception e) {} // ignore failure to find encoding
+		return encoding;
+	}
 }
diff --git a/tim/prune/threedee/ImageDefinition.java b/tim/prune/threedee/ImageDefinition.java
new file mode 100644
index 0000000..a456f37
--- /dev/null
+++ b/tim/prune/threedee/ImageDefinition.java
@@ -0,0 +1,66 @@
+package tim.prune.threedee;
+
+/**
+ * Holds the definition of the image to use
+ * (whether or not to use an image, and the source index and zoom)
+ */
+public class ImageDefinition
+{
+	private boolean _useImage    = false;
+	private int     _sourceIndex = 0;
+	private int     _zoom        = 0;
+
+
+	/**
+	 * Empty constructor specifying no image
+	 */
+	public ImageDefinition()
+	{
+		this(false, 0, 0);
+	}
+
+	/**
+	 * Constructor
+	 * @param inUse true to use an image
+	 * @param inSourceIndex index of map source
+	 * @param inZoom zoom level
+	 */
+	public ImageDefinition(boolean inUse, int inSourceIndex, int inZoom)
+	{
+		setUseImage(inUse, inSourceIndex, inZoom);
+	}
+
+	/**
+	 * Set the parameters
+	 * @param inUse true to use an image
+	 * @param inSourceIndex index of map source
+	 * @param inZoom zoom level
+	 */
+	public void setUseImage(boolean inUse, int inSourceIndex, int inZoom)
+	{
+		_useImage = inUse;
+		_sourceIndex = inSourceIndex;
+		_zoom = inZoom;
+	}
+
+	/**
+	 * @return true if image should be used, false otherwise
+	 */
+	public boolean getUseImage() {
+		return _useImage && _sourceIndex >= 0 && _zoom > 2;
+	}
+
+	/**
+	 * @return source index
+	 */
+	public int getSourceIndex() {
+		return _sourceIndex;
+	}
+
+	/**
+	 * @return zoom level
+	 */
+	public int getZoom() {
+		return _zoom;
+	}
+}
diff --git a/tim/prune/threedee/Java3DWindow.java b/tim/prune/threedee/Java3DWindow.java
index cfac9af..de52bd6 100644
--- a/tim/prune/threedee/Java3DWindow.java
+++ b/tim/prune/threedee/Java3DWindow.java
@@ -1,7 +1,7 @@
 package tim.prune.threedee;
 
-import java.awt.FlowLayout;
 import java.awt.BorderLayout;
+import java.awt.FlowLayout;
 import java.awt.Font;
 import java.awt.GraphicsConfiguration;
 import java.awt.GraphicsEnvironment;
@@ -18,14 +18,18 @@ import javax.media.j3d.BranchGroup;
 import javax.media.j3d.Canvas3D;
 import javax.media.j3d.Font3D;
 import javax.media.j3d.FontExtrusion;
+import javax.media.j3d.GeometryArray;
 import javax.media.j3d.GraphicsConfigTemplate3D;
 import javax.media.j3d.Group;
 import javax.media.j3d.Material;
 import javax.media.j3d.PointLight;
+import javax.media.j3d.QuadArray;
 import javax.media.j3d.Shape3D;
 import javax.media.j3d.Text3D;
+import javax.media.j3d.Texture;
 import javax.media.j3d.Transform3D;
 import javax.media.j3d.TransformGroup;
+import javax.media.j3d.TriangleStripArray;
 import javax.swing.JButton;
 import javax.swing.JFrame;
 import javax.swing.JOptionPane;
@@ -34,19 +38,26 @@ import javax.vecmath.Color3f;
 import javax.vecmath.Matrix3d;
 import javax.vecmath.Point3d;
 import javax.vecmath.Point3f;
+import javax.vecmath.TexCoord2f;
 import javax.vecmath.Vector3d;
 
+import tim.prune.DataStatus;
+import tim.prune.FunctionLibrary;
+import tim.prune.I18nManager;
+import tim.prune.data.Track;
+import tim.prune.function.Export3dFunction;
+import tim.prune.function.srtm.LookupSrtmFunction;
+import tim.prune.gui.map.MapSourceLibrary;
+import tim.prune.save.GroutedImage;
+import tim.prune.save.MapGrouter;
+
 import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
 import com.sun.j3d.utils.geometry.Box;
 import com.sun.j3d.utils.geometry.Cylinder;
 import com.sun.j3d.utils.geometry.Sphere;
+import com.sun.j3d.utils.image.TextureLoader;
 import com.sun.j3d.utils.universe.SimpleUniverse;
 
-import tim.prune.FunctionLibrary;
-import tim.prune.I18nManager;
-import tim.prune.data.Track;
-import tim.prune.function.Export3dFunction;
-
 
 /**
  * Class to hold main window for java3d view of data
@@ -58,7 +69,11 @@ public class Java3DWindow implements ThreeDWindow
 	private JFrame _frame = null;
 	private ThreeDModel _model = null;
 	private OrbitBehavior _orbit = null;
-	private double _altFactor = 5.0;
+	private double _altFactor = -1.0;
+	private ImageDefinition _imageDefinition = null;
+	private GroutedImage _baseImage = null;
+	private TerrainDefinition _terrainDefinition = null;
+	private DataStatus _dataStatus = null;
 
 	/** only prompt about big track size once */
 	private static boolean TRACK_SIZE_WARNING_GIVEN = false;
@@ -90,23 +105,52 @@ public class Java3DWindow implements ThreeDWindow
 		_track = inTrack;
 	}
 
+	/**
+	 * @param inFactor altitude factor to use
+	 */
+	public void setAltitudeFactor(double inFactor)
+	{
+		_altFactor = inFactor;
+	}
+
+	/**
+	 * Set the parameters for the base image and do the grouting already
+	 * (setTrack should already be called by now)
+	 */
+	public void setBaseImageParameters(ImageDefinition inDefinition)
+	{
+		_imageDefinition = inDefinition;
+		if (inDefinition != null && inDefinition.getUseImage())
+		{
+			_baseImage = new MapGrouter().createMapImage(_track, MapSourceLibrary.getSource(inDefinition.getSourceIndex()),
+				inDefinition.getZoom());
+		}
+		else _baseImage = null;
+	}
+
+	/**
+	 * Set the terrain parameters
+	 */
+	public void setTerrainParameters(TerrainDefinition inDefinition)
+	{
+		_terrainDefinition = inDefinition;
+	}
+
+	/**
+	 * Set the current data status
+	 */
+	public void setDataStatus(DataStatus inStatus)
+	{
+		_dataStatus = inStatus;
+	}
 
 	/**
 	 * Show the window
 	 */
 	public void show() throws ThreeDException
 	{
-		// Get the altitude exaggeration to use
-		Object factorString = JOptionPane.showInputDialog(_parentFrame,
-			I18nManager.getText("dialog.3d.altitudefactor"),
-			I18nManager.getText("dialog.3d.title"),
-			JOptionPane.QUESTION_MESSAGE, null, null, _altFactor);
-		if (factorString == null) return;
-		try {
-			_altFactor = Double.parseDouble(factorString.toString());
-		}
-		catch (Exception e) {} // Ignore parse errors
-		if (_altFactor < 1.0) {_altFactor = 1.0;}
+		// Make sure altitude exaggeration is positive
+		if (_altFactor < 0.0) {_altFactor = 1.0;}
 
 		// Set up the graphics config
 		GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
@@ -270,6 +314,71 @@ public class Java3DWindow implements ThreeDWindow
 		plane = new Box(10f, 0.04f, 10f, planeAppearance);
 		objTrans.addChild(plane);
 
+		// Image on top of base plane, if specified
+		final boolean showTerrain = _terrainDefinition != null && _terrainDefinition.getUseTerrain();
+		if (_baseImage != null && !showTerrain)
+		{
+			QuadArray baseSquare = new QuadArray (4, QuadArray.COORDINATES | GeometryArray.TEXTURE_COORDINATE_2);
+			baseSquare.setCoordinate(0, new Point3f(-10f, 0.05f, -10f));
+			baseSquare.setCoordinate(1, new Point3f(-10f, 0.05f, 10f));
+			baseSquare.setCoordinate(2, new Point3f( 10f, 0.05f, 10f));
+			baseSquare.setCoordinate(3, new Point3f( 10f, 0.05f, -10f));
+			// and set anchor points for the texture
+			baseSquare.setTextureCoordinate(0, 0, new TexCoord2f(0.0f, 1.0f));
+			baseSquare.setTextureCoordinate(0, 1, new TexCoord2f(0.0f, 0.0f));
+			baseSquare.setTextureCoordinate(0, 2, new TexCoord2f(1.0f, 0.0f));
+			baseSquare.setTextureCoordinate(0, 3, new TexCoord2f(1.0f, 1.0f));
+			// Set appearance including image
+			Appearance baseAppearance = new Appearance();
+			Texture mapImage = new TextureLoader(_baseImage.getImage(), _frame).getTexture();
+			baseAppearance.setTexture(mapImage);
+			objTrans.addChild(new Shape3D(baseSquare, baseAppearance));
+		}
+
+		// Create model containing track information
+		_model = new ThreeDModel(_track);
+		_model.setAltitudeFactor(_altFactor);
+
+		if (showTerrain)
+		{
+			TerrainHelper terrainHelper = new TerrainHelper(_terrainDefinition.getGridSize());
+			// See if there's a previously saved terrain track we can reuse
+			Track terrainTrack = TerrainCache.getTerrainTrack(_dataStatus, _terrainDefinition);
+			if (terrainTrack == null)
+			{
+				// Construct the terrain track according to these extents and the grid size
+				terrainTrack = terrainHelper.createGridTrack(_track);
+				// Get the altitudes from SRTM for all the points in the track
+				LookupSrtmFunction srtmLookup = (LookupSrtmFunction) FunctionLibrary.FUNCTION_LOOKUP_SRTM;
+				srtmLookup.begin(terrainTrack);
+				while (srtmLookup.isRunning())
+				{
+					try {
+						Thread.sleep(750);  // just polling in a wait loop isn't ideal but simple
+					}
+					catch (InterruptedException e) {}
+				}
+	
+				// Fix the voids
+				terrainHelper.fixVoids(terrainTrack);
+
+				// Store this back in the cache, maybe we'll need it again
+				TerrainCache.storeTerrainTrack(terrainTrack, _dataStatus, _terrainDefinition);
+			}
+			else System.out.println("Yay - reusing the cached track!");
+
+			// Give the terrain definition to the _model as well
+			_model.setTerrain(terrainTrack);
+			_model.scale();
+
+			objTrans.addChild(createTerrain(_model, terrainHelper, _baseImage));
+		}
+		else
+		{
+			// No terrain, so just scale the model as it is
+			_model.scale();
+		}
+
 		// N, S, E, W
 		GeneralPath bevelPath = new GeneralPath();
 		bevelPath.moveTo(0.0f, 0.0f);
@@ -289,11 +398,6 @@ public class Java3DWindow implements ThreeDWindow
 		objTrans.addChild(createCompassPoint(I18nManager.getText("cardinal.w"), new Point3f(-11f, 0f, 0f), compassFont));
 		objTrans.addChild(createCompassPoint(I18nManager.getText("cardinal.e"), new Point3f(10f, 0f, 0f), compassFont));
 
-		// create and scale model
-		_model = new ThreeDModel(_track);
-		_model.setAltitudeFactor(_altFactor);
-		_model.scale();
-
 		// Add points to model
 		objTrans.addChild(createDataPoints(_model));
 
@@ -407,7 +511,9 @@ public class Java3DWindow implements ThreeDWindow
 	}
 
 
-	/** @return track point object */
+	/**
+	 * @return track point object
+	 */
 	private static Group createTrackpoint(Point3d inPointPos, byte inHeightCode)
 	{
 		Material mat = getTrackpointMaterial(inHeightCode);
@@ -417,7 +523,9 @@ public class Java3DWindow implements ThreeDWindow
 	}
 
 
-	/** @return Material object for track points with the appropriate colour for the height */
+	/**
+	 * @return Material object for track points with the appropriate colour for the height
+	 */
 	private static Material getTrackpointMaterial(byte inHeightCode)
 	{
 		// create default material
@@ -473,6 +581,50 @@ public class Java3DWindow implements ThreeDWindow
 		return group;
 	}
 
+	/**
+	 * Create a java3d Shape for the terrain
+	 * @param inModel threedModel
+	 * @param inHelper terrain helper
+	 * @param inBaseImage base image for shape, or null for no image
+	 * @return Shape3D object
+	 */
+	private static Shape3D createTerrain(ThreeDModel inModel, TerrainHelper inHelper, GroutedImage inBaseImage)
+	{
+		final int numNodes = inHelper.getGridSize();
+		final int RESULT_SIZE = numNodes * (numNodes * 2 - 2);
+		final int GEOMETRY_COLOURING_TYPE = (inBaseImage == null ? GeometryArray.COLOR_3 : GeometryArray.TEXTURE_COORDINATE_2);
+
+		int[] stripData = inHelper.getStripLengths();
+		TriangleStripArray tsa = new TriangleStripArray(RESULT_SIZE, GeometryArray.COORDINATES | GEOMETRY_COLOURING_TYPE,
+			stripData);
+		// Get the scaled terrainTrack coordinates (or just heights) from the model
+		final int nSquared = numNodes * numNodes;
+		Point3d[] rawPoints = new Point3d[nSquared];
+		for (int i=0; i<nSquared; i++)
+		{
+			double height = inModel.getScaledTerrainValue(i) * MODEL_SCALE_FACTOR;
+			rawPoints[i] = new Point3d(inModel.getScaledTerrainHorizValue(i) * MODEL_SCALE_FACTOR,
+				Math.max(height, 0.05), // make sure it's above the box
+				-inModel.getScaledTerrainVertValue(i) * MODEL_SCALE_FACTOR);
+		}
+		tsa.setCoordinates(0, inHelper.getTerrainCoordinates(rawPoints));
+
+		Appearance tAppearance = new Appearance();
+		if (inBaseImage != null)
+		{
+			tsa.setTextureCoordinates(0, 0, inHelper.getTextureCoordinates());
+			Texture mapImage = new TextureLoader(inBaseImage.getImage()).getTexture();
+			tAppearance.setTexture(mapImage);
+		}
+		else
+		{
+			Color3f[] colours = new Color3f[RESULT_SIZE];
+			Color3f terrainColour = new Color3f(0.1f, 0.2f, 0.2f);
+			for (int i=0; i<RESULT_SIZE; i++) {colours[i] = terrainColour;}
+			tsa.setColors(0, colours);
+		}
+		return new Shape3D(tsa, tAppearance);
+	}
 
 	/**
 	 * Calculate the angles and call them back to the app
@@ -495,9 +647,13 @@ public class Java3DWindow implements ThreeDWindow
 		Point3d result = new Point3d();
 		secondTran.transform(point, result);
 		firstTran.transform(result);
-		// Callback settings to pov export function
+
+		// Give the settings to the rendering function
 		inFunction.setCameraCoordinates(result.x, result.y, result.z);
 		inFunction.setAltitudeExaggeration(_altFactor);
+		inFunction.setTerrainDefinition(_terrainDefinition); // ignored by svg, used by pov
+		inFunction.setImageDefinition(_imageDefinition);     // ignored by svg, used by pov
+
 		inFunction.begin();
 	}
 }
diff --git a/tim/prune/threedee/TerrainCache.java b/tim/prune/threedee/TerrainCache.java
new file mode 100644
index 0000000..5f5733f
--- /dev/null
+++ b/tim/prune/threedee/TerrainCache.java
@@ -0,0 +1,57 @@
+package tim.prune.threedee;
+
+import tim.prune.DataStatus;
+import tim.prune.data.Track;
+
+/**
+ * This abstract class acts as a singleton to store a single
+ * terrain model (as a Track) for a given data status and terrain definition.
+ * When the data or the definition changes, this track becomes invalid.
+ */
+public abstract class TerrainCache
+{
+	/** The data status at the time this terrain was generated */
+	private static DataStatus _dataStatus = null;
+	/** The definition (grid size) for this terrain */
+	private static TerrainDefinition _terrainDef = null;
+	/** The generated grid of points with altitudes */
+	private static Track _terrainTrack = null;
+
+
+	/**
+	 * Get the stored terrain track if it's still valid
+	 * @param inCurrStatus current data status
+	 * @param inTerrainDef currently selected terrain definition
+	 * @return stored terrain track if it's valid, null otherwise
+	 */
+	public static Track getTerrainTrack(DataStatus inCurrStatus, TerrainDefinition inTerrainDef)
+	{
+		if (_dataStatus == null || _terrainDef == null || _terrainTrack == null)
+		{
+			return null; // nothing stored
+		}
+		if (inCurrStatus == null || inTerrainDef == null || !inTerrainDef.getUseTerrain())
+		{
+			return null; // nonsense requested
+		}
+		if (inCurrStatus.hasDataChanged(_dataStatus) || !inTerrainDef.equals(_terrainDef))
+		{
+			return null; // stored track is out of date
+		}
+		// we have a match
+		return _terrainTrack;
+	}
+
+	/**
+	 * Now that a terrain track has been generated, store it for possible reuse
+	 * @param inTrack terrain track to store
+	 * @param inCurrStatus current data status
+	 * @param inTerrainDef terrain definition
+	 */
+	public static void storeTerrainTrack(Track inTrack, DataStatus inCurrStatus, TerrainDefinition inTerrainDef)
+	{
+		_terrainTrack = inTrack;
+		_dataStatus = inCurrStatus;
+		_terrainDef = inTerrainDef;
+	}
+}
diff --git a/tim/prune/threedee/TerrainDefinition.java b/tim/prune/threedee/TerrainDefinition.java
new file mode 100644
index 0000000..d6cf385
--- /dev/null
+++ b/tim/prune/threedee/TerrainDefinition.java
@@ -0,0 +1,68 @@
+package tim.prune.threedee;
+
+/**
+ * Holds the definition of the terrain to use
+ * (whether or not to use a terrain, and the resolution)
+ */
+public class TerrainDefinition
+{
+	private boolean _useTerrain = false;
+	private int     _gridSize   = 0;
+
+	/**
+	 * Empty constructor specifying no terrain
+	 */
+	public TerrainDefinition()
+	{
+		this(false, 0);
+	}
+
+	/**
+	 * Constructor
+	 * @param inUse true to use a terrain
+	 * @param inGridSize size of grid
+	 */
+	public TerrainDefinition(boolean inUse, int inGridSize)
+	{
+		setUseTerrain(inUse, inGridSize);
+	}
+
+	/**
+	 * Set the parameters
+	 * @param inUse true to use a terrain
+	 * @param inGridSize size of grid
+	 */
+	public void setUseTerrain(boolean inUse, int inGridSize)
+	{
+		_useTerrain = inUse;
+		_gridSize   = inGridSize;
+	}
+
+	/**
+	 * @return true if terrain should be used, false otherwise
+	 */
+	public boolean getUseTerrain() {
+		return _useTerrain && _gridSize > 2;
+	}
+
+	/**
+	 * @return grid size
+	 */
+	public int getGridSize() {
+		return _gridSize;
+	}
+
+	@Override
+	/**
+	 * Compare two TerrainDefinitions to see if they're equal
+	 */
+	public boolean equals(Object obj)
+	{
+		if (obj == null || !(obj instanceof TerrainDefinition)) {
+			return false;
+		}
+		TerrainDefinition other = (TerrainDefinition) obj;
+		return _useTerrain == other._useTerrain
+			&& _gridSize == other._gridSize;
+	}
+}
diff --git a/tim/prune/threedee/TerrainHelper.java b/tim/prune/threedee/TerrainHelper.java
new file mode 100644
index 0000000..0be2216
--- /dev/null
+++ b/tim/prune/threedee/TerrainHelper.java
@@ -0,0 +1,477 @@
+package tim.prune.threedee;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.vecmath.Point3d;
+import javax.vecmath.TexCoord2f;
+
+import tim.prune.data.Altitude;
+import tim.prune.data.Coordinate;
+import tim.prune.data.DataPoint;
+import tim.prune.data.DoubleRange;
+import tim.prune.data.Field;
+import tim.prune.data.FieldList;
+import tim.prune.data.Latitude;
+import tim.prune.data.Longitude;
+import tim.prune.data.Track;
+import tim.prune.data.TrackExtents;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.map.MapUtils;
+
+/**
+ * Helper for generating the arrays needed for the 3d terrain
+ */
+public class TerrainHelper
+{
+	/** Number of nodes on each side of the square grid */
+	private int _gridSize = 0;
+
+	/**
+	 * Constructor
+	 * @param inGridSize grid size
+	 */
+	public TerrainHelper(int inGridSize) {
+		_gridSize = inGridSize;
+	}
+
+	/**
+	 * @return grid size
+	 */
+	public int getGridSize() {
+		return _gridSize;
+	}
+
+
+	/**
+	 * Convert the terrain coordinates from raw form to TriangleStripArray form
+	 * (with repeated nodes)
+	 * @param inRawPoints array of raw points as formed from the track
+	 * @return point coordinates as array
+	 */
+	public Point3d[] getTerrainCoordinates(Point3d[] inRawPoints)
+	{
+		final int numNodes = _gridSize * _gridSize;
+		if (_gridSize <= 1 || inRawPoints == null || inRawPoints.length != numNodes) {return null;}
+		// Put these nodes into a new result array (repeating nodes as necessary)
+		final int resultSize = _gridSize * (_gridSize * 2 - 2);
+		Point3d[] result = new Point3d[resultSize];
+		final int numStrips = _gridSize - 1;
+		int resultIndex = 0;
+		for (int strip=0; strip<numStrips; strip++)
+		{
+			for (int col=0; col<_gridSize; col++)
+			{
+				int bottomNodeIndex = strip * _gridSize + col;
+				int topNodeIndex = bottomNodeIndex + _gridSize;
+				result[resultIndex++] = inRawPoints[bottomNodeIndex];
+				result[resultIndex++] = inRawPoints[topNodeIndex];
+			}
+		}
+		return result;
+	}
+
+
+	/**
+	 * Get the texture coordinates as an array
+	 * @return texture coordinates as array
+	 */
+	public TexCoord2f[] getTextureCoordinates()
+	{
+		if (_gridSize <= 1) {return null;}
+		final int numNodes = _gridSize * _gridSize;
+		final float gridStep = 1.0f / (_gridSize - 1);
+		// Build all the required nodes
+		TexCoord2f[] nodes = new TexCoord2f[numNodes];
+		for (int i=0; i<_gridSize; i++)
+		{
+			for (int j=0; j<_gridSize; j++)
+			{
+				nodes[j * _gridSize + i] = new TexCoord2f(gridStep * i, 1.0f - gridStep * j);
+			}
+		}
+		// Now put these nodes into a new result array (repeating nodes as necessary)
+		final int resultSize = _gridSize * (_gridSize * 2 - 2);
+		TexCoord2f[] result = new TexCoord2f[resultSize];
+		final int numStrips = _gridSize - 1;
+		int resultIndex = 0;
+		for (int strip=0; strip<numStrips; strip++)
+		{
+			for (int col=0; col<_gridSize; col++)
+			{
+				int bottomNodeIndex = strip * _gridSize + col;
+				int topNodeIndex = bottomNodeIndex + _gridSize;
+				result[resultIndex++] = nodes[bottomNodeIndex];
+				result[resultIndex++] = nodes[topNodeIndex];
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * @return strip lengths as array
+	 */
+	public int[] getStripLengths()
+	{
+		final int numStrips = _gridSize - 1;
+		final int nodesPerStrip = _gridSize * 2;
+		int[] result = new int[numStrips];
+		for (int i=0; i<numStrips; i++) {
+			result[i] = nodesPerStrip;
+		}
+		return result;
+	}
+
+	/**
+	 * Create a grid of points in a new Track
+	 * @param inDataTrack track from which the extents should be obtained
+	 * @return Track containing all the points in the grid
+	 */
+	public Track createGridTrack(Track inDataTrack)
+	{
+		// Work out the size of the current track
+		TrackExtents extents = new TrackExtents(inDataTrack);
+		extents.applySquareBorder();
+		DoubleRange xRange = extents.getXRange();
+		DoubleRange yRange = extents.getYRange();
+		// Create the array of points
+		final int numPoints = _gridSize * _gridSize;
+		final double xStep = xRange.getRange() / (_gridSize - 1);
+		final double yStep = yRange.getRange() / (_gridSize - 1);
+		DataPoint[] points = new DataPoint[numPoints];
+		for (int i=0; i<_gridSize; i++)
+		{
+			double pY = yRange.getMinimum() + i * yStep;
+			for (int j=0; j<_gridSize; j++)
+			{
+				// Create a new point with the appropriate lat and long, with no altitude
+				double pX = xRange.getMinimum() + j * xStep;
+				DataPoint point = new DataPoint(
+					new Latitude(MapUtils.getLatitudeFromY(pY), Coordinate.FORMAT_NONE),
+					new Longitude(MapUtils.getLongitudeFromX(pX), Coordinate.FORMAT_NONE),
+					null);
+				//System.out.println("Created point at " + point.getLatitude().output(Coordinate.FORMAT_DEG_MIN_SEC)
+				//	+ ", " + point.getLongitude().output(Coordinate.FORMAT_DEG_MIN_SEC));
+				points[i * _gridSize + j] = point;
+			}
+		}
+		// Put these into a new track
+		Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE};
+		Track grid = new Track(new FieldList(fields), points);
+		return grid;
+	}
+
+	/**
+	 * Write the given terrain track out to an indexed png file
+	 * @param inModel three-d data model with terrain
+	 * @param inPngFile file to write to
+	 */
+	public void writeHeightMap(ThreeDModel inModel, File inPngFile)
+	{
+		BufferedImage image = new BufferedImage(_gridSize, _gridSize, BufferedImage.TYPE_BYTE_INDEXED);
+		for (int y=0; y<_gridSize; y++)
+		{
+			for (int x=0; x<_gridSize; x++)
+			{
+				double heightValue = inModel.getScaledTerrainValue(y * _gridSize + x) * 256;
+				// Need to ask colour model what rgb to use for this index (a little round-the-houses)
+				image.setRGB(x, y, image.getColorModel().getRGB((int) heightValue));
+			}
+		}
+		try
+		{
+			ImageIO.write(image, "PNG", inPngFile);
+		}
+		catch (IOException ioe) {System.err.println(ioe.getClass().getName() + " - " + ioe.getMessage());}
+	}
+
+
+	/**
+	 * Try to fix the voids in the given terrain track by averaging neighbour values where possible
+	 * @param inTerrainTrack terrain track to fix
+	 */
+	public void fixVoids(Track inTerrainTrack)
+	{
+		int numVoids = countVoids(inTerrainTrack);
+		if (numVoids == 0) {return;}
+		//System.out.println("Starting to fix, num voids = " + numVoids);
+		// Fix the holes which are surrounded on all four sides by non-holes
+		fixSingleHoles(inTerrainTrack);
+		//System.out.println("Fixed single holes, now num voids = " + countVoids(inTerrainTrack));
+		// Maybe there is something to do in the corners?
+		fixCornersAndEdges(inTerrainTrack);
+		//System.out.println("Fixed corners, now num voids = " + countVoids(inTerrainTrack));
+		// Now fix the bigger holes, which should fix everything left
+		fixBiggerHoles(inTerrainTrack);
+		final int numHolesLeft = countVoids(inTerrainTrack);
+		if (numHolesLeft > 0) {
+			System.out.println("Fixed bigger holes, now num voids = " + countVoids(inTerrainTrack));
+		}
+	}
+
+	/**
+	 * @param inTerrainTrack terrain track
+	 * @return number of voids (points without altitudes)
+	 */
+	private static int countVoids(Track inTerrainTrack)
+	{
+		// DEBUG: Show state of voids first
+//		final int gridSize = (int) Math.sqrt(inTerrainTrack.getNumPoints());
+//		StringBuilder sb = new StringBuilder();
+//		for (int i=0; i<inTerrainTrack.getNumPoints(); i++)
+//		{
+//			if ((i%gridSize) == 0) sb.append('\n');
+//			if (inTerrainTrack.getPoint(i).hasAltitude()) {
+//				sb.append('A');
+//			} else {
+//				sb.append(' ');
+//			}
+//		}
+//		System.out.println("Voids:" + sb.toString());
+		// END DEBUG
+
+		int numVoids = 0;
+		if (inTerrainTrack != null)
+		{
+			for (int i=0; i<inTerrainTrack.getNumPoints(); i++) {
+				if (!inTerrainTrack.getPoint(i).hasAltitude()) {
+					numVoids++;
+				}
+			}
+		}
+		return numVoids;
+	}
+
+	/**
+	 * Just deal with single holes surrounded by at least four direct neighbours
+	 * @param inTerrainTrack terrain track to fix
+	 */
+	private void fixSingleHoles(Track inTerrainTrack)
+	{
+		// Holes with neighbours in all directions
+		final int startIndex = 1, endIndex = _gridSize - 2;
+		for (int x = startIndex; x <= endIndex; x++)
+		{
+			for (int y = startIndex; y <= endIndex; y++)
+			{
+				int pIndex = x * _gridSize + y;
+				// Get the point and its neighbours
+				final DataPoint p = inTerrainTrack.getPoint(pIndex);
+				if (!p.hasAltitude())
+				{
+					final DataPoint pl = inTerrainTrack.getPoint(pIndex - 1);
+					final DataPoint pr = inTerrainTrack.getPoint(pIndex + 1);
+					final DataPoint pu = inTerrainTrack.getPoint(pIndex + _gridSize);
+					final DataPoint pd = inTerrainTrack.getPoint(pIndex - _gridSize);
+					// Check if the points are null??
+					if (pl == null || pr == null || pu == null || pd == null)
+					{
+						System.err.println("Woah. Got a null point in fixSingleHoles. x=" + x + ", y=" + y + ", grid=" + _gridSize);
+						System.err.println("index=" + pIndex);
+						if (pl == null) System.err.println("pl is null");
+						if (pr == null) System.err.println("pr is null");
+						if (pu == null) System.err.println("pu is null");
+						if (pd == null) System.err.println("pd is null");
+						continue;
+					}
+					// Check that all the neighbours have altitudes
+					if (pl.hasAltitude() && pr.hasAltitude() && pu.hasAltitude() && pd.hasAltitude())
+					{
+						// Now check the double-neighbours
+						final DataPoint pll = inTerrainTrack.getPoint(pIndex - 2);
+						final DataPoint prr = inTerrainTrack.getPoint(pIndex + 2);
+						final DataPoint puu = inTerrainTrack.getPoint(pIndex + 2 * _gridSize);
+						final DataPoint pdd = inTerrainTrack.getPoint(pIndex - 2 * _gridSize);
+
+						double altitude = 0.0;
+						if (pll != null && pll.hasAltitude() && prr != null && prr.hasAltitude()
+							&& puu != null && puu.hasAltitude() && pdd != null && pdd.hasAltitude())
+						{
+							// Use the double-neighbours too to take into account the gradients
+							altitude = (
+								  pl.getAltitude().getMetricValue() * 1.5
+								- pll.getAltitude().getMetricValue() * 0.5
+								+ pr.getAltitude().getMetricValue() * 1.5
+								- prr.getAltitude().getMetricValue() * 0.5
+								+ pd.getAltitude().getMetricValue() * 1.5
+								- pdd.getAltitude().getMetricValue() * 0.5
+								+ pu.getAltitude().getMetricValue() * 1.5
+								- puu.getAltitude().getMetricValue() * 0.5) / 4.0;
+						}
+						else
+						{
+							// no double-neighbours, just use neighbours
+							altitude = (
+								  pl.getAltitude().getMetricValue()
+								+ pr.getAltitude().getMetricValue()
+								+ pd.getAltitude().getMetricValue()
+								+ pu.getAltitude().getMetricValue()) / 4.0;
+						}
+						// Set this altitude in the point
+						p.setFieldValue(Field.ALTITUDE, "" + altitude, false);
+						// force value to metres
+						p.getAltitude().reset(new Altitude((int) altitude, UnitSetLibrary.UNITS_METRES));
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Try to fix the corners and edges, if they're blank
+	 * @param inTerrainTrack terrain track
+	 */
+	private void fixCornersAndEdges(Track inTerrainTrack)
+	{
+		fixCorner(inTerrainTrack, 0, 1, 1);
+		fixCorner(inTerrainTrack, _gridSize-1, -1, 1);
+		fixCorner(inTerrainTrack, (_gridSize-1)*_gridSize, 1, -1);
+		fixCorner(inTerrainTrack, _gridSize*_gridSize-1, -1, -1);
+		fixEdge(inTerrainTrack, 0, 1);
+		fixEdge(inTerrainTrack, _gridSize-1, _gridSize);
+		fixEdge(inTerrainTrack, (_gridSize-1)*_gridSize, -_gridSize);
+		fixEdge(inTerrainTrack, _gridSize*_gridSize-1, -1);
+	}
+
+	/**
+	 * Fix a single corner by searching along adjacent edges and averaging the nearest neighbours
+	 * @param inTerrainTrack terrain track
+	 * @param inCornerIndex index of corner to fill
+	 * @param inXinc increment in x direction (+1 or -1)
+	 * @param inYinc increment in y direction (+1 or -1)
+	 */
+	private void fixCorner(Track inTerrainTrack, int inCornerIndex, int inXinc, int inYinc)
+	{
+		DataPoint corner = inTerrainTrack.getPoint(inCornerIndex);
+		if (corner == null || corner.hasAltitude()) {return;}
+		// Corner hasn't got an altitude, we'll have to look for it
+		int sIndex1 = inCornerIndex, sIndex2 = inCornerIndex;
+		Altitude alt1 = null, alt2 = null;
+
+		for (int i=1; i<_gridSize && !corner.hasAltitude(); i++)
+		{
+			sIndex1 += inXinc;
+			sIndex2 += (inYinc * _gridSize);
+			// System.out.println("To fill corner " + inCornerIndex + ", looking at indexes " + sIndex1 + " and " + sIndex2);
+			if (alt1 == null)
+			{
+				DataPoint source1 = inTerrainTrack.getPoint(sIndex1);
+				if (source1 != null && source1.hasAltitude()) {alt1 = source1.getAltitude();}
+			}
+			if (alt2 == null)
+			{
+				DataPoint source2 = inTerrainTrack.getPoint(sIndex2);
+				if (source2 != null && source2.hasAltitude()) {alt2 = source2.getAltitude();}
+			}
+			// Can we average these?
+			if (alt1 != null && alt2 != null)
+			{
+				// System.out.println("Averaging values " + alt1.getMetricValue() + " and " + alt2.getMetricValue());
+				int newAltitude = (int) ((alt1.getMetricValue() + alt2.getMetricValue()) / 2.0);
+				corner.setFieldValue(Field.ALTITUDE, "" + newAltitude, false);
+			}
+		}
+	}
+
+	/**
+	 * Fix any holes found in the specified edge
+	 * @param inTerrainTrack terrain track
+	 * @param inCornerIndex index of corner to start from
+	 * @param inInc increment along edge
+	 */
+	private void fixEdge(Track inTerrainTrack, int inCornerIndex, int inInc)
+	{
+		int prevIndexWithAlt = -1;
+		int sIndex = inCornerIndex;
+		if (inTerrainTrack.getPoint(sIndex).hasAltitude()) {prevIndexWithAlt = 0;}
+		for (int i=1; i<_gridSize; i++)
+		{
+			sIndex += inInc;
+			if (inTerrainTrack.getPoint(sIndex).hasAltitude())
+			{
+				if (prevIndexWithAlt >= 0 && prevIndexWithAlt < (i-1))
+				{
+					final int gapLen = i - prevIndexWithAlt;
+					final double alt1 = inTerrainTrack.getPoint(prevIndexWithAlt).getAltitude().getMetricValue();
+					final double alt2 = inTerrainTrack.getPoint(i).getAltitude().getMetricValue();
+					for (int j = 1; j < gapLen; j++)
+					{
+						// System.out.println("Fill in " + (prevIndexWithAlt + j) + " using " + prevIndexWithAlt + " and " + i);
+						final double alt = alt1 + (alt2-alt1) * j / gapLen;
+						final DataPoint p = inTerrainTrack.getPoint(inCornerIndex + (prevIndexWithAlt + j) * inInc);
+						p.setFieldValue(Field.ALTITUDE, "" + (int) alt, false);
+					}
+				}
+				prevIndexWithAlt = i;
+			}
+		}
+	}
+
+	/**
+	 * Try to fix bigger holes by interpolating between neighbours
+	 * @param inTerrainTrack terrain track
+	 */
+	private void fixBiggerHoles(Track inTerrainTrack)
+	{
+		double[] altitudes = new double[inTerrainTrack.getNumPoints()];
+		for (int i=0; i<_gridSize; i++)
+		{
+			int prevHoriz = -1, prevVert = -1;
+			for (int j=0; j<_gridSize; j++)
+			{
+				if (inTerrainTrack.getPoint(i * _gridSize + j).hasAltitude())
+				{
+					if (prevHoriz > -1 && prevHoriz != (j-1))
+					{
+//						System.out.println("Found a gap for y=" + i +" between x=" + prevHoriz + " and " + j + " (" + (j-prevHoriz-1) + ")");
+						double startVal = inTerrainTrack.getPoint(i * _gridSize + prevHoriz).getAltitude().getMetricValue();
+						double endVal   = inTerrainTrack.getPoint(i * _gridSize + j).getAltitude().getMetricValue();
+						for (int k=prevHoriz + 1; k< j; k++)
+						{
+							double val = startVal + (k-prevHoriz) * (endVal-startVal) / (j-prevHoriz);
+							if (altitudes[i * _gridSize + k] > 0.0) {
+								altitudes[i * _gridSize + k] = (altitudes[i * _gridSize + k] + val) / 2.0;
+							}
+							else {
+								altitudes[i * _gridSize + k] = val;
+							}
+						}
+					}
+					prevHoriz = j;
+				}
+				if (inTerrainTrack.getPoint(j * _gridSize + i).hasAltitude())
+				{
+					if (prevVert > -1 && prevVert != (j-1))
+					{
+//						System.out.println("Found a gap for x=" + i +" between y=" + prevVert + " and " + j + " (" + (j-prevVert-1) + ")");
+						double startVal = inTerrainTrack.getPoint(prevVert * _gridSize + i).getAltitude().getMetricValue();
+						double endVal   = inTerrainTrack.getPoint(j * _gridSize + i).getAltitude().getMetricValue();
+						for (int k=prevVert + 1; k< j; k++)
+						{
+							double val = startVal + (k-prevVert) * (endVal-startVal) / (j-prevVert);
+							if (altitudes[k * _gridSize + i] > 0.0) {
+								altitudes[k * _gridSize + i] = (altitudes[k * _gridSize + i] + val) / 2.0;
+							}
+							else {
+								altitudes[k * _gridSize + i] = val;
+							}
+						}
+					}
+					prevVert = j;
+				}
+			}
+		}
+		// Now the doubles have been set and/or averaged, we can set the values in the points
+		for (int i=0; i<inTerrainTrack.getNumPoints(); i++)
+		{
+			DataPoint p = inTerrainTrack.getPoint(i);
+			if (!p.hasAltitude() && altitudes[i] > 0.0)
+			{
+				p.setFieldValue(Field.ALTITUDE, "" + altitudes[i], false);
+				p.getAltitude().reset(new Altitude((int) altitudes[i], UnitSetLibrary.UNITS_METRES));
+			}
+		}
+	}
+}
diff --git a/tim/prune/threedee/ThreeDModel.java b/tim/prune/threedee/ThreeDModel.java
index ddf9162..abe166c 100644
--- a/tim/prune/threedee/ThreeDModel.java
+++ b/tim/prune/threedee/ThreeDModel.java
@@ -12,6 +12,7 @@ import tim.prune.data.Track;
 public class ThreeDModel
 {
 	private Track _track = null;
+	private Track _terrainTrack = null;
 	private PointScaler _scaler = null;
 	private double _scaleFactor = 1.0;
 	private double _altFactor = 1.0;
@@ -37,6 +38,14 @@ public class ThreeDModel
 
 
 	/**
+	 * @param inTrack terrain track to set
+	 */
+	public void setTerrain(Track inTrack)
+	{
+		_terrainTrack = inTrack;
+	}
+
+	/**
 	 * @return the number of points in the model
 	 */
 	public int getNumPoints()
@@ -50,9 +59,7 @@ public class ThreeDModel
 	 */
 	public void setAltitudeFactor(double inFactor)
 	{
-		if (inFactor >= 1.0) {
-			_altFactor = inFactor;
-		}
+		_altFactor = inFactor;
 	}
 
 	/**
@@ -70,6 +77,7 @@ public class ThreeDModel
 	{
 		// Use PointScaler to sort out x and y values
 		_scaler = new PointScaler(_track);
+		_scaler.addTerrain(_terrainTrack);
 		_scaler.scale(); // Add 10% border
 
 		// cap altitude scale factor if it's too big
@@ -77,7 +85,7 @@ public class ThreeDModel
 		if (maxAlt > 0.5)
 		{
 			// capped
-			//System.out.println("Capped alt factor from " + _altFactor + " to " + (_altFactor * 0.5 / maxAlt));
+			// System.out.println("Capped alt factor from " + _altFactor + " to " + (_altFactor * 0.5 / maxAlt));
 			_altFactor = _altFactor * 0.5 / maxAlt;
 		}
 
@@ -134,13 +142,48 @@ public class ThreeDModel
 	{
 		// if no altitude, just return 0
 		double altVal = _scaler.getAltValue(inIndex);
-		if (altVal < 0) return 0;
+		if (altVal <= 0.0) return 0.0;
 		// scale according to exaggeration factor
 		return altVal * _altFactor * _externalScaleFactor;
 	}
 
 
 	/**
+	 * Get the scaled horizontal value for the specified terrain point
+	 * @param inIndex index of point
+	 * @return scaled horizontal value
+	 */
+	public double getScaledTerrainHorizValue(int inIndex)
+	{
+		return _scaler.getTerrainHorizValue(inIndex) * _scaleFactor * _externalScaleFactor;
+	}
+
+	/**
+	 * Get the scaled vertical value for the specified terrain point
+	 * @param inIndex index of point
+	 * @return scaled vertical value
+	 */
+	public double getScaledTerrainVertValue(int inIndex)
+	{
+		return _scaler.getTerrainVertValue(inIndex) * _scaleFactor * _externalScaleFactor;
+	}
+
+	/**
+	 * Get the scaled altitude value for the specified terrain point
+	 * @param inIndex index of point
+	 * @return scaled altitude value
+	 */
+	public double getScaledTerrainValue(int inIndex)
+	{
+		// if no altitude, just return 0
+		double altVal = _scaler.getTerrainAltValue(inIndex);
+		if (altVal <= 0.0) return 0.0;
+		// don't scale by scale factor, needs to be unscaled
+		return altVal * _altFactor;
+	}
+
+
+	/**
 	 * @param inIndex index of point, starting at 0
 	 * @return point type, either POINT_TYPE_WAYPOINT or POINT_TYPE_NORMAL_POINT
 	 */
diff --git a/tim/prune/threedee/ThreeDWindow.java b/tim/prune/threedee/ThreeDWindow.java
index df72bc7..f4b3ed3 100644
--- a/tim/prune/threedee/ThreeDWindow.java
+++ b/tim/prune/threedee/ThreeDWindow.java
@@ -1,5 +1,6 @@
 package tim.prune.threedee;
 
+import tim.prune.DataStatus;
 import tim.prune.data.Track;
 
 /**
@@ -14,6 +15,25 @@ public interface ThreeDWindow
 	 */
 	public void setTrack(Track inTrack);
 
+	/**
+	 * @param inFactor altitude factor to use
+	 */
+	public void setAltitudeFactor(double inFactor);
+
+	/**
+	 * @param inDefinition image definition (image or not, source, zoom)
+	 */
+	public void setBaseImageParameters(ImageDefinition inDefinition);
+
+	/**
+	 * @param inDefinition terrain definition (terrain or not, resolution)
+	 */
+	public void setTerrainParameters(TerrainDefinition inDefinition);
+
+	/**
+	 * @param inStatus current data status for caching
+	 */
+	public void setDataStatus(DataStatus inStatus);
 
 	/**
 	 * Show the window
diff --git a/tim/prune/threedee/WindowFactory.java b/tim/prune/threedee/WindowFactory.java
index 549c11a..42ff8a4 100644
--- a/tim/prune/threedee/WindowFactory.java
+++ b/tim/prune/threedee/WindowFactory.java
@@ -45,9 +45,13 @@ public abstract class WindowFactory
 		{
 			// no java3d classes available
 		}
+		catch (NoClassDefFoundError nfe)
+		{
+			// no java3d classes available
+		}
 		catch (UnsatisfiedLinkError ule)
 		{
-			// java3d available but somehow incompatible?
+			// java3d classes found but no native components
 		}
 		return has3d;
 	}
diff --git a/tim/prune/tips/TipDefinition.java b/tim/prune/tips/TipDefinition.java
new file mode 100644
index 0000000..c58fe5c
--- /dev/null
+++ b/tim/prune/tips/TipDefinition.java
@@ -0,0 +1,66 @@
+package tim.prune.tips;
+
+/**
+ * Definition of a tip, including key and whether the tip
+ * has already been shown or not.
+ * This class is only visible within this package
+ */
+class TipDefinition
+{
+	/** Key of message to show when fired */
+	private String _messageKey = null;
+	/** Threshold of calls before tip is shown */
+	private int _threshold = 0;
+	/** Number of times this tip has been hit */
+	private int _hitCount = 0;
+	/** Flag whether tip is active or has already been shown */
+	private boolean _active = true;
+
+	/**
+	 * Constructor
+	 * @param inKey key for message to show
+	 */
+	TipDefinition(String inKey)
+	{
+		this(inKey, 0);
+	}
+
+	/**
+	 * Constructor
+	 * @param inKey message key
+	 * @param inThreshold threshold
+	 */
+	TipDefinition(String inKey, int inThreshold)
+	{
+		_messageKey = inKey;
+		_threshold  = inThreshold;
+	}
+
+	/**
+	 * Hit this definition and check the threshold
+	 * @return true if the message should be shown
+	 */
+	boolean shouldShowMessage()
+	{
+		if (_active)
+		{
+			boolean overThreshold = (_hitCount >= _threshold);
+			if (!overThreshold) {
+				_hitCount++;
+			}
+			else {
+				_active = false; // only fire once
+			}
+			return overThreshold;
+		}
+		// not active
+		return false;
+	}
+
+	/**
+	 * @return message key
+	 */
+	String getMessageKey() {
+		return _messageKey;
+	}
+}
diff --git a/tim/prune/tips/TipManager.java b/tim/prune/tips/TipManager.java
new file mode 100644
index 0000000..24c12bc
--- /dev/null
+++ b/tim/prune/tips/TipManager.java
@@ -0,0 +1,45 @@
+package tim.prune.tips;
+
+/**
+ * Class to manage the showing of tips according
+ * to the fixed TipDefinitions
+ */
+public abstract class TipManager
+{
+	public static final int Tip_UseAMapCache    = 0;
+	public static final int Tip_LearnTimeParams = 1;
+	public static final int Tip_DownloadSrtm    = 2;
+	public static final int Tip_UseSrtmFor3d    = 3;
+	public static final int Tip_ManuallyCorrelateOne = 4;
+	private static final int Number_Tips = Tip_ManuallyCorrelateOne + 1;
+
+	/** Array of tip definitions */
+	private static TipDefinition[] TIPDEFS = new TipDefinition[Number_Tips];
+
+	/** Static block to initialise tip definitions */
+	static
+	{
+		TIPDEFS[Tip_UseAMapCache] = new TipDefinition("tip.useamapcache", 150);
+		TIPDEFS[Tip_LearnTimeParams] = new TipDefinition("tip.learntimeparams");
+		TIPDEFS[Tip_DownloadSrtm] = new TipDefinition("tip.downloadsrtm", 5);
+		TIPDEFS[Tip_UseSrtmFor3d] = new TipDefinition("tip.usesrtmfor3d");
+		TIPDEFS[Tip_ManuallyCorrelateOne] = new TipDefinition("tip.manuallycorrelateone");
+	}
+
+	/**
+	 * Fire a trigger for the specified tip and get the message key if tip should be shown
+	 * @param inTipNumber number of tip from constants
+	 * @return message key if a message should be shown, or null otherwise
+	 */
+	public static String fireTipTrigger(int inTipNumber)
+	{
+		try {
+			TipDefinition tip = TIPDEFS[inTipNumber];
+			if (tip.shouldShowMessage()) {
+				return tip.getMessageKey();
+			}
+		}
+		catch (ArrayIndexOutOfBoundsException obe) {} // unrecognised tip given
+		return null;
+	}
+}
diff --git a/tim/prune/undo/UndoAddAltitudeOffset.java b/tim/prune/undo/UndoAddAltitudeOffset.java
index 9a9f7a4..ffaabdd 100644
--- a/tim/prune/undo/UndoAddAltitudeOffset.java
+++ b/tim/prune/undo/UndoAddAltitudeOffset.java
@@ -57,8 +57,7 @@ public class UndoAddAltitudeOffset implements UndoOperation
 		for (int i=0; i<numPoints; i++)
 		{
 			DataPoint point = inTrackInfo.getTrack().getPoint(i+_startIndex);
-			point.getAltitude().reset(_altitudes[i]);
-			point.setModified(true);
+			point.resetAltitude(_altitudes[i]);
 		}
 		_altitudes = null;
 		inTrackInfo.getSelection().markInvalid();
diff --git a/tim/prune/undo/UndoCutAndMove.java b/tim/prune/undo/UndoCutAndMove.java
index 4287bab..2a6f9ca 100644
--- a/tim/prune/undo/UndoCutAndMove.java
+++ b/tim/prune/undo/UndoCutAndMove.java
@@ -89,6 +89,7 @@ public class UndoCutAndMove implements UndoOperation
 		if (_moveTrackPoint != null) {
 			_moveTrackPoint.setSegmentStart(_moveToSegmentFlag);
 		}
+		inTrackInfo.getSelection().clearAll();
 		UpdateMessageBroker.informSubscribers();
 	}
 }
diff --git a/tim/prune/undo/UndoDeleteAudio.java b/tim/prune/undo/UndoDeleteAudio.java
index 0a49732..2b69865 100644
--- a/tim/prune/undo/UndoDeleteAudio.java
+++ b/tim/prune/undo/UndoDeleteAudio.java
@@ -9,7 +9,7 @@ import tim.prune.data.TrackInfo;
 /**
  * Operation to undo a delete of a single audio item, either with or without point
  */
-public class UndoDeleteAudio implements UndoOperation
+public class UndoDeleteAudio extends UndoDeleteOperation
 {
 	private int _audioIndex = -1;
 	private AudioClip _audio = null;
@@ -55,6 +55,8 @@ public class UndoDeleteAudio implements UndoOperation
 			if (!inTrackInfo.getTrack().insertPoint(_point, _pointIndex)) {
 				throw new UndoException(getDescription());
 			}
+			// Change the current point/range selection if required
+			modifySelection(inTrackInfo, _pointIndex, _pointIndex);
 		}
 		else
 		{
diff --git a/tim/prune/undo/UndoDeleteOperation.java b/tim/prune/undo/UndoDeleteOperation.java
new file mode 100644
index 0000000..d99a9f6
--- /dev/null
+++ b/tim/prune/undo/UndoDeleteOperation.java
@@ -0,0 +1,38 @@
+package tim.prune.undo;
+
+import tim.prune.data.TrackInfo;
+
+/**
+ * Abstract class to hold the selection handling required by all
+ * Undo operations which have undeleted something
+ */
+public abstract class UndoDeleteOperation implements UndoOperation
+{
+	/**
+	 * Modify the current point/range selection after the delete operation is undone
+	 * @param inTrackInfo track info object
+	 * @param inStartIndex start index of reinserted range
+	 * @param inEndIndex end index of reinserted range
+	 */
+	protected static void modifySelection(TrackInfo inTrackInfo, int inStartIndex, int inEndIndex)
+	{
+		final int numPointsInserted = inEndIndex - inStartIndex + 1;
+		// See if there is a currently selected point, if so does it need to be modified
+		final int currentPoint = inTrackInfo.getSelection().getCurrentPointIndex();
+		if (currentPoint >= inStartIndex)
+		{
+			inTrackInfo.selectPoint(currentPoint + numPointsInserted);
+		}
+		// Same for currently selected range
+		int rangeStart = inTrackInfo.getSelection().getStart();
+		int rangeEnd   = inTrackInfo.getSelection().getEnd();
+		if (rangeEnd >= inStartIndex && rangeEnd > rangeStart)
+		{
+			rangeEnd += numPointsInserted;
+			if (rangeStart >= inStartIndex) {
+				rangeStart += numPointsInserted;
+			}
+			inTrackInfo.getSelection().selectRange(rangeStart, rangeEnd);
+		}
+	}
+}
diff --git a/tim/prune/undo/UndoDeletePhoto.java b/tim/prune/undo/UndoDeletePhoto.java
index 5edd37b..45be6ad 100644
--- a/tim/prune/undo/UndoDeletePhoto.java
+++ b/tim/prune/undo/UndoDeletePhoto.java
@@ -9,7 +9,7 @@ import tim.prune.data.TrackInfo;
 /**
  * Operation to undo a delete of a single photo, either with or without point
  */
-public class UndoDeletePhoto implements UndoOperation
+public class UndoDeletePhoto extends UndoDeleteOperation
 {
 	private int _photoIndex = -1;
 	private Photo _photo = null;
@@ -58,6 +58,8 @@ public class UndoDeletePhoto implements UndoOperation
 			{
 				throw new UndoException(getDescription());
 			}
+			// Change the current point/range selection if required
+			modifySelection(inTrackInfo, _pointIndex, _pointIndex);
 		}
 		else
 		{
diff --git a/tim/prune/undo/UndoDeletePoint.java b/tim/prune/undo/UndoDeletePoint.java
index 9919d0d..beddfbf 100644
--- a/tim/prune/undo/UndoDeletePoint.java
+++ b/tim/prune/undo/UndoDeletePoint.java
@@ -7,7 +7,7 @@ import tim.prune.data.TrackInfo;
 /**
  * Operation to undo a delete of a single point
  */
-public class UndoDeletePoint implements UndoOperation
+public class UndoDeletePoint extends UndoDeleteOperation
 {
 	private int _pointIndex = -1;
 	private DataPoint _point = null;
@@ -89,5 +89,7 @@ public class UndoDeletePoint implements UndoOperation
 				nextTrackPoint.setSegmentStart(false);
 			}
 		}
+		// If there's a current point or range selected, maybe need to adjust start and/or end
+		modifySelection(inTrackInfo, _pointIndex, _pointIndex);
 	}
 }
diff --git a/tim/prune/undo/UndoDeleteRange.java b/tim/prune/undo/UndoDeleteRange.java
index 58f09e9..da42bc4 100644
--- a/tim/prune/undo/UndoDeleteRange.java
+++ b/tim/prune/undo/UndoDeleteRange.java
@@ -9,7 +9,7 @@ import tim.prune.data.TrackInfo;
 /**
  * Operation to undo a delete of a range of points
  */
-public class UndoDeleteRange implements UndoOperation
+public class UndoDeleteRange extends UndoDeleteOperation
 {
 	/**
 	 * Inner class to hold a single range information set
@@ -40,6 +40,14 @@ public class UndoDeleteRange implements UndoOperation
 		{
 			return _startIndex >= 0 && _points != null && _points.length > 0;
 		}
+
+		/**
+		 * @return end index of range
+		 */
+		public int getEndIndex()
+		{
+			return _startIndex + _points.length - 1;
+		}
 	}
 
 
@@ -141,6 +149,13 @@ public class UndoDeleteRange implements UndoOperation
 		// Undo both the ranges
 		performUndo(inTrackInfo, _rangeInfo1);
 		performUndo(inTrackInfo, _rangeInfo2);
+		// If there's a current point/range selected, maybe need to adjust start and/or end
+		if (_rangeInfo1 != null && _rangeInfo1.isValid()) {
+			modifySelection(inTrackInfo, _rangeInfo1._startIndex, _rangeInfo1.getEndIndex());
+		}
+		if (_rangeInfo2 != null && _rangeInfo2.isValid()) {
+			modifySelection(inTrackInfo, _rangeInfo2._startIndex, _rangeInfo2.getEndIndex());
+		}
 	}
 
 	/**
diff --git a/tim/prune/undo/UndoReorder.java b/tim/prune/undo/UndoReorder.java
index e1d5539..38c460e 100644
--- a/tim/prune/undo/UndoReorder.java
+++ b/tim/prune/undo/UndoReorder.java
@@ -42,5 +42,6 @@ public abstract class UndoReorder implements UndoOperation
 	{
 		// restore track to previous values
 		inTrackInfo.getTrack().replaceContents(_contents);
+		inTrackInfo.getSelection().clearAll();
 	}
-}
\ No newline at end of file
+}
diff --git a/tim/prune/undo/UndoSewSegments.java b/tim/prune/undo/UndoSewSegments.java
new file mode 100644
index 0000000..fbcf853
--- /dev/null
+++ b/tim/prune/undo/UndoSewSegments.java
@@ -0,0 +1,44 @@
+package tim.prune.undo;
+
+import tim.prune.data.DataPoint;
+import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Operation to undo the sewing together of track segments
+ */
+public class UndoSewSegments extends UndoReorder
+{
+	/** All segment start flags need to be remembered as well */
+	private boolean[] _segmentStartFlags = null;
+
+	/**
+	 * Constructor
+	 * @param inTrack track contents to copy
+	 */
+	public UndoSewSegments(Track inTrack)
+	{
+		super(inTrack, "undo.sewsegments");
+		// Also remember segment start flags, as they may have been changed by reversals
+		final int numPoints = inTrack.getNumPoints();
+		_segmentStartFlags = new boolean[numPoints];
+		for (int i=0; i<numPoints; i++) {
+			_segmentStartFlags[i] = inTrack.getPoint(i).getSegmentStart();
+		}
+	}
+
+	/** Perform the undo */
+	public void performUndo(TrackInfo inTrackInfo) throws UndoException
+	{
+		// Put all the points back in the right order
+		super.performUndo(inTrackInfo);
+		// And then restore the segment flags
+		for (int i=0; i<_segmentStartFlags.length; i++)
+		{
+			DataPoint point = inTrackInfo.getTrack().getPoint(i);
+			if (point != null && !point.isWaypoint()) {
+				point.setSegmentStart(_segmentStartFlags[i]);
+			}
+		}
+	}
+}
diff --git a/tim/prune/undo/UndoSplitSegments.java b/tim/prune/undo/UndoSplitSegments.java
new file mode 100644
index 0000000..85968b7
--- /dev/null
+++ b/tim/prune/undo/UndoSplitSegments.java
@@ -0,0 +1,23 @@
+package tim.prune.undo;
+
+import tim.prune.I18nManager;
+import tim.prune.data.Track;
+
+/**
+ * Undo splitting of track segments
+ */
+public class UndoSplitSegments extends UndoMergeTrackSegments
+{
+	/** Constructor */
+	public UndoSplitSegments(Track inTrack) {
+		super(inTrack, 0, inTrack.getNumPoints()-1);
+	}
+
+	/**
+	 * @return description of operation
+	 */
+	public String getDescription()
+	{
+		return I18nManager.getText("undo.splitsegments");
+	}
+}
diff --git a/tim/prune/undo/UndoStack.java b/tim/prune/undo/UndoStack.java
new file mode 100644
index 0000000..bca92b1
--- /dev/null
+++ b/tim/prune/undo/UndoStack.java
@@ -0,0 +1,24 @@
+package tim.prune.undo;
+
+import java.util.Stack;
+
+/**
+ * Stack of undo operations
+ * which also remembers how many times it's been cleared
+ */
+public class UndoStack extends Stack<UndoOperation>
+{
+	private int _numTimesDeleted = 0;
+
+	/** @return number of times this stack has been deleted */
+	public int getNumTimesDeleted() {
+		return _numTimesDeleted;
+	}
+
+	@Override
+	public void clear()
+	{
+		_numTimesDeleted++;
+		super.clear();
+	}
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/gpsprune.git



More information about the Pkg-grass-devel mailing list