]> gitweb.fperrin.net Git - GpsPrune.git/commitdiff
Version 16, February 2014
authoractivityworkshop <mail@activityworkshop.net>
Sun, 15 Feb 2015 16:05:07 +0000 (17:05 +0100)
committeractivityworkshop <mail@activityworkshop.net>
Sun, 15 Feb 2015 16:05:07 +0000 (17:05 +0100)
144 files changed:
tim/prune/App.java
tim/prune/ExternalTools.java
tim/prune/FunctionLibrary.java
tim/prune/GpsPrune.java
tim/prune/I18nManager.java
tim/prune/UpdateMessageBroker.java
tim/prune/config/Config.java
tim/prune/copyright.txt
tim/prune/correlate/Correlator.java
tim/prune/data/DataPoint.java
tim/prune/data/DoubleRange.java
tim/prune/data/PointScaler.java
tim/prune/data/RangeStats.java
tim/prune/data/Selection.java
tim/prune/data/Track.java
tim/prune/function/AboutScreen.java
tim/prune/function/AddMapSourceDialog.java
tim/prune/function/DeleteFieldValues.java
tim/prune/function/Export3dFunction.java
tim/prune/function/FieldListModel.java
tim/prune/function/FindWaypoint.java
tim/prune/function/FullRangeDetails.java
tim/prune/function/GetWikipediaXmlHandler.java
tim/prune/function/MapSourceListModel.java
tim/prune/function/PasteCoordinates.java
tim/prune/function/SelectTracksFunction.java
tim/prune/function/SetLanguage.java
tim/prune/function/SetMapBgFunction.java
tim/prune/function/ShowThreeDFunction.java
tim/prune/function/browser/BrowserLauncher.java
tim/prune/function/cache/ManageCacheFunction.java
tim/prune/function/compress/CompressTrackFunction.java
tim/prune/function/compress/MarkPointsInRectangleFunction.java
tim/prune/function/estimate/EstimateTime.java
tim/prune/function/estimate/EstimationParameters.java
tim/prune/function/estimate/LearnParameters.java
tim/prune/function/sew/CandidateSorter.java [new file with mode: 0644]
tim/prune/function/sew/SegmentEnd.java [new file with mode: 0644]
tim/prune/function/sew/SewTrackSegmentsFunction.java [new file with mode: 0644]
tim/prune/function/sew/SplitPoint.java [new file with mode: 0644]
tim/prune/function/sew/SplitSegmentsFunction.java [new file with mode: 0644]
tim/prune/function/srtm/DownloadSrtmFunction.java [new file with mode: 0644]
tim/prune/function/srtm/LookupSrtmFunction.java
tim/prune/function/srtm/SrtmTile.java
tim/prune/function/weather/GetWeatherForecastFunction.java [new file with mode: 0644]
tim/prune/function/weather/IconRenderer.java [new file with mode: 0644]
tim/prune/function/weather/OWMCurrentHandler.java [new file with mode: 0644]
tim/prune/function/weather/OWMForecastHandler.java [new file with mode: 0644]
tim/prune/function/weather/ResultSet.java [new file with mode: 0644]
tim/prune/function/weather/SingleForecast.java [new file with mode: 0644]
tim/prune/function/weather/WeatherResults.java [new file with mode: 0644]
tim/prune/function/weather/WeatherTableModel.java [new file with mode: 0644]
tim/prune/gui/BaseImageDefinitionPanel.java [new file with mode: 0644]
tim/prune/gui/DecimalNumberField.java
tim/prune/gui/DetailsDisplay.java
tim/prune/gui/GenericProgressDialog.java [new file with mode: 0644]
tim/prune/gui/MediaListModel.java
tim/prune/gui/MenuManager.java
tim/prune/gui/ProgressDialog.java
tim/prune/gui/SelectorDisplay.java
tim/prune/gui/TerrainDefinitionPanel.java [new file with mode: 0644]
tim/prune/gui/UndoManager.java
tim/prune/gui/WaypointListModel.java
tim/prune/gui/WaypointNameMatcher.java
tim/prune/gui/images/add_photo_icon.png [changed mode: 0755->0644]
tim/prune/gui/images/add_textfile_icon.png [changed mode: 0755->0644]
tim/prune/gui/images/weather-clear-day.png [new file with mode: 0644]
tim/prune/gui/images/weather-clear-night.png [new file with mode: 0644]
tim/prune/gui/images/weather-clouds-day.png [new file with mode: 0644]
tim/prune/gui/images/weather-clouds-night.png [new file with mode: 0644]
tim/prune/gui/images/weather-clouds.png [new file with mode: 0644]
tim/prune/gui/images/weather-extreme.png [new file with mode: 0644]
tim/prune/gui/images/weather-fog.png [new file with mode: 0644]
tim/prune/gui/images/weather-hail.png [new file with mode: 0644]
tim/prune/gui/images/weather-lightrain.png [new file with mode: 0644]
tim/prune/gui/images/weather-rain.png [new file with mode: 0644]
tim/prune/gui/images/weather-snow.png [new file with mode: 0644]
tim/prune/gui/images/weather-storm.png [new file with mode: 0644]
tim/prune/gui/map/DiskTileCacher.java
tim/prune/gui/map/MapCanvas.java
tim/prune/gui/map/MapTileManager.java
tim/prune/gui/map/TileConsumer.java [new file with mode: 0644]
tim/prune/gui/map/TileDownloader.java
tim/prune/lang/prune-texts_af.properties
tim/prune/lang/prune-texts_cz.properties
tim/prune/lang/prune-texts_de.properties
tim/prune/lang/prune-texts_de_CH.properties
tim/prune/lang/prune-texts_en.properties
tim/prune/lang/prune-texts_es.properties
tim/prune/lang/prune-texts_fr.properties
tim/prune/lang/prune-texts_hu.properties
tim/prune/lang/prune-texts_it.properties
tim/prune/lang/prune-texts_ja.properties
tim/prune/lang/prune-texts_ko.properties
tim/prune/lang/prune-texts_nl.properties
tim/prune/lang/prune-texts_pl.properties
tim/prune/lang/prune-texts_pt.properties
tim/prune/lang/prune-texts_ro.properties
tim/prune/lang/prune-texts_ru.properties
tim/prune/lang/prune-texts_sv.properties [new file with mode: 0644]
tim/prune/lang/prune-texts_tr.properties
tim/prune/lang/prune-texts_uk.properties [new file with mode: 0644]
tim/prune/lang/prune-texts_zh.properties
tim/prune/load/BabelFileFormats.java
tim/prune/load/BabelLoadFromFile.java
tim/prune/load/FileCacher.java
tim/prune/load/MediaLoadProgressDialog.java
tim/prune/load/TextFileLoader.java
tim/prune/load/babel/DiscardFilter.java
tim/prune/load/babel/DistanceFilter.java
tim/prune/load/babel/InterpolateFilter.java
tim/prune/load/babel/SimplifyFilter.java
tim/prune/load/xml/GzipFileLoader.java
tim/prune/load/xml/XmlFileLoader.java
tim/prune/load/xml/ZipFileLoader.java
tim/prune/readme.txt
tim/prune/save/BaseImageConfigDialog.java
tim/prune/save/BaseImageConsumer.java [new file with mode: 0644]
tim/prune/save/ExifSaver.java
tim/prune/save/GpxExporter.java
tim/prune/save/GroutedImage.java
tim/prune/save/ImageExporter.java
tim/prune/save/MapGrouter.java
tim/prune/save/PovExporter.java
tim/prune/save/SvgExporter.java
tim/prune/threedee/ImageDefinition.java [new file with mode: 0644]
tim/prune/threedee/Java3DWindow.java
tim/prune/threedee/TerrainDefinition.java [new file with mode: 0644]
tim/prune/threedee/TerrainHelper.java [new file with mode: 0644]
tim/prune/threedee/ThreeDModel.java
tim/prune/threedee/ThreeDWindow.java
tim/prune/threedee/WindowFactory.java
tim/prune/tips/TipDefinition.java [new file with mode: 0644]
tim/prune/tips/TipManager.java [new file with mode: 0644]
tim/prune/undo/UndoAddAltitudeOffset.java
tim/prune/undo/UndoCutAndMove.java
tim/prune/undo/UndoDeleteAudio.java
tim/prune/undo/UndoDeleteOperation.java [new file with mode: 0644]
tim/prune/undo/UndoDeletePhoto.java
tim/prune/undo/UndoDeletePoint.java
tim/prune/undo/UndoDeleteRange.java
tim/prune/undo/UndoReorder.java
tim/prune/undo/UndoSewSegments.java [new file with mode: 0644]
tim/prune/undo/UndoSplitSegments.java [new file with mode: 0644]

index 686b3dee485d4b3ffcd58203f2a3e5445be9bb17..71bd354172ee3df66957c75b6faf1a8c08369db2 100644 (file)
@@ -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.*;
 
 
@@ -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
index 31c03026813a046293db5cfa870b614f52ec6692..8cd1aef178f44c80674af5923a7a4937ec9e4466 100644 (file)
@@ -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;
index 53d8563cc33587d4303d945ff2d51fd44cca57fa..9c51c8df9509276c53afb7dcee17f72a624b175c 100644 (file)
@@ -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);
index 5cc5c9043099ef3fe960ee425b5a5a7b281ba7f2..0503ddaaf12d9365230e55853f29c0e285dd16f7 100644 (file)
@@ -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.2";
+       public static final String VERSION_NUMBER = "16";
        /** Build number, just used for about screen */
-       public static final String BUILD_NUMBER = "283b";
+       public static final String BUILD_NUMBER = "301";
        /** Static reference to App object */
        private static App APP = null;
 
index 349db3fcaff83bace412ebdf02e459b28d1e9199..24a4fde41ab2a73288dcdb7975c9c7791f54cf47 100644 (file)
@@ -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;
+       }
 }
index 337fc27967712542addcde896b07568558058d03..c03235905318b8ed4d4307f887365dae0e3b6b91 100644 (file)
@@ -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)
index 385a5d9f12dc64d387711cf54abc4fbbb5644633..655847cf338cf9b4900cb426c850f1a8248d8681 100644 (file)
@@ -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;
 
 
 /**
@@ -80,6 +81,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 +145,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();
@@ -167,9 +172,30 @@ public abstract class Config
                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
index 1add3e76a84a8ca94b2e2829fa327b9e97876432..7990765fb248319174e9351574d6e059d1d06567 100644 (file)
@@ -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
index 85b96e92304b0a5f1da3ea470272d8d165582972..7e887ffc814868bda09ed32657ef11ea32abc547 100644 (file)
@@ -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);
index 4de97ef219bb60f05153dc4f3a4cbbdf72a2e672..c2446228161401202b4d3cbfba107a56f3cfde70 100644 (file)
@@ -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
index 4e361224f420ed32b72473edc1e5adf87d37a3f0..3f252eed6cd2bcfe00bcdc272c2f1f82404dcbba 100644 (file)
@@ -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
index a1338857e0c509acc6a3d6227f4044080e3f2914..446c0a204758b0b9c940d6462b8f686aad6030ee 100644 (file)
@@ -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;
+       }
 }
index 0ed8fc7a4588f69e712525ad19cf232b532e1cb2..37d061bf66cb3318c68a41829f1f286c2a855cd5 100644 (file)
@@ -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) */
index 6125eacad76e7b0390daef85fa11e14bd65573fb..68654e8ed35d204dc41cf3977e23ba0b67f5a15f 100644 (file)
@@ -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;
        }
@@ -151,15 +150,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
         */
@@ -169,14 +159,6 @@ public class Selection
                return _movingSeconds;
        }
 
-       /**
-        * @return distance of Selection in specified units
-        */
-       public double getDistance()
-       {
-               return Distance.convertRadiansToDistance(_angDistance);
-       }
-
        /**
         * @return moving distance of Selection in current units
         */
index 8623c587ba5c9131d9051ad083cdcafffd24e54c..45810e2ef9e4b7ba1a036f423962cecc8a87d5a5 100644 (file)
@@ -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
index 7a95693b734dcf0ae67bdd19c27b202ede513c6f..70a9eb35ca6109e1af3abdcd0ae10b798f817e79 100644 (file)
@@ -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);
                }
index 52a8874cf100b355b8b0d6acac60215dcfe29202..bcc8b7064bed60cb5f81b5672e560f8600f5c1a8 100644 (file)
@@ -43,11 +43,11 @@ public class AddMapSourceDialog
        private JTextField _oNameField = null;
        private JTextField _baseUrlField = null, _topUrlField = null;
        private JRadioButton[] _baseTypeRadios = null, _topTypeRadios = null;
-       private JComboBox _oZoomCombo = null;
+       private JComboBox<Integer> _oZoomCombo = null;
        // controls for cloudmade panel
        private JTextField _cNameField = null;
        private JTextField _cStyleField = null;
-       private JComboBox _cZoomCombo = null;
+       private JComboBox<Integer> _cZoomCombo = null;
        private JButton _okButton = null;
 
        /** array of file types */
@@ -181,9 +181,9 @@ 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);
@@ -208,9 +208,9 @@ public class AddMapSourceDialog
                _cStyleField.addKeyListener(keyListener);
                cloudGridPanel.add(_cStyleField);
                cloudGridPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.maxzoom")));
-               _cZoomCombo = new JComboBox();
+               _cZoomCombo = new JComboBox<Integer>();
                for (int i=10; i<=20; i++) {
-                       _cZoomCombo.addItem("" + i);
+                       _cZoomCombo.addItem(i);
                }
                cloudGridPanel.add(_cZoomCombo);
                cloudPanel.add(cloudGridPanel, BorderLayout.NORTH);
index b6c97f5594a2c7f00703467adebab407f75c3524..0c0e44f71257532b430acfaa29b90d076bff4e4f 100644 (file)
@@ -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() {
index 9e92e62158573d624b5f24c409c8ccdb56954544..d32828ab997bb34fe801a515fd86da4744ce2893 100644 (file)
@@ -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;
+       }
 }
index 89a721ce451098f18723583a98ecb2cb6861ec46..b4589d897569e2b4e0b11ffefbe12ced14f1f05d 100644 (file)
@@ -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();
index 8b6beeabf2bedf84258e82aaf912278b3bb22322..ac0bd37290005c4d281f49b1525c218599072d75 100644 (file)
@@ -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)
                        {
index d9dbb7011253fdb4f1ac54008739259a8eb3eab5..07ef39e36a1151d06af5b4f2238857c278bc7d44 100644 (file)
@@ -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);
index d9a3b747bf14d8e39ab1c92952e333039f22bd6d..f70e5f710da6f8cb5e7304af7c3d8edfe3f1f571 100644 (file)
@@ -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);
        }
index 4548cc4eec351c2bc77037742284aa69ec1fe96d..489f95d06610c52b3930e64fdc3b954c39b75595 100644 (file)
@@ -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();
index 0228014deb4810f025ba3d340247fe4bc920004b..c9b2ad1c7ff3f74da6e8162fbb84d8fa886c1e42 100644 (file)
@@ -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"));
index f2fe23b1c5488f69a89a1c51f90d2fa712500277..fa06daebfeb0c095f95e68aed08d60628871fec9 100644 (file)
@@ -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
index 3962787601f9ef789ea39d8ca108781384e41292..27ec94e0a42f61022c69ad8f034a5bff51e8075b 100644 (file)
@@ -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"));
index a42b789ef2661cfe9ec716b8cb0a96343dc6a584..a875d009cf06224603c38a5c860a4a0bea16e486 100644 (file)
@@ -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() {
index 8ba0f41af3ba6b627f1d9e1fa61af29ea9a0840f..bbe771988fd640ee673a81b28f8d04c16cc95191 100644 (file)
@@ -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
@@ -42,11 +68,107 @@ public class ShowThreeDFunction extends GenericFunction
                                I18nManager.getText("error.function.notavailable.title"), JOptionPane.WARNING_MESSAGE);
                }
                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.show();
                        }
                        catch (ThreeDException e)
index d23a2f72cee7920c3173ddebcab15401fceaae35..4ba4b8d6a40cbe08de0864a565552128b91afe7e 100644 (file)
@@ -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};
index 04e3cc14e60e5d01a1cb21f02d8cea9b73fb106b..59ef13a9d1248eb9add927dbfe3f8ba53958a487 100644 (file)
@@ -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);
index 5aa76aa76a1c570c89330fafab5192a329082b8a..ce0203cb9e221d3b8f00dd82e0d998629d6e1ee1 100644 (file)
@@ -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);
                }
index 73c867f8c5b786944a2ee7c652a43be5ee4048b1..f6ffe208b9383e68ab7f22177060a85c8646d0c2 100644 (file)
@@ -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";
-       }
 }
index 7a502e4332ed03e9aca1d451d170f7bd70aa4800..ef6979358edaefb64828469f18a2c41e6daf8846 100644 (file)
@@ -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);
+               }
+       }
 }
index e78994bbed0f574650dbc2669dcb06647b653e82..1b8046740b767b21bc8e55cdda4134831d092efa 100644 (file)
@@ -61,6 +61,19 @@ public class EstimationParameters
                _parseFailed = false;
        }
 
+       /**
+        * @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
index 8f4ea68bb5236e202c14a6b0cea481cf494610f3..74021dd3b2ba46fd03e6fddbd9054b50ba6ae65a 100644 (file)
@@ -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 (file)
index 0000000..6736f32
--- /dev/null
@@ -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 (file)
index 0000000..9f075f1
--- /dev/null
@@ -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 (file)
index 0000000..44ad9db
--- /dev/null
@@ -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 (file)
index 0000000..c482cda
--- /dev/null
@@ -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 (file)
index 0000000..91a7d52
--- /dev/null
@@ -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 (file)
index 0000000..8517cd0
--- /dev/null
@@ -0,0 +1,217 @@
+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 > 0)
+               {
+                       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;
+       }
+}
index 4d13348d3c4be742656f3b3690c7a9db09609ca8..2ed145439227af2d7ddf2e37ff2b1262698aece0 100644 (file)
@@ -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);
@@ -225,6 +284,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)
@@ -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;
+       }
 }
index d7ab38ff279ec6f9573cf457b7581062ffa6a688..301bbaf2ccb7849f1378a6f910999c188965a12a 100644 (file)
@@ -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;
 
        /**
@@ -25,6 +25,17 @@ public class SrtmTile
                _longitude = (int) Math.floor(longitude.getDouble());
        }
 
+       /**
+        * 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
diff --git a/tim/prune/function/weather/GetWeatherForecastFunction.java b/tim/prune/function/weather/GetWeatherForecastFunction.java
new file mode 100644 (file)
index 0000000..7b6f8c7
--- /dev/null
@@ -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 (file)
index 0000000..caeb184
--- /dev/null
@@ -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 (file)
index 0000000..5380ba7
--- /dev/null
@@ -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 (file)
index 0000000..93bcc12
--- /dev/null
@@ -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 (file)
index 0000000..664d7ae
--- /dev/null
@@ -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 (file)
index 0000000..cfc1947
--- /dev/null
@@ -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  + " &mdash; " + 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 (file)
index 0000000..83c5ca0
--- /dev/null
@@ -0,0 +1,133 @@
+package tim.prune.function.weather;
+
+import java.util.ArrayList;
+
+
+/**
+ * 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)
+               {
+                       _sunriseTime = inRiseTime.substring(11, 16);
+                       _sunsetTime  = 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;
+       }
+}
diff --git a/tim/prune/function/weather/WeatherTableModel.java b/tim/prune/function/weather/WeatherTableModel.java
new file mode 100644 (file)
index 0000000..274490e
--- /dev/null
@@ -0,0 +1,125 @@
+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("Wind", forecast.getWindDescription());
+                               case ROW_ICON: return forecast.getImageName();
+                               case ROW_TEMP: return buildDisplayString("Temp", forecast.getTemps()
+                                       + (_results.isCelsius() ? UNITS_DEGC : UNITS_DEGF));
+                               case ROW_HUMID: return buildDisplayString("Humidity", forecast.getHumidity());
+                       }
+                       // TODO: Use language-specific texts for wind, temp and humidity
+               }
+               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 + ":&nbsp;"))
+                       + "<big>" + inValue.replaceAll(" ", "&nbsp;") + "</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 (file)
index 0000000..d186bd2
--- /dev/null
@@ -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();
+       }
+}
index f1baa2a874f02de26f20f785fd9cbdbb7367aaac..39c55d4ecaf0d4b29be10647adfe604f83acbc3b 100644 (file)
@@ -52,8 +52,7 @@ public class DecimalNumberField extends JTextField
         */
        public DecimalNumberField()
        {
-               super(6);
-               setDocument(new DecimalNumberDocument(false));
+               this(false);
        }
 
        /**
index 13df7cd31609b0ea8ef1eb6b7fdb59d6639a722d..f66d99b3c469f7690400919faf909afba1ccbf67 100644 (file)
@@ -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/GenericProgressDialog.java b/tim/prune/gui/GenericProgressDialog.java
new file mode 100644 (file)
index 0000000..acef147
--- /dev/null
@@ -0,0 +1,117 @@
+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;
+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;
+
+/**
+ * Class to show a progress dialog for various time-consuming functions
+ */
+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;
+
+       /**
+        * 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 GenericProgressDialog(String inTitleKey, String inLabelKey,
+               JFrame inParentFrame, Cancellable inFunction)
+       {
+               _dialogTitleKey = inTitleKey;
+               _labelKey = inLabelKey;
+               if (_labelKey == null) {
+                       _labelKey = "confirm.running";
+               }
+               _parentFrame = inParentFrame;
+               _function = inFunction;
+       }
+
+       /**
+        * Create the dialog to show the progress
+        */
+       private void createProgressDialog()
+       {
+               _progressDialog = new JDialog(_parentFrame, I18nManager.getText(_dialogTitleKey));
+               _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(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)
+                       {
+                               _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();
+       }
+}
index e2a73079dd16e48e4cb5d5c9a31982b7f5f80e53..4de374ba179db06a71ca6d553167967f91442309 100644 (file)
@@ -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
index 16a0a66a73d7c84792063a4054dcaec256b78598..d2be8adbe7544f4684a46ab479d20316b84abacd 100644 (file)
@@ -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();
index d09087e7daa433f73f5d143a8d5cda14df979c64..363989ba7384a57fcfe1f53cd260a888dff992db 100644 (file)
@@ -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));
index 0f2b66b4610c93a102c7289414fac6af2c27c703..a4df9cd0df3a680ec486192c31c041e60ea0c446 100644 (file)
@@ -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 (file)
index 0000000..690c1ae
--- /dev/null
@@ -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());
+       }
+}
index 1b77fe151136ea8254f60622639a6823ec2c2bd9..48307c1a1f9cc54d107462a7a68f4008d9d938a4 100644 (file)
@@ -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()
index 350869713c0a2851ad5b47a17502b2422f303d11..3d5dd1fb62bafaa222df382109ead68756ad0605 100644 (file)
@@ -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()
index db0f827947475e692586c1f9ca01053f7ddff389..83a10bb7cd864e38198a126d86be30c7a40597a1 100644 (file)
@@ -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();
        }
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
diff --git a/tim/prune/gui/images/weather-clear-day.png b/tim/prune/gui/images/weather-clear-day.png
new file mode 100644 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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 (file)
index 0000000..86e4271
Binary files /dev/null and b/tim/prune/gui/images/weather-storm.png differ
index 350b3b4b1cdb1f2bbc53ff9f98accadbb1d0bbe5..459b140f2c0108b9adf35805bf1b0451d3f80811 100644 (file)
@@ -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;}
                        }
index a6d5a8861fb62600b9e006f36b29d7466d9dd456..a62b10855ce14d25299f41e891f1b432999c4aff 100644 (file)
@@ -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);
index 75f170a20f57df5ec5e84d3a2afa904a66d247e1..ed8849bd469118f5314322adbe77c1ba097523f4 100644 (file)
@@ -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
         */
@@ -68,6 +76,21 @@ public class MapTileManager implements ImageObserver
                return (_zoom > maxZoom);
        }
 
+       /**
+        * 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
         */
@@ -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 (file)
index 0000000..35a557e
--- /dev/null
@@ -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);
+}
index df820fc46225454430c1b28a3f6c5fed90426e8e..7c7540066c839d86105f71a0a95b3af1398d17de 100644 (file)
@@ -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);
index c99a13bbefef5f6fa73f3e1d35905ffc641039d4..f208cf0ad41d8d54ca81f8e815ba845a52c23bb2 100644 (file)
@@ -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
index 80573561282bcdac2c75a636f3ce431805aa0833..fc6c7ff18b50c69328b6b19193d9373a323f29c7 100644 (file)
@@ -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 prvn\u00ed, posledn\u00ed bod</td></tr><tr><td>Del</td><td>Smazat aktu\u00e1ln\u00ed bod</td></tr></table>
 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,22 @@ 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.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 +632,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 +721,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 +755,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 +767,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 +789,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 +813,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
@@ -811,3 +851,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.
index deb44edc31b5501daaaa1379f8a3d62c85f9c25e..91f75ac6b3e90e321606c1ccc0fb7e8d1706b314 100644 (file)
@@ -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,31 @@ 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.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 +580,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 +599,22 @@ 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.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 +632,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 +721,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 +761,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 +783,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 +807,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 +845,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.
index 27f3c7e6ca4a4cfeb470d5c9bcd6e8beeda513a5..fd56f48b79e328e2ac9626e2d9923993e0273564 100644 (file)
@@ -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,31 @@ 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.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 +575,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 +594,30 @@ 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.none=Kei Files abeglade, die sin 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 +628,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 +716,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 +756,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 +778,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 +802,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
@@ -804,3 +840,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.
index 925419c628a7ad15a36472bae78ad4f906a9ba74..03d04dfc528566bfca8cdd245829478909dabb8c 100644 (file)
@@ -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,31 @@ 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.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 +580,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 +599,22 @@ 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.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 +632,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 +721,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 +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=and
@@ -728,6 +767,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 +789,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 +813,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
@@ -811,3 +851,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.
index 4ddc66b15a258ace0636c295267e42f103020edf..182e6700b94c5564fa32b4c0b78e461fc0d9d87a 100644 (file)
@@ -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
@@ -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
@@ -106,6 +108,7 @@ 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
@@ -134,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
@@ -181,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
@@ -212,6 +225,7 @@ dialog.exportgpx.copysource=Copiar la fuente
 dialog.exportgpx.encoding=Codificaci\u00f3n
 dialog.exportgpx.encoding.system=Sistema
 dialog.exportgpx.encoding.utf8=UTF-8
+dialog.3d.useterrain=Terreno
 dialog.exportpov.text=Introduca los par\u00e1metros para exportar
 dialog.exportpov.font=Fuente
 dialog.exportpov.camerax=C\u00e1mara X
@@ -232,6 +246,8 @@ 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 del track
 dialog.pointtype.waypoint=Waypoints
@@ -252,6 +268,7 @@ dialog.clearundo.text=\u00bfEsta seguro que desea despejar la lista de deshacer?
 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
@@ -334,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
@@ -377,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
@@ -441,6 +458,7 @@ dialog.saveconfig.prune.mapsource=Proveedor de mapas seleccionado
 dialog.saveconfig.prune.mapsourcelist=Proveedor de mapas
 dialog.saveconfig.prune.diskcache=Memoria intermedia de mapas
 dialog.saveconfig.prune.kmzimagewidth=Ancho de im\u00e1genes en KMZ
+dialog.saveconfig.prune.kmzimageheight=Alto de im\u00e1genes en KMZ
 dialog.saveconfig.prune.colourscheme=Color de esquema
 dialog.saveconfig.prune.linewidth=Ancho de l\u00ednea
 dialog.saveconfig.prune.kmltrackcolour=Color de pista de KML
@@ -483,13 +501,22 @@ 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
 
 # 3d window
 dialog.3d.title=GpsPrune vista 3-D
@@ -511,8 +538,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
@@ -526,13 +552,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
@@ -638,7 +667,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
@@ -673,6 +701,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
@@ -681,6 +713,7 @@ logic.or=o
 # External urls
 url.googlemaps=maps.google.es
 wikipedia.lang=es
+openweathermap.lang=sp
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -724,10 +757,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
index 7d744f87151cc36c97eaccb7eb084e1a93627f6f..4e7ceef17082c14fb7c967032ac640f87c9a7f34 100644 (file)
@@ -134,6 +134,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
@@ -338,7 +339,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
@@ -381,8 +381,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
@@ -447,6 +446,7 @@ dialog.saveconfig.prune.mapsource=Carte source s\u00e9lectionn\u00e9e
 dialog.saveconfig.prune.mapsourcelist=Sources de cartes
 dialog.saveconfig.prune.diskcache=Cache de carte
 dialog.saveconfig.prune.kmzimagewidth=Largeur de l'image KMZ
+dialog.saveconfig.prune.kmzimageheight=Hauteur de l'image KMZ
 dialog.saveconfig.prune.colourscheme=Mod\u00e8le de couleurs
 dialog.saveconfig.prune.linewidth=Largeur de ligne
 dialog.saveconfig.prune.kmltrackcolour=Couleur de la trace KML
@@ -490,13 +490,30 @@ 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
 
 # 3d window
 dialog.3d.title=Vue 3D de GpsPrune
@@ -518,8 +535,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
@@ -533,13 +549,16 @@ 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.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.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
@@ -644,7 +663,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
@@ -687,6 +705,7 @@ logic.or=ou
 # External urls
 url.googlemaps=maps.google.fr
 wikipedia.lang=fr
+openweathermap.lang=fr
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -730,10 +749,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
index 8b2b35ba1c6586523c6b5c848893e2a72f20f548..e0e6b1ded649a8704fd87541210d77388d1fe5ad 100644 (file)
@@ -7,6 +7,7 @@ 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
@@ -57,6 +58,7 @@ 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,6 +86,7 @@ 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
@@ -99,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
@@ -132,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
@@ -139,10 +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.deleteaudio.deletepoint=T\u00f6rli a pontot, amely ehhez a hangf\u00e1jlhoz 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
@@ -157,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
@@ -175,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?
@@ -191,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
@@ -208,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
@@ -231,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
@@ -275,6 +331,27 @@ dialog.distances.toofewpoints=Ehhez a funkci\u00f3hoz \u00fatpontok kellenek, am
 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
@@ -310,7 +387,6 @@ dialog.correlate.photoselect.intro=V\u00e1lasszon egyet ezek k\u00f6z\u00fcl a m
 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
@@ -343,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
@@ -354,19 +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 \u00fajra
-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.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
@@ -383,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=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
@@ -398,7 +474,7 @@ dialog.checkversion.newversion1=El\u00e9rhet\u0151 a GpsPrune \u00faj verzi\u00f
 dialog.checkversion.newversion2=.
 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><td>Els\u0151, utols\u00f3 pont kiv\u00e1laszt\u00e1sa</td></tr><tr><td>Del</td><td>Jelenlegi pont t\u00f6rl\u00e9se</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -425,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
@@ -461,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
@@ -485,11 +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
@@ -503,13 +599,22 @@ 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.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
@@ -527,6 +632,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
@@ -541,6 +647,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
@@ -558,6 +665,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
@@ -605,7 +713,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
@@ -613,7 +721,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
@@ -628,20 +735,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
@@ -667,6 +788,8 @@ 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
@@ -689,10 +812,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
@@ -703,7 +824,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
@@ -728,3 +849,7 @@ error.cache.notthere=A csempegyors\u00edt\u00f3t\u00e1r k\u00f6nyvt\u00e1ra nem
 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.
index 5ee22f0a02514fd80bf4fd3b2ee00128f76b97dc..63c70ea13ff24e7d392956bbfee8c5e0b540378c 100644 (file)
@@ -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
@@ -487,6 +494,7 @@ dialog.saveconfig.prune.mapsource=Selezionale la fonte delle mappe
 dialog.saveconfig.prune.mapsourcelist=Fonte delle mappe
 dialog.saveconfig.prune.diskcache=Cache delle mappe
 dialog.saveconfig.prune.kmzimagewidth=larghezza immagine KMZ
+dialog.saveconfig.prune.kmzimageheight=altezza immagine KMZ
 dialog.saveconfig.prune.colourscheme=Schema colori
 dialog.saveconfig.prune.linewidth=Spessore linea
 dialog.saveconfig.prune.kmltrackcolour=Colore della traccia KML
@@ -530,13 +538,30 @@ 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
 
 # 3d window
 dialog.3d.title=Visione GpsPrune in 3D
@@ -549,7 +574,7 @@ 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
@@ -558,8 +583,7 @@ confirm.rearrangephotos=Foto riorganizzate
 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 +597,16 @@ 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.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 +624,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
@@ -720,6 +748,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 +760,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 +782,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 +806,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
index 78f2dc45ce96f2e776616012fa83d30e2109d8e1..5e09c49c5056b759799757b9c0d7c257abf3d878 100644 (file)
@@ -296,7 +296,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 +336,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 +371,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 +390,8 @@ 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.kmzimageheight=KML \u753b\u50cf\u9ad8
 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 +441,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 +455,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 +561,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 +632,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
index 70f895c6a00b54fa1ddc38ea691191dfa882fc3b..583c2c5d7e57c3a81ae973af3c1218be112dda0d 100644 (file)
@@ -295,7 +295,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 +339,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 +378,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 + Home, End</td><td>\ucc98\uc74c \uc9c0\uc810, \ub9c8\uc9c0\ub9c9 \uc9c0\uc810 \uc120\ud0dd</td></tr><tr><td>Del</td><td>\ud604\uc7ac \uc9c0\uc810 \uc0ad\uc81c</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -399,6 +398,7 @@ dialog.saveconfig.prune.mapsource=\uc120\ud0dd\ub41c \uc9c0\ub3c4 \uc704\uce58
 dialog.saveconfig.prune.mapsourcelist=\uc9c0\ub3c4 \uc18c\uc2a4
 dialog.saveconfig.prune.diskcache=\uc9c0\ub3c4 \uce90\uc2dc
 dialog.saveconfig.prune.kmzimagewidth=KMZ \uc774\ubbf8\uc9c0 \ub113\uc774
+dialog.saveconfig.prune.kmzimageheight=KMZ \uc774\ubbf8\uc9c0 \ub192\uc774
 dialog.saveconfig.prune.colourscheme=\uc0c9 \uad6c\uc131
 dialog.saveconfig.prune.linewidth=\ud2b8\ub799\uc120 \ub450\uaed8
 dialog.saveconfig.prune.kmltrackcolour=KML \ud2b8\ub799 \uc0c9
@@ -453,8 +453,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 +467,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 +576,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 +646,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.
index 1ec6b81146ae005ca56f09e2d65f428fbc62eca5..8c0e8a9cd8096264fd06f47ae5787bdf6ad9ac14 100644 (file)
@@ -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,31 @@ 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.creditnotice=Deze gegevens worden beschikbaar gesteld door openweathermap.org. Hun website heeft meer details.
 
 # 3d window
 dialog.3d.title=GpsPrune in 3D
@@ -555,11 +580,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 +599,22 @@ 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.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 +632,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 +721,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 +755,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 +765,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 +787,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 +811,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
@@ -811,3 +849,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.
index 333caa79679a78c4d582e8d4c782bc857cc14f64..212eb695b30b10c751851f1bf6fd3ef77808a2b9 100644 (file)
@@ -107,6 +107,7 @@ 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
@@ -135,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
@@ -200,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
@@ -225,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=KML ze znacznikami czasu
+dialog.exportkml.extendedkml=Standardowy KML ze znacznikami czasu
 dialog.exportgpx.name=Nazwa
 dialog.exportgpx.desc=Opis
 dialog.exportgpx.includetimestamps=Do\u0142\u0105cz znaczniki czasu
@@ -257,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
@@ -378,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
@@ -421,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
@@ -467,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
@@ -530,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
@@ -558,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
@@ -573,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
@@ -597,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
@@ -685,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
@@ -720,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
@@ -728,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
@@ -771,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
index dc9a8038d92190be0038c310347f91c24985e87e..f70a01e9497d2d2624770e77f2192f3f135ddc98 100644 (file)
@@ -7,9 +7,11 @@ 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
@@ -20,8 +22,6 @@ menu.range.all=Selectionar tudo
 menu.range.none=N\u00e3o selecionar nenhuns
 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
@@ -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,11 +102,16 @@ 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.downloadsrtm=Baixar arquivos SRTM
 function.getwikipedia=Obter artigos da Wikipedia das redondezas
 function.searchwikipedianames=Procurar na Wikipedia por nome
 function.downloadosm=Baixar dados OSM para a \u00e1rea
@@ -129,6 +140,7 @@ 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
@@ -139,6 +151,7 @@ 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
@@ -187,7 +228,10 @@ 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.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
@@ -204,11 +248,25 @@ 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.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
@@ -220,6 +278,7 @@ dialog.confirmreversetrack.text=Esta rota possui informa\u00e7\u00f5es de data-h
 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.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
@@ -229,6 +288,7 @@ dialog.clearundo.text=Voc\u00ea tem certeza que deseja limpar a lista de desfaze
 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
@@ -299,11 +382,11 @@ 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.nouncorrelatedaudios=Existem \u00e1udios n\u00e3o correlacionados.\nVoc\u00ea tem certeza 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,31 @@ 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.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 +580,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 +599,22 @@ 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.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 +632,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 +647,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 +658,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 +702,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 +721,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 +735,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 +784,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 +813,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
@@ -705,7 +844,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.
index 992d246f1e5fcc347f40aa1a50364e78ddaf660f..b94b88c63b1eef426ef123c46803fd3824655b98 100644 (file)
@@ -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
index 3a691c83eddc64a45accdcec9618a6990236fca8..ce483bd554c74a1f629e57abace781711104787f 100644 (file)
@@ -318,7 +318,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\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.
 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 +360,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,8 +468,7 @@ 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)
@@ -498,8 +495,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 +509,16 @@ 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.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
+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\u0447\u0435\u0441\u043a\u0438.
+
 # Buttons
 button.ok=OK
 button.back=\u041d\u0430\u0437\u0430\u0434
@@ -623,7 +622,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 +708,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
diff --git a/tim/prune/lang/prune-texts_sv.properties b/tim/prune/lang/prune-texts_sv.properties
new file mode 100644 (file)
index 0000000..eeffcd9
--- /dev/null
@@ -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
index 32eed1293e4155867020c1db585ce1ccee02acfa..e509978ee6a2cf3ddb10c338f178b19c1a6ad479 100644 (file)
@@ -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:
@@ -269,12 +269,13 @@ dialog.saveconfig.prune.exiftoolpath=exiftool'un yeriyolu
 dialog.saveconfig.prune.mapserverindex=Harita sunucunun index
 dialog.saveconfig.prune.mapserverurl=Harita sunucunun adresi
 dialog.saveconfig.prune.kmzimagewidth=KMZ resim geni\u015fli\u011fi
+dialog.saveconfig.prune.kmzimageheight=KMZ resim y\u00fcksekli\u011fi
 dialog.setpaths.intro=\u0130ste\u011fe ba\u011fl\u0131 a\u015fa\u011f\u0131daki uygulamalar\u0131n veriyolu kaydedebilirsin:
 dialog.addaltitude.noaltitudes=Se\u00e7ili s\u0131rada y\u00fckseklik bilgisi bulunmad\u0131
 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 +301,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 +401,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 (file)
index 0000000..6f74d55
--- /dev/null
@@ -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
+#############################
+
index 2a605bf83411d6adc659c9ea5e4771fd18f618af..d0225139fddbec476e7b110ddd2f0d1f91f6bc66 100644 (file)
@@ -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,22 @@ 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.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 +632,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 +721,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 +755,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 +767,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 +789,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 +812,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 +825,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 +850,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
index 2d1c9e80098ee85d29250b68b36b5c075389a3bc..16030ca64cbb03223c4abd0ca32c89f0c4c8a0db 100644 (file)
@@ -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);
        }
 
index 1897e0cdcf9d717e442ffe2522c43223a2d466fb..187bb495c196ead68ad00b73a5926041267360e8 100644 (file)
@@ -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));
@@ -226,10 +226,8 @@ 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);
        }
 }
index 22383c6acb544f6f7e754f50e8b98eae1d645120..a151c2e285022f48c5ffe8bbdf2c3d8433c09df0 100644 (file)
@@ -49,7 +49,7 @@ public class FileCacher
                                {
                                        if (currLine.indexOf('\0') >= 0)
                                        {
-                                               try {reader.close();} catch (IOException ioe2) {}
+                                               reader.close();
                                                return; // it's a binary file, shouldn't use this cacher
                                        }
                                        if (currLine.trim().length() > 0)
index a052b55d14fca6dabc18497775575005945410bd..9372704ade85ae469ee367695464f6c611cafe7c 100644 (file)
@@ -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);
        }
 }
index 53d6d2a77d50de6355be51ff0f19af1808b9d808..8fd30b26303b689263d49d70cc05799129e8ead7 100644 (file)
@@ -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++)
                {
index f310031fad6810e7191892263f1b7f7db1e6b048..22f36f72e87a3b1a820494d1f27373cb54740777 100644 (file)
@@ -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);
index 7554160a78a61633e4319d92eb9cb9c4bce38099..4826bc872e864e2c67580d12c5999ffbd5492331 100644 (file)
@@ -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);
index a1698460b161c0d334f85f714e14a53102fd2430..9670fb2350f011e374c016f82d65f228895579c7 100644 (file)
@@ -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);
index ebbc9f4f6ee3cbc538ac9e84789ed553bac829c9..1567c883dc1a60bc65f56fa0e273c0bfc48aba49 100644 (file)
@@ -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())
                });
index 3ebd4caae7658eca9e345ada1c25d202ef4fde53..4be2bab3fc521627a65b793d490f5a01c173bfd4 100644 (file)
@@ -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");
index 783ac798ed3d0bf9a6a580cb1ee33b4cc1ebfb18..5af471a2119e2e839589ec1af042d479d75f6016 100644 (file)
@@ -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)
index 74e48f9497c6ab387dd779b2a97747511e320a85..eb2f9a604d3aefd2fabcb9ad33f73ec2004dde28 100644 (file)
@@ -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");
index 4bf30f1521a43d5eb7a4fe8bcf09afcd5bd21b5d..93303b205e0932bf640aceb56e4fc9cfad6ac75f 100644 (file)
@@ -1,11 +1,11 @@
-GpsPrune version 15.2
-=====================
+GpsPrune version 16
+===================
 
 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.2.jar
+   java -jar gpsprune_16.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,21 @@ 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.2.jar --lang=DE
+   java -jar gpsprune_16.jar --lang=DE
 
 
-New with version 15.2
+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 +53,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
index 514ebd79ec46d8c8fa5203d9f460872ddbc66d49..026556924f06408d3b735dcce2dce98ecf1757e6 100644 (file)
@@ -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;
@@ -83,6 +85,17 @@ public class BaseImageConfigDialog implements Runnable
                _track = inTrack;
        }
 
+       /**
+        * @param inDefinition image definition object from previous dialog
+        */
+       public void setImageDefinition(ImageDefinition inDefinition)
+       {
+               _imageDef = inDefinition;
+               if (_imageDef == null) {
+                       _imageDef = new ImageDefinition();
+               }
+       }
+
        /**
         * Begin the function
         */
@@ -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 (file)
index 0000000..3930099
--- /dev/null
@@ -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();
+}
index 3c1a7b814f605777054862d04632f3123ed05794..d17c5d48fe4ca951ae58518b408c3724d7fbd5e5 100644 (file)
@@ -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
index 8d9df8a0c15f8a10f79fc98a0368b609b841d617..a4ac1a7de2e72ade39734e7f0099fe6e14f5faca 100644 (file)
@@ -694,6 +694,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"
index 44000678ea420e73eed834029a8e1d569dbe1817..d5cbc05bd3a51a69af748a5d42f7d71e93ee7420 100644 (file)
@@ -37,13 +37,6 @@ public class GroutedImage
                return _image != null && _numTilesFound > 0;
        }
 
-       /**
-        * @return true if all the required tiles were found
-        */
-       public boolean isComplete() {
-               return _numTilesMissing == 0;
-       }
-
        /**
         * @return the pixel dimensions of the result image
         */
index fabed33bb9553583cf7d18986dc2289e27ca0eb2..41bc0e6d0e7f186bc884ad98b080de4b0474bef6 100644 (file)
@@ -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);
+       }
 }
index 65e23c09fabe3962f41833a6a4c64467a97bda94..263a3067f4902b5916c2b5e43d63bfeccdb21c5c 100644 (file)
@@ -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
+       }
 }
index 135ccbbe2fa1c5f776e1d78045bb9969b0890e10..1f29d4a70bb075420db66a471ca7a9d7e730c28d 100644 (file)
@@ -26,27 +26,30 @@ 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.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 +58,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 +111,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 +129,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 +161,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 +177,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 +240,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 +254,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 +301,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 +339,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 +358,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 +388,39 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
                                }
                        }
 
+                       boolean useTerrain = _terrainPanel.getUseTerrain();
+                       if (useTerrain)
+                       {
+                               TerrainHelper terrainHelper = new TerrainHelper(_terrainPanel.getGridSize());
+                               Track 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);
+
+                               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 +459,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 +482,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 +588,8 @@ public class PovExporter extends Export3dFunction implements DataSubscriber
                  "box {",
                  boxDefinition,
                  "}", "",
+                 // terrain
+                 terrainDefinition,
                // write cardinals
                  "// Cardinal letters N,S,E,W",
                  "text {",
@@ -612,6 +628,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
@@ -876,16 +917,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) {
-       }
 }
index 33f42f95a75bbbb3d4d671edc44b76a824c481ca..d623b4af3d2721196b5910ac73562d70fba4d1c1 100644 (file)
@@ -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/threedee/ImageDefinition.java b/tim/prune/threedee/ImageDefinition.java
new file mode 100644 (file)
index 0000000..a456f37
--- /dev/null
@@ -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;
+       }
+}
index cfac9afabf1798d87e33f199371ed98ba62fb743..ba1ed1530c282e5607d24816bc391f72d68d48c7 100644 (file)
@@ -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,25 @@ 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.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 +68,10 @@ 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;
 
        /** only prompt about big track size once */
        private static boolean TRACK_SIZE_WARNING_GIVEN = false;
@@ -90,23 +103,44 @@ 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;
+       }
 
        /**
         * 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 +304,64 @@ 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)
+               {
+                       // TODO: Is it maybe possible to cache the last terrainTrack?
+                       //       (if the dataTrack and the resolution haven't changed)
+                       // Construct the terrain track according to these extents and the grid size
+                       TerrainHelper terrainHelper = new TerrainHelper(_terrainDefinition.getGridSize());
+                       Track 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);
+
+                       // 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 +381,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 +494,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 +506,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 +564,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 +630,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/TerrainDefinition.java b/tim/prune/threedee/TerrainDefinition.java
new file mode 100644 (file)
index 0000000..0b35670
--- /dev/null
@@ -0,0 +1,54 @@
+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;
+       }
+}
diff --git a/tim/prune/threedee/TerrainHelper.java b/tim/prune/threedee/TerrainHelper.java
new file mode 100644 (file)
index 0000000..71bc196
--- /dev/null
@@ -0,0 +1,423 @@
+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?
+               fixCorners(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)
+       {
+               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, if they're blank
+        * @param inTerrainTrack terrain track
+        */
+       private void fixCorners(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);
+       }
+
+       /**
+        * 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);
+                       }
+               }
+       }
+
+       /**
+        * 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));
+                       }
+               }
+       }
+}
index ddf91623b717309daf1bb3bfcd82dadefe850097..abe166c837f88d5c65def6dd947eeedd9b4209f7 100644 (file)
@@ -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;
@@ -36,6 +37,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
         */
@@ -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,12 +142,47 @@ 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
index df72bc758f9431bea0f5e069d97e514042ad1b5d..b7c73d29cf5a6c2fd85d9627531912ea18029025 100644 (file)
@@ -14,6 +14,20 @@ 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);
 
        /**
         * Show the window
index 549c11a1ef97439899d759c22fa1a9fa84cfab90..42ff8a46b906c3030903eb0064ca6a5eac31f4c4 100644 (file)
@@ -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 (file)
index 0000000..c58fe5c
--- /dev/null
@@ -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 (file)
index 0000000..24c12bc
--- /dev/null
@@ -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;
+       }
+}
index 9a9f7a428ce95be69b66a3741efe120b2afe6c81..ffaabdd7ca77021a6c3de9126cb66d4cc81754b5 100644 (file)
@@ -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();
index 4287baba51a50e825abffa7c2df07deb688d4816..2a6f9ca10d56cd936861d3426fd6dbb10c92d9c3 100644 (file)
@@ -89,6 +89,7 @@ public class UndoCutAndMove implements UndoOperation
                if (_moveTrackPoint != null) {
                        _moveTrackPoint.setSegmentStart(_moveToSegmentFlag);
                }
+               inTrackInfo.getSelection().clearAll();
                UpdateMessageBroker.informSubscribers();
        }
 }
index 0a4973284dd4c02f32c880a1ed3beb260804dd94..2b698654ec7e75562201154b18d41c409f3ba4b5 100644 (file)
@@ -9,7 +9,7 @@ import tim.prune.data.TrackInfo;
 /**\r
  * Operation to undo a delete of a single audio item, either with or without point\r
  */\r
-public class UndoDeleteAudio implements UndoOperation\r
+public class UndoDeleteAudio extends UndoDeleteOperation\r
 {\r
        private int _audioIndex = -1;\r
        private AudioClip _audio = null;\r
@@ -55,6 +55,8 @@ public class UndoDeleteAudio implements UndoOperation
                        if (!inTrackInfo.getTrack().insertPoint(_point, _pointIndex)) {\r
                                throw new UndoException(getDescription());\r
                        }\r
+                       // Change the current point/range selection if required\r
+                       modifySelection(inTrackInfo, _pointIndex, _pointIndex);\r
                }\r
                else\r
                {\r
diff --git a/tim/prune/undo/UndoDeleteOperation.java b/tim/prune/undo/UndoDeleteOperation.java
new file mode 100644 (file)
index 0000000..d99a9f6
--- /dev/null
@@ -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);
+               }
+       }
+}
index 5edd37bfb664cb3a65fa1d52d37f097440d0bb97..45be6addcb5040fa2844caad22f0c236ddd6fa91 100644 (file)
@@ -9,7 +9,7 @@ import tim.prune.data.TrackInfo;
 /**\r
  * Operation to undo a delete of a single photo, either with or without point\r
  */\r
-public class UndoDeletePhoto implements UndoOperation\r
+public class UndoDeletePhoto extends UndoDeleteOperation\r
 {\r
        private int _photoIndex = -1;\r
        private Photo _photo = null;\r
@@ -58,6 +58,8 @@ public class UndoDeletePhoto implements UndoOperation
                        {\r
                                throw new UndoException(getDescription());\r
                        }\r
+                       // Change the current point/range selection if required\r
+                       modifySelection(inTrackInfo, _pointIndex, _pointIndex);\r
                }\r
                else\r
                {\r
index 9919d0d3a7dd9cac197bc2059a15a38c996bb88e..beddfbf32588eec6a16a0657c6935f5878740ce4 100644 (file)
@@ -7,7 +7,7 @@ import tim.prune.data.TrackInfo;
 /**\r
  * Operation to undo a delete of a single point\r
  */\r
-public class UndoDeletePoint implements UndoOperation\r
+public class UndoDeletePoint extends UndoDeleteOperation\r
 {\r
        private int _pointIndex = -1;\r
        private DataPoint _point = null;\r
@@ -89,5 +89,7 @@ public class UndoDeletePoint implements UndoOperation
                                nextTrackPoint.setSegmentStart(false);\r
                        }\r
                }\r
+               // If there's a current point or range selected, maybe need to adjust start and/or end\r
+               modifySelection(inTrackInfo, _pointIndex, _pointIndex);\r
        }\r
 }\r
index 58f09e9c267afd867c104fa316fc058878367b89..da42bc411c892c11814a8daedf135e8f3864aad5 100644 (file)
@@ -9,7 +9,7 @@ import tim.prune.data.TrackInfo;
 /**\r
  * Operation to undo a delete of a range of points\r
  */\r
-public class UndoDeleteRange implements UndoOperation\r
+public class UndoDeleteRange extends UndoDeleteOperation\r
 {\r
        /**\r
         * Inner class to hold a single range information set\r
@@ -40,6 +40,14 @@ public class UndoDeleteRange implements UndoOperation
                {\r
                        return _startIndex >= 0 && _points != null && _points.length > 0;\r
                }\r
+\r
+               /**\r
+                * @return end index of range\r
+                */\r
+               public int getEndIndex()\r
+               {\r
+                       return _startIndex + _points.length - 1;\r
+               }\r
        }\r
 \r
 \r
@@ -141,6 +149,13 @@ public class UndoDeleteRange implements UndoOperation
                // Undo both the ranges\r
                performUndo(inTrackInfo, _rangeInfo1);\r
                performUndo(inTrackInfo, _rangeInfo2);\r
+               // If there's a current point/range selected, maybe need to adjust start and/or end\r
+               if (_rangeInfo1 != null && _rangeInfo1.isValid()) {\r
+                       modifySelection(inTrackInfo, _rangeInfo1._startIndex, _rangeInfo1.getEndIndex());\r
+               }\r
+               if (_rangeInfo2 != null && _rangeInfo2.isValid()) {\r
+                       modifySelection(inTrackInfo, _rangeInfo2._startIndex, _rangeInfo2.getEndIndex());\r
+               }\r
        }\r
 \r
        /**\r
index e1d5539e46d8b70a4e95a6d911e60f7195114748..38c460e7f65fa309d0eba8be982e4acbb0a99957 100644 (file)
@@ -42,5 +42,6 @@ public abstract class UndoReorder implements UndoOperation
        {\r
                // restore track to previous values\r
                inTrackInfo.getTrack().replaceContents(_contents);\r
+               inTrackInfo.getSelection().clearAll();\r
        }\r
-}
\ No newline at end of file
+}\r
diff --git a/tim/prune/undo/UndoSewSegments.java b/tim/prune/undo/UndoSewSegments.java
new file mode 100644 (file)
index 0000000..fbcf853
--- /dev/null
@@ -0,0 +1,44 @@
+package tim.prune.undo;\r
+\r
+import tim.prune.data.DataPoint;\r
+import tim.prune.data.Track;\r
+import tim.prune.data.TrackInfo;\r
+\r
+/**\r
+ * Operation to undo the sewing together of track segments\r
+ */\r
+public class UndoSewSegments extends UndoReorder\r
+{\r
+       /** All segment start flags need to be remembered as well */\r
+       private boolean[] _segmentStartFlags = null;\r
+\r
+       /**\r
+        * Constructor\r
+        * @param inTrack track contents to copy\r
+        */\r
+       public UndoSewSegments(Track inTrack)\r
+       {\r
+               super(inTrack, "undo.sewsegments");\r
+               // Also remember segment start flags, as they may have been changed by reversals\r
+               final int numPoints = inTrack.getNumPoints();\r
+               _segmentStartFlags = new boolean[numPoints];\r
+               for (int i=0; i<numPoints; i++) {\r
+                       _segmentStartFlags[i] = inTrack.getPoint(i).getSegmentStart();\r
+               }\r
+       }\r
+\r
+       /** Perform the undo */\r
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
+       {\r
+               // Put all the points back in the right order\r
+               super.performUndo(inTrackInfo);\r
+               // And then restore the segment flags\r
+               for (int i=0; i<_segmentStartFlags.length; i++)\r
+               {\r
+                       DataPoint point = inTrackInfo.getTrack().getPoint(i);\r
+                       if (point != null && !point.isWaypoint()) {\r
+                               point.setSegmentStart(_segmentStartFlags[i]);\r
+                       }\r
+               }\r
+       }\r
+}\r
diff --git a/tim/prune/undo/UndoSplitSegments.java b/tim/prune/undo/UndoSplitSegments.java
new file mode 100644 (file)
index 0000000..85968b7
--- /dev/null
@@ -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");
+       }
+}